Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
12814 lines (10702 sloc) 337 KB
/*
*
* Iter Vehemens ad Necem (IVAN)
* Copyright (C) Timo Kiviluoto
* Released under the GNU General
* Public License
*
* See LICENSING which should be included
* along with this file for more details
*
*/
/* Compiled through charset.cpp */
/* These statedata structs contain functions and values used for handling
* states. Remember to update them. All normal states must have
* PrintBeginMessage and PrintEndMessage functions and a Description string.
* BeginHandler, EndHandler, Handler (called each tick) and IsAllowed are
* optional, enter zero if the state doesn't need one. If the SECRET flag
* is set, Description is not shown in the panel without magical means.
* You can also set some source (SRC_*) and duration (DUR_*) flags, which
* control whether the state can be randomly activated in certain situations.
* These flags can be found in ivandef.h. RANDOMIZABLE sets all source
* & duration flags at once. */
#include "hiteffect.h" //TODO move to charsset.cpp?
#include "lterras.h"
#include "gods.h"
//#define DBGMSG_V2
#include "dbgmsgproj.h"
#include <bitset>
#include <cmath>
struct statedata
{
cchar* Description;
int Flags;
void (character::*PrintBeginMessage)() const;
void (character::*PrintEndMessage)() const;
void (character::*BeginHandler)();
void (character::*EndHandler)();
void (character::*Handler)();
truth (character::*IsAllowed)() const;
void (character::*SituationDangerModifier)(double&) const;
};
statedata StateData[STATES] =
{
{
"Polymorphed",
NO_FLAGS,
0,
0,
0,
&character::EndPolymorph,
0,
0,
0
}, {
"Hasted",
RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
&character::PrintBeginHasteMessage,
&character::PrintEndHasteMessage,
0,
0,
0,
0,
0
}, {
"Slowed",
RANDOMIZABLE&~SRC_GOOD,
&character::PrintBeginSlowMessage,
&character::PrintEndSlowMessage,
0,
0,
0,
0,
0
}, {
"PolyControl",
RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL|SRC_GOOD),
&character::PrintBeginPolymorphControlMessage,
&character::PrintEndPolymorphControlMessage,
0,
0,
0,
0,
0
}, {
"Life Saved",
SECRET,
&character::PrintBeginLifeSaveMessage,
&character::PrintEndLifeSaveMessage,
0,
0,
0,
0,
0
}, {
"Lycanthropy",
SECRET|SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
&character::PrintBeginLycanthropyMessage,
&character::PrintEndLycanthropyMessage,
0,
0,
&character::LycanthropyHandler,
0,
&character::LycanthropySituationDangerModifier
}, {
"Invisible",
RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
&character::PrintBeginInvisibilityMessage,
&character::PrintEndInvisibilityMessage,
&character::BeginInvisibility, &character::EndInvisibility,
0,
0,
0
}, {
"Infravision",
RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
&character::PrintBeginInfraVisionMessage,
&character::PrintEndInfraVisionMessage,
&character::BeginInfraVision,
&character::EndInfraVision,
0,
0,
0
}, {
"ESP",
RANDOMIZABLE&~SRC_EVIL,
&character::PrintBeginESPMessage,
&character::PrintEndESPMessage,
&character::BeginESP,
&character::EndESP,
0,
0,
0
}, {
"Poisoned",
DUR_TEMPORARY,
&character::PrintBeginPoisonedMessage,
&character::PrintEndPoisonedMessage,
0,
0,
&character::PoisonedHandler,
&character::CanBePoisoned,
&character::PoisonedSituationDangerModifier
}, {
"Teleporting",
SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
&character::PrintBeginTeleportMessage,
&character::PrintEndTeleportMessage,
0,
0,
&character::TeleportHandler,
0,
0
}, {
"Polymorphing",
SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_GOOD)),
&character::PrintBeginPolymorphMessage,
&character::PrintEndPolymorphMessage,
0,
0,
&character::PolymorphHandler,
0,
&character::PolymorphingSituationDangerModifier
}, {
"TeleControl",
RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL),
&character::PrintBeginTeleportControlMessage,
&character::PrintEndTeleportControlMessage,
0,
0,
0,
0,
0
}, {
"Panicked",
NO_FLAGS,
&character::PrintBeginPanicMessage,
&character::PrintEndPanicMessage,
&character::BeginPanic,
&character::EndPanic,
0,
&character::CanPanic,
&character::PanicSituationDangerModifier
}, {
"Confused",
SECRET|(RANDOMIZABLE&~(DUR_PERMANENT|SRC_GOOD)),
&character::PrintBeginConfuseMessage,
&character::PrintEndConfuseMessage,
0,
0,
0,
&character::CanBeConfused,
&character::ConfusedSituationDangerModifier
}, {
"Parasite (tapeworm)",
SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
&character::PrintBeginParasitizedMessage,
&character::PrintEndParasitizedMessage,
0,
0,
&character::ParasitizedHandler,
&character::CanBeParasitized,
&character::ParasitizedSituationDangerModifier
}, {
"Searching",
NO_FLAGS,
&character::PrintBeginSearchingMessage,
&character::PrintEndSearchingMessage,
0,
0,
&character::SearchingHandler,
0,
0
}, {
"Gas Immunity",
SECRET|(RANDOMIZABLE&~(SRC_GOOD|SRC_EVIL)),
&character::PrintBeginGasImmunityMessage,
&character::PrintEndGasImmunityMessage,
0,
0,
0,
0,
0
}, {
"Levitating",
RANDOMIZABLE&~SRC_EVIL,
&character::PrintBeginLevitationMessage,
&character::PrintEndLevitationMessage,
0,
&character::EndLevitation,
0,
0,
0
}, {
"Leprosy",
SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
&character::PrintBeginLeprosyMessage,
&character::PrintEndLeprosyMessage,
&character::BeginLeprosy,
&character::EndLeprosy,
&character::LeprosyHandler,
0,
&character::LeprosySituationDangerModifier
}, {
"Hiccups",
SRC_FOUNTAIN|SRC_CONFUSE_READ|DUR_FLAGS,
&character::PrintBeginHiccupsMessage,
&character::PrintEndHiccupsMessage,
0,
0,
&character::HiccupsHandler,
0,
&character::HiccupsSituationDangerModifier
}, {
"Ethereal",
NO_FLAGS,
&character::PrintBeginEtherealityMessage,
&character::PrintEndEtherealityMessage,
&character::BeginEthereality, &character::EndEthereality,
0,
0,
0
}, {
"Vampirism",
DUR_FLAGS,
&character::PrintBeginVampirismMessage,
&character::PrintEndVampirismMessage,
0,
0,
&character::VampirismHandler,
0,
0
}, {
"Swimming",
SECRET|(RANDOMIZABLE&~SRC_EVIL),
&character::PrintBeginSwimmingMessage,
&character::PrintEndSwimmingMessage,
&character::BeginSwimming, &character::EndSwimming,
0,
0,
0
}, {
"Detecting",
SECRET|(RANDOMIZABLE&~(SRC_MUSHROOM|SRC_EVIL)),
&character::PrintBeginDetectMessage,
&character::PrintEndDetectMessage,
0,
0,
&character::DetectHandler,
0,
0
}, {
"Polymorph Locked",
SECRET|(RANDOMIZABLE&~SRC_EVIL),
&character::PrintBeginPolymorphLockMessage,
&character::PrintEndPolymorphLockMessage,
0,
0,
&character::PolymorphLockHandler,
0,
0
}, {
"Regenerating",
SECRET|(RANDOMIZABLE&~SRC_EVIL),
&character::PrintBeginRegenerationMessage,
&character::PrintEndRegenerationMessage,
0,
0,
0,
0,
0
}, {
"Disease Immunity",
SECRET|(RANDOMIZABLE&~SRC_EVIL),
&character::PrintBeginDiseaseImmunityMessage,
&character::PrintEndDiseaseImmunityMessage,
0,
0,
0,
0,
0
}, {
"Teleport Locked",
SECRET,
&character::PrintBeginTeleportLockMessage,
&character::PrintEndTeleportLockMessage,
0,
0,
&character::TeleportLockHandler,
0,
0
}, {
"Fearless",
RANDOMIZABLE&~SRC_EVIL,
&character::PrintBeginFearlessMessage,
&character::PrintEndFearlessMessage,
0,
0,
0,
0,
0
}, {
"Fasting",
SECRET|(RANDOMIZABLE&~SRC_EVIL),
&character::PrintBeginFastingMessage,
&character::PrintEndFastingMessage,
0,
0,
0,
0,
0
}, {
"Parasite (mindworm)",
SECRET|(RANDOMIZABLE&~DUR_TEMPORARY),
&character::PrintBeginMindwormedMessage,
&character::PrintEndMindwormedMessage,
0,
0,
&character::MindwormedHandler,
&character::CanBeParasitized, // We are using TorsoIsAlive right now, but I think it's OK,
// because with unliving torso, your head will not be much better for mind worms.
&character::ParasitizedSituationDangerModifier
}
};
characterprototype::characterprototype(const characterprototype* Base,
characterspawner Spawner,
charactercloner Cloner,
cchar* ClassID)
: Base(Base), Spawner(Spawner), Cloner(Cloner), ClassID(ClassID)
{ Index = protocontainer<character>::Add(this); }
std::list<character*>::iterator character::GetTeamIterator()
{ return TeamIterator; }
void character::SetTeamIterator(std::list<character*>::iterator What)
{ TeamIterator = What; }
void character::CreateInitialEquipment(int SpecialFlags)
{ AddToInventory(DataBase->Inventory, SpecialFlags); }
void character::EditAP(long What)
{ AP = Limit<long>(AP + What, -12000, 1200); }
int character::GetRandomStepperBodyPart() const { return TORSO_INDEX; }
void character::GainIntrinsic(long What)
{ BeginTemporaryState(What, PERMANENT); }
truth character::IsUsingArms() const { return GetAttackStyle() & USE_ARMS; }
truth character::IsUsingLegs() const { return GetAttackStyle() & USE_LEGS; }
truth character::IsUsingHead() const { return GetAttackStyle() & USE_HEAD; }
void character::CalculateAllowedWeaponSkillCategories()
{ AllowedWeaponSkillCategories = MARTIAL_SKILL_CATEGORIES; }
festring character::GetBeVerb() const
{ return IsPlayer() ? CONST_S("are") : CONST_S("is"); }
void character::SetEndurance(int What)
{ BaseExperience[ENDURANCE] = What * EXP_MULTIPLIER; }
void character::SetPerception(int What)
{ BaseExperience[PERCEPTION] = What * EXP_MULTIPLIER; }
void character::SetIntelligence(int What)
{ BaseExperience[INTELLIGENCE] = What * EXP_MULTIPLIER; }
void character::SetWisdom(int What)
{ BaseExperience[WISDOM] = What * EXP_MULTIPLIER; }
void character::SetWillPower(int What)
{ BaseExperience[WILL_POWER] = What * EXP_MULTIPLIER; }
void character::SetCharisma(int What)
{ BaseExperience[CHARISMA] = What * EXP_MULTIPLIER; }
void character::SetMana(int What)
{ BaseExperience[MANA] = What * EXP_MULTIPLIER; }
truth character::IsOnGround() const
{ return MotherEntity && MotherEntity->IsOnGround(); }
truth character::LeftOversAreUnique() const
{ return GetArticleMode() || AssignedName.GetSize(); }
truth character::HomeDataIsValid() const
{ return (HomeData && HomeData->Level == GetLSquareUnder()->GetLevelIndex()
&& HomeData->Dungeon == GetLSquareUnder()->GetDungeonIndex()); }
void character::SetHomePos(v2 Pos) { HomeData->Pos = Pos; }
cchar* character::FirstPersonUnarmedHitVerb() const { return "hit"; }
cchar* character::FirstPersonCriticalUnarmedHitVerb() const
{ return "critically hit"; }
cchar* character::ThirdPersonUnarmedHitVerb() const { return "hits"; }
cchar* character::ThirdPersonCriticalUnarmedHitVerb() const
{ return "critically hits"; }
cchar* character::FirstPersonKickVerb() const { return "kick"; }
cchar* character::FirstPersonCriticalKickVerb() const
{ return "critically kick"; }
cchar* character::ThirdPersonKickVerb() const { return "kicks"; }
cchar* character::ThirdPersonCriticalKickVerb() const
{ return "critically kicks"; }
cchar* character::FirstPersonBiteVerb() const { return "bite"; }
cchar* character::FirstPersonCriticalBiteVerb() const
{ return "critically bite"; }
cchar* character::ThirdPersonBiteVerb() const { return "bites"; }
cchar* character::ThirdPersonCriticalBiteVerb() const
{ return "critically bites"; }
cchar* character::UnarmedHitNoun() const { return "attack"; }
cchar* character::KickNoun() const { return "kick"; }
cchar* character::BiteNoun() const { return "attack"; }
cchar* character::GetEquipmentName(int) const { return ""; }
const std::list<ulong>& character::GetOriginalBodyPartID(int I) const
{ return OriginalBodyPartID[I]; }
square* character::GetNeighbourSquare(int I) const
{ return GetSquareUnder()->GetNeighbourSquare(I); }
lsquare* character::GetNeighbourLSquare(int I) const
{ return static_cast<lsquare*>(GetSquareUnder())->GetNeighbourLSquare(I); }
wsquare* character::GetNeighbourWSquare(int I) const
{ return static_cast<wsquare*>(GetSquareUnder())->GetNeighbourWSquare(I); }
god* character::GetMasterGod() const { return game::GetGod(GetConfig()); }
col16 character::GetBodyPartColorA(int, truth) const
{ return GetSkinColor(); }
col16 character::GetBodyPartColorB(int, truth) const
{ return GetTorsoMainColor(); }
col16 character::GetBodyPartColorC(int, truth) const
{ return GetBeltColor(); } // sorry...
col16 character::GetBodyPartColorD(int, truth) const
{ return GetTorsoSpecialColor(); }
int character::GetRandomApplyBodyPart() const { return TORSO_INDEX; }
truth character::MustBeRemovedFromBone() const
{ return IsUnique() && !CanBeGenerated(); }
truth character::IsPet() const { return GetTeam()->GetID() == PLAYER_TEAM; }
character* character::GetLeader() const { return GetTeam()->GetLeader(); }
int character::GetMoveType() const
{ return ((!StateIsActivated(LEVITATION)
? DataBase->MoveType
: DataBase->MoveType | FLY) |
(!StateIsActivated(ETHEREAL_MOVING)
? DataBase->MoveType
: DataBase->MoveType | ETHEREAL) |
(!StateIsActivated(SWIMMING)
? DataBase->MoveType
: DataBase->MoveType | WALK|SWIM)); }
festring character::GetZombieDescription() const
{ return " of " + GetName(INDEFINITE); }
truth character::BodyPartCanBeSevered(int I) const { return I; }
truth character::HasBeenSeen() const
{ return DataBase->Flags & HAS_BEEN_SEEN; }
truth character::IsTemporary() const
{ return GetTorso()->GetLifeExpectancy(); }
cchar* character::GetNormalDeathMessage() const
{
const char* killed_by[] = { "murdered @k", "eliminated @k", "slain @k",
"dismembered @k", "sent to the next life @k", "overpowered @k",
"killed @k", "inhumed @k", "dispatched @k", "exterminated @k",
"done in @k", "defeated @k", "struck down @k", "offed @k", "mowed down @k",
"taken down @k", "sent to the grave @k", "destroyed @k", "executed @k",
"slaughtered @k", "annihilated @k", "finished @k", "neutralized @k",
"obliterated @k", "snuffed @k", "done away with @k", "put to death @k" };
return killed_by[RAND() % 27];
}
festring character::GetGhostDescription() const
{ return " of " + GetName(INDEFINITE); }
int characterdatabase::* ExpPtr[ATTRIBUTES] =
{
&characterdatabase::DefaultEndurance,
&characterdatabase::DefaultPerception,
&characterdatabase::DefaultIntelligence,
&characterdatabase::DefaultWisdom,
&characterdatabase::DefaultWillPower,
&characterdatabase::DefaultCharisma,
&characterdatabase::DefaultMana,
&characterdatabase::DefaultArmStrength,
&characterdatabase::DefaultLegStrength,
&characterdatabase::DefaultDexterity,
&characterdatabase::DefaultAgility
};
contentscript<item> characterdatabase::* EquipmentDataPtr[EQUIPMENT_DATAS] =
{
&characterdatabase::Helmet,
&characterdatabase::Amulet,
&characterdatabase::Cloak,
&characterdatabase::BodyArmor,
&characterdatabase::Belt,
&characterdatabase::RightWielded,
&characterdatabase::LeftWielded,
&characterdatabase::RightRing,
&characterdatabase::LeftRing,
&characterdatabase::RightGauntlet,
&characterdatabase::LeftGauntlet,
&characterdatabase::RightBoot,
&characterdatabase::LeftBoot
};
character::character(ccharacter& Char)
: entity(Char), id(Char), NP(Char.NP), AP(Char.AP),
TemporaryState(Char.TemporaryState&~POLYMORPHED),
Team(Char.Team), GoingTo(ERROR_V2),
RandomMoveDir(femath::RandReal(8)),
Money(0), AssignedName(Char.AssignedName), Action(0),
DataBase(Char.DataBase), MotherEntity(0),
PolymorphBackup(0), EquipmentState(0), SquareUnder(0),
AllowedWeaponSkillCategories(Char.AllowedWeaponSkillCategories),
BodyParts(Char.BodyParts),
RegenerationCounter(Char.RegenerationCounter),
SquaresUnder(Char.SquaresUnder), LastAcidMsgMin(0),
Stamina(Char.Stamina), MaxStamina(Char.MaxStamina),
BlocksSinceLastTurn(0), GenerationDanger(Char.GenerationDanger),
CommandFlags(Char.CommandFlags), WarnFlags(0),
ScienceTalks(Char.ScienceTalks), TrapData(0), CounterToMindWormHatch(0)
{
Flags &= ~C_PLAYER;
Flags |= C_INITIALIZING|C_IN_NO_MSG_MODE;
Stack = new stack(0, this, HIDDEN);
int c;
for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
MemorizedEquippedItemIDs[c] = Char.MemorizedEquippedItemIDs[c];
v2HoldPos=Char.v2HoldPos;
for(c = 0; c < STATES; ++c)
TemporaryStateCounter[c] = Char.TemporaryStateCounter[c];
if(Team)
TeamIterator = Team->Add(this);
for(c = 0; c < BASE_ATTRIBUTES; ++c)
BaseExperience[c] = Char.BaseExperience[c];
BodyPartSlot = new bodypartslot[BodyParts];
OriginalBodyPartID = new std::list<ulong>[BodyParts];
CWeaponSkill = new cweaponskill[AllowedWeaponSkillCategories];
SquareUnder = new square*[SquaresUnder];
if(SquaresUnder == 1)
*SquareUnder = 0;
else
memset(SquareUnder, 0, SquaresUnder * sizeof(square*));
for(c = 0; c < BodyParts; ++c)
{
BodyPartSlot[c].SetMaster(this);
bodypart* CharBodyPart = Char.GetBodyPart(c);
OriginalBodyPartID[c] = Char.OriginalBodyPartID[c];
if(CharBodyPart)
{
bodypart* BodyPart = static_cast<bodypart*>(CharBodyPart->Duplicate());
SetBodyPart(c, BodyPart);
BodyPart->CalculateEmitation();
}
}
for(c = 0; c < AllowedWeaponSkillCategories; ++c)
CWeaponSkill[c] = Char.CWeaponSkill[c];
HomeData = Char.HomeData ? new homedata(*Char.HomeData) : 0;
ID = game::CreateNewCharacterID(this);
}
character::character()
: entity(HAS_BE), NP(50000), AP(0), TemporaryState(0), Team(0),
GoingTo(ERROR_V2), RandomMoveDir(femath::RandReal(8)),
Money(0), Action(0), MotherEntity(0), PolymorphBackup(0), EquipmentState(0),
SquareUnder(0), RegenerationCounter(0), HomeData(0), LastAcidMsgMin(0),
BlocksSinceLastTurn(0), GenerationDanger(DEFAULT_GENERATION_DANGER),
WarnFlags(0), ScienceTalks(0), TrapData(0), CounterToMindWormHatch(0)
{
Stack = new stack(0, this, HIDDEN);
int c;
for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
MemorizedEquippedItemIDs[c]=0;
v2HoldPos=v2(0,0);
}
character::~character()
{
if(Action)
delete Action;
if(Team)
Team->Remove(GetTeamIterator());
delete Stack;
int c;
for(c = 0; c < BodyParts; ++c)
delete GetBodyPart(c);
delete [] BodyPartSlot;
delete [] OriginalBodyPartID;
delete PolymorphBackup;
delete [] SquareUnder;
delete [] CWeaponSkill;
delete HomeData;
for(trapdata* T = TrapData; T;)
{
trapdata* ToDel = T;
T = T->Next;
delete ToDel;
}
game::RemoveCharacterID(ID);
}
void character::Hunger()
{
switch(GetBurdenState())
{
case OVER_LOADED:
case STRESSED:
EditNP(-8);
EditExperience(LEG_STRENGTH, 150, 1 << 2);
EditExperience(AGILITY, -50, 1 << 2);
break;
case BURDENED:
EditNP(-2);
EditExperience(LEG_STRENGTH, 75, 1 << 1);
EditExperience(AGILITY, -25, 1 << 1);
break;
case UNBURDENED:
EditNP(-1);
break;
}
switch(GetHungerState())
{
case STARVING:
EditExperience(ARM_STRENGTH, -75, 1 << 3);
EditExperience(LEG_STRENGTH, -75, 1 << 3);
break;
case VERY_HUNGRY:
EditExperience(ARM_STRENGTH, -50, 1 << 2);
EditExperience(LEG_STRENGTH, -50, 1 << 2);
break;
case HUNGRY:
EditExperience(ARM_STRENGTH, -25, 1 << 1);
EditExperience(LEG_STRENGTH, -25, 1 << 1);
break;
case SATIATED:
EditExperience(AGILITY, -25, 1 << 1);
break;
case BLOATED:
EditExperience(AGILITY, -50, 1 << 2);
break;
case OVER_FED:
EditExperience(AGILITY, -75, 1 << 3);
break;
}
CheckStarvationDeath(CONST_S("starved to death"));
}
int character::TakeHit(character* Enemy, item* Weapon,
bodypart* EnemyBodyPart, v2 HitPos,
double Damage, double ToHitValue,
int Success, int Type, int GivenDir,
truth Critical, truth ForceHit)
{
hiteffectSetup* pHitEff=NULL;DBGLN;
bool bShowHitEffect = false;
static bool bHardestMode = false; //TODO make these an user hardcore combat option?
if(!bHardestMode){
//w/o ESP/infravision and if the square is visible even if both fighting are invisible
static bool bPlayerCanHearWhereTheFightIsHappening = true; //TODO this feels like cheating? making things easier? if so, set it to false
static bool bPlayerCanSensePetFighting = true; //TODO this feels like cheating? making things easier? if so, set it to false
if(bPlayerCanHearWhereTheFightIsHappening) //TODO should then also show at non directly visible squares?
if(GetLSquareUnder()->CanBeSeenByPlayer() || Enemy->GetLSquareUnder()->CanBeSeenByPlayer())bShowHitEffect = true;
if(bPlayerCanSensePetFighting && IsPet())bShowHitEffect=true; // override for team awareness
}
if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())bShowHitEffect=true; //throwing hits in the air is valid (seen) if the other one is invisible
if(IsPlayer())bShowHitEffect=true; //override
if(bShowHitEffect){DBGLN;
pHitEff=new hiteffectSetup();
pHitEff->Critical=Critical;
pHitEff->GivenDir=GivenDir;
pHitEff->Type=Type;
pHitEff->WhoHits=Enemy; DBGLN;DBG2(Enemy,"WhoHits"); DBGSV2(Enemy->GetPos()); DBG1(Enemy->GetName(DEFINITE).CStr());
pHitEff->WhoIsHit=this;
pHitEff->lItemEffectReferenceID = Weapon!=NULL ? Weapon->GetID() : EnemyBodyPart->GetID();
}
int Dir = Type == BITE_ATTACK ? YOURSELF : GivenDir;
double DodgeValue = GetDodgeValue();
if(!Enemy->IsPlayer() && GetAttackWisdomLimit() != NO_LIMIT)
Enemy->EditExperience(WISDOM, 75, 1 << 13);
if(!Enemy->CanBeSeenBy(this))
ToHitValue *= 2;
if(!CanBeSeenBy(Enemy))
DodgeValue *= 2;
if(Enemy->StateIsActivated(CONFUSED))
ToHitValue *= 0.75;
switch(Enemy->GetTirednessState())
{
case FAINTING:
ToHitValue *= 0.50;
case EXHAUSTED:
ToHitValue *= 0.75;
}
switch(GetTirednessState())
{
case FAINTING:
DodgeValue *= 0.50;
case EXHAUSTED:
DodgeValue *= 0.75;
}
if(!ForceHit)
{
if(!IsRetreating())
SetGoingTo(Enemy->GetPos());
else
SetGoingTo(GetPos() - ((Enemy->GetPos() - GetPos()) << 4));
if(!Enemy->IsRetreating())
Enemy->SetGoingTo(GetPos());
else
Enemy->SetGoingTo(Enemy->GetPos()
- ((GetPos() - Enemy->GetPos()) << 4));
}
/* Effectively, the average chance to hit is 100% / (DV/THV + 1). */
if(RAND() % int(100 + ToHitValue / DodgeValue * (100 + Success)) < 100
&& !Critical && !ForceHit)
{
Enemy->AddMissMessage(this);
EditExperience(AGILITY, 150, 1 << 7);
EditExperience(PERCEPTION, 75, 1 << 7);
if(Enemy->CanBeSeenByPlayer())
DeActivateVoluntaryAction(CONST_S("The attack of ")
+ Enemy->GetName(DEFINITE)
+ CONST_S(" interrupts you."));
else
DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
// if(hitEff!=NULL)hitEff->End();
return HAS_DODGED;
}
int TrueDamage = int(Damage * (100 + Success) / 100)
+ (RAND() % 3 ? 1 : 0);
if(Critical)
{
TrueDamage += TrueDamage >> 1;
++TrueDamage;
}
int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
if(Critical)
{
switch(Type)
{
case UNARMED_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonCriticalUnarmedHitVerb(),
Enemy->ThirdPersonCriticalUnarmedHitVerb(),
BodyPart);
break;
case WEAPON_ATTACK:
Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, true);
break;
case KICK_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonCriticalKickVerb(),
Enemy->ThirdPersonCriticalKickVerb(),
BodyPart);
break;
case BITE_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonCriticalBiteVerb(),
Enemy->ThirdPersonCriticalBiteVerb(),
BodyPart);
break;
}
}
else
{
switch(Type)
{
case UNARMED_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonUnarmedHitVerb(),
Enemy->ThirdPersonUnarmedHitVerb(),
BodyPart);
break;
case WEAPON_ATTACK:
Enemy->AddWeaponHitMessage(this, Weapon, BodyPart, false);
break;
case KICK_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonKickVerb(),
Enemy->ThirdPersonKickVerb(),
BodyPart);
break;
case BITE_ATTACK:
Enemy->AddPrimitiveHitMessage(this,
Enemy->FirstPersonBiteVerb(),
Enemy->ThirdPersonBiteVerb(),
BodyPart);
break;
}
}
if(!Critical && TrueDamage && Enemy->AttackIsBlockable(Type))
{
TrueDamage = CheckForBlock(Enemy, Weapon, ToHitValue,
TrueDamage, Success, Type);
if(!TrueDamage || (Weapon && !Weapon->Exists()))
{
if(Enemy->CanBeSeenByPlayer())
DeActivateVoluntaryAction(CONST_S("The attack of ")
+ Enemy->GetName(DEFINITE)
+ CONST_S(" interrupts you."));
else
DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
// if(hitEff!=NULL)hitEff->End();
return HAS_BLOCKED;
}
}
int WeaponSkillHits = CalculateWeaponSkillHits(Enemy);
int DoneDamage = ReceiveBodyPartDamage(Enemy, TrueDamage,
PHYSICAL_DAMAGE, BodyPart,
Dir, false, Critical, true,
Type == BITE_ATTACK
&& Enemy->BiteCapturesBodyPart());
truth Succeeded = (GetBodyPart(BodyPart)
&& HitEffect(Enemy, Weapon, HitPos, Type,
BodyPart, Dir, !DoneDamage, Critical, DoneDamage))
|| DoneDamage;
if(Succeeded)
Enemy->WeaponSkillHit(Weapon, Type, WeaponSkillHits);
if(Weapon)
{
if(Weapon->Exists() && DoneDamage < TrueDamage)
Weapon->ReceiveDamage(Enemy, TrueDamage - DoneDamage, PHYSICAL_DAMAGE);
if(Weapon->Exists() && DoneDamage
&& SpillsBlood() && GetBodyPart(BodyPart)
&& (GetBodyPart(BodyPart)->IsAlive()
|| GetBodyPart(BodyPart)->GetMainMaterial()->IsLiquid()))
Weapon->SpillFluid(0, CreateBlood(15 + RAND() % 15));
}
if(Enemy->AttackIsBlockable(Type))
SpecialBodyDefenceEffect(Enemy, EnemyBodyPart, Type);
if(!Succeeded)
{
if(Enemy->CanBeSeenByPlayer())
DeActivateVoluntaryAction(CONST_S("The attack of ")
+ Enemy->GetName(DEFINITE)
+ CONST_S(" interrupts you."));
else
DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
// if(hitEff!=NULL)hitEff->End();
return DID_NO_DAMAGE;
}
if(pHitEff!=NULL){DBGLN;
if(GetLSquareUnder()!=NULL) //may be null if char died TODO right?
GetLSquareUnder()->AddHitEffect(*pHitEff); //after all returns of failure and before any other returns
delete pHitEff; //already possibly copied
}
if(CheckDeath(GetNormalDeathMessage(), Enemy,
Enemy->IsPlayer() ? FORCE_MSG : 0))
return HAS_DIED;
if(Enemy->CanBeSeenByPlayer())
DeActivateVoluntaryAction(CONST_S("The attack of ")
+ Enemy->GetName(DEFINITE)
+ CONST_S(" interrupts you."));
else
DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
return HAS_HIT;
}
struct svpriorityelement
{
svpriorityelement(int BodyPart, int StrengthValue)
: BodyPart(BodyPart), StrengthValue(StrengthValue) { }
bool operator<(const svpriorityelement& AnotherPair) const
{ return StrengthValue > AnotherPair.StrengthValue; }
int BodyPart;
int StrengthValue;
};
int character::ChooseBodyPartToReceiveHit(double ToHitValue,
double DodgeValue)
{
if(BodyParts == 1)
return 0;
std::priority_queue<svpriorityelement> SVQueue;
for(int c = 0; c < BodyParts; ++c)
{
bodypart* BodyPart = GetBodyPart(c);
if(BodyPart && (BodyPart->GetHP() != 1 || BodyPart->CanBeSevered(PHYSICAL_DAMAGE)))
SVQueue.push(svpriorityelement(c, ModifyBodyPartHitPreference(c, BodyPart->GetStrengthValue()
+ BodyPart->GetHP())));
}
while(SVQueue.size())
{
svpriorityelement E = SVQueue.top();
int ToHitPercentage = int(GLOBAL_WEAK_BODYPART_HIT_MODIFIER
* ToHitValue
* GetBodyPart(E.BodyPart)->GetBodyPartVolume()
/ (DodgeValue * GetBodyVolume()));
ToHitPercentage = ModifyBodyPartToHitChance(E.BodyPart, ToHitPercentage);
if(ToHitPercentage < 1)
ToHitPercentage = 1;
else if(ToHitPercentage > 95)
ToHitPercentage = 95;
if(ToHitPercentage > RAND() % 100)
return E.BodyPart;
SVQueue.pop();
}
return 0;
}
#include "audio.h"
void character::Be()
{
if(game::ForceJumpToPlayerBe())
{
if(!IsPlayer())
return;
else
game::SetForceJumpToPlayerBe(false);
}
else
{
truth ForceBe = HP != MaxHP || AllowSpoil();
for(int c = 0; c < BodyParts; ++c)
{
bodypart* BodyPart = GetBodyPart(c);
if(BodyPart && (ForceBe || BodyPart->NeedsBe()))
BodyPart->Be();
}
HandleStates();
if(!IsEnabled())
return;
if(GetTeam() == PLAYER->GetTeam())
{
for(int c = 0; c < AllowedWeaponSkillCategories; ++c)
if(CWeaponSkill[c].Tick() && IsPlayer())
CWeaponSkill[c].AddLevelDownMessage(c);
SWeaponSkillTick();
}
if(IsPlayer())
{
if(GetHungerState() == STARVING && !(RAND() % 50))
LoseConsciousness(250 + RAND_N(250), true);
if(!Action || Action->AllowFoodConsumption())
Hunger();
int MinHPPercent = 128;
for(int c = 0; c < BodyParts; ++c)
{
int tempHpPercent;
bodypart* BodyPart = GetBodyPart(c);
if(BodyPart)
{
tempHpPercent = (BodyPart->GetHP() * audio::MAX_INTENSITY_VOLUME) / BodyPart->GetMaxHP();
if(tempHpPercent < MinHPPercent )
{
MinHPPercent = tempHpPercent;
}
}
}
audio::IntensityLevel( audio::MAX_INTENSITY_VOLUME - MinHPPercent );
}
if(Stamina != MaxStamina)
RegenerateStamina();
if(HP != MaxHP || StateIsActivated(REGENERATION))
Regenerate();
if(Action && AP >= 1000)
ActionAutoTermination();
if(Action && AP >= 1000)
{
if(IsPlayer() && ivanconfig::IsXBRZScale())
game::UpdateSRegionsXBRZ(false); // to speed up the action processing
Action->Handle();
if(!IsEnabled())
return;
}
else
EditAP(GetStateAPGain(100));
}
if(AP >= 1000)
{
SpecialTurnHandler();
BlocksSinceLastTurn = 0;
if(IsPlayer())
{
static int Timer = 0;
if(ivanconfig::GetAutoSaveInterval() && !GetAction()
&& ++Timer >= ivanconfig::GetAutoSaveInterval())
{
game::Save(game::GetAutoSaveFileName());
Timer = 0;
}
game::CalculateNextDanger();
if(!StateIsActivated(POLYMORPHED))
game::UpdatePlayerAttributeAverage();
if(!game::IsInWilderness())
Search(GetAttribute(PERCEPTION));
if(!Action)
{
GetPlayerCommand();
}
else
{
if(Action->ShowEnvironment())
{
static int Counter = 0;
if(++Counter == 10)
{
game::DrawEverything();
Counter = 0;
}
}
msgsystem::ThyMessagesAreNowOld();
if(Action->IsVoluntary() && WAIT_FOR_KEY_DOWN())
{
READ_KEY();
Action->Terminate(false);
}
}
}
else
{
if(!Action && !game::IsInWilderness())
GetAICommand();
}
}
}
void character::Move(v2 MoveTo, truth TeleportMove, truth Run)
{
if(!IsEnabled())
return;
/* Test whether the player is stuck to something */
if(!TeleportMove && !TryToUnStickTraps(MoveTo - GetPos()))
return;
if(Run && !IsPlayer() && TorsoIsAlive()
&& (Stamina <= 10000 / Max(GetAttribute(LEG_STRENGTH), 1)
|| (!StateIsActivated(PANIC) && Stamina < MaxStamina >> 2)))
Run = false;
RemoveTraps();
if(GetBurdenState() != OVER_LOADED || TeleportMove)
{
lsquare* OldSquareUnder[MAX_SQUARES_UNDER];
if(!game::IsInWilderness())
for(int c = 0; c < GetSquaresUnder(); ++c)
OldSquareUnder[c] = GetLSquareUnder(c);
Remove();
PutTo(MoveTo);
if(!TeleportMove)
{
/* Multitiled creatures should behave differently, maybe? */
if(Run)
{
int ED = GetSquareUnder()->GetEntryDifficulty();
EditAP(-GetMoveAPRequirement(ED) >> 1);
EditNP(-24 * ED);
EditExperience(AGILITY, 125, ED << 7);
int Base = 1000;
if(IsPlayer())
switch(GetHungerState())
{
case SATIATED:
Base = 1100;
break;
case BLOATED:
Base = 1250;
break;
case OVER_FED:
Base = 1500;
break;
}
EditStamina(GetAdjustedStaminaCost(-Base, Max(GetAttribute(LEG_STRENGTH), 1)), true);
}
else
{
int ED = GetSquareUnder()->GetEntryDifficulty();
EditAP(-GetMoveAPRequirement(ED));
EditNP(-12 * ED);
EditExperience(AGILITY, 75, ED << 7);
}
}
if(IsPlayer())
ShowNewPosInfo();
if(!game::IsInWilderness())
SignalStepFrom(OldSquareUnder);
}
else
{
if(IsPlayer())
{
cchar* CrawlVerb = StateIsActivated(LEVITATION) ? "float" : "crawl";
ADD_MESSAGE("You try very hard to %s forward. But your load is too heavy.", CrawlVerb);
}
EditAP(-1000);
}
}
void character::GetAICommand()
{
if(!IsPlayerAutoPlay()){
SeekLeader(GetLeader());
if(FollowLeader(GetLeader()))
return;
}
if(CheckForEnemies(true, true, true))
return;
if(CheckForUsefulItemsOnGround())
return;
if(CheckForDoors())
return;
if(CheckSadism())
return;
if(MoveRandomly())
return;
EditAP(-1000);
}
truth character::MoveTowardsTarget(truth Run)
{
v2 Pos = GetPos();
v2 TPos;
if(!Route.empty())
{
TPos = Route.back();
Route.pop_back();
}
else
TPos = GoingTo;
v2 MoveTo[3];
if(TPos.X < Pos.X)
{
if(TPos.Y < Pos.Y)
{
MoveTo[0] = v2(-1, -1);
MoveTo[1] = v2(-1, 0);
MoveTo[2] = v2( 0, -1);
}
if(TPos.Y == Pos.Y)
{
MoveTo[0] = v2(-1, 0);
MoveTo[1] = v2(-1, -1);
MoveTo[2] = v2(-1, 1);
}
if(TPos.Y > Pos.Y)
{
MoveTo[0] = v2(-1, 1);
MoveTo[1] = v2(-1, 0);
MoveTo[2] = v2( 0, 1);
}
}
if(TPos.X == Pos.X)
{
if(TPos.Y < Pos.Y)
{
MoveTo[0] = v2( 0, -1);
MoveTo[1] = v2(-1, -1);
MoveTo[2] = v2( 1, -1);
}
if(TPos.Y == Pos.Y)
{
TerminateGoingTo();
return false;
}
if(TPos.Y > Pos.Y)
{
MoveTo[0] = v2( 0, 1);
MoveTo[1] = v2(-1, 1);
MoveTo[2] = v2( 1, 1);
}
}
if(TPos.X > Pos.X)
{
if(TPos.Y < Pos.Y)
{
MoveTo[0] = v2(1, -1);
MoveTo[1] = v2(1, 0);
MoveTo[2] = v2(0, -1);
}
if(TPos.Y == Pos.Y)
{
MoveTo[0] = v2(1, 0);
MoveTo[1] = v2(1, -1);
MoveTo[2] = v2(1, 1);
}
if(TPos.Y > Pos.Y)
{
MoveTo[0] = v2(1, 1);
MoveTo[1] = v2(1, 0);
MoveTo[2] = v2(0, 1);
}
}
v2 ModifiedMoveTo = ApplyStateModification(MoveTo[0]);
if(TryMove(ModifiedMoveTo, true, Run))
{
RandomMoveDir = femath::RandReal(8);
return true;
}
int L = (Pos - TPos).GetManhattanLength();
if(RAND() & 1)
Swap(MoveTo[1], MoveTo[2]);
if(Pos.IsAdjacent(TPos))
{
TerminateGoingTo();
return false;
}
if((Pos + MoveTo[1] - TPos).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo[1]), true, Run))
return true;
if((Pos + MoveTo[2] - TPos).GetManhattanLength() <= L
&& TryMove(ApplyStateModification(MoveTo[2]), true, Run))
return true;
Illegal.insert(Pos + ModifiedMoveTo);
if(CreateRoute())
return true;
return false;
}
int character::CalculateNewSquaresUnder(lsquare** NewSquare, v2 Pos) const
{
if(GetLevel()->IsValidPos(Pos))
{
*NewSquare = GetNearLSquare(Pos);
return 1;
}
else
return 0;
}
truth character::TryMove(v2 MoveVector, truth Important, truth Run, truth* pbWaitNeutralMove)
{
lsquare* MoveToSquare[MAX_SQUARES_UNDER];
character* Pet[MAX_SQUARES_UNDER];
character* Neutral[MAX_SQUARES_UNDER];
character* Hostile[MAX_SQUARES_UNDER];
v2 PetPos[MAX_SQUARES_UNDER];
v2 NeutralPos[MAX_SQUARES_UNDER];
v2 HostilePos[MAX_SQUARES_UNDER];
v2 MoveTo = GetPos() + MoveVector;
int Direction = game::GetDirectionForVector(MoveVector);
if(Direction == DIR_ERROR)
ABORT("Direction fault.");
if(!game::IsInWilderness())
{
int Squares = CalculateNewSquaresUnder(MoveToSquare, MoveTo);
if(Squares)
{
int Pets = 0;
int Neutrals = 0;
int Hostiles = 0;
for(int c = 0; c < Squares; ++c)
{
character* Char = MoveToSquare[c]->GetCharacter();
if(Char && Char != this)
{
v2 Pos = MoveToSquare[c]->GetPos();
if(IsAlly(Char))
{
Pet[Pets] = Char;
PetPos[Pets++] = Pos;
}
else if(Char->GetRelation(this) != HOSTILE)
{
Neutral[Neutrals] = Char;
NeutralPos[Neutrals++] = Pos;
}
else
{
Hostile[Hostiles] = Char;
HostilePos[Hostiles++] = Pos;
}
}
}
if(Hostiles == 1)
return Hit(Hostile[0], HostilePos[0], Direction);
else if(Hostiles)
{
int Index = RAND() % Hostiles;
return Hit(Hostile[Index], HostilePos[Index], Direction);
}
if(Neutrals>0 && ivanconfig::IsWaitNeutralsMoveAway() && pbWaitNeutralMove!=NULL){
(*pbWaitNeutralMove)=true;
return false;
}else{
if(Neutrals == 1)
{
if(!IsPlayer() && !Pets && Important && CanMoveOn(MoveToSquare[0]))
return HandleCharacterBlockingTheWay(Neutral[0], NeutralPos[0], Direction);
else
return IsPlayer() && Hit(Neutral[0], NeutralPos[0], Direction);
}
else if(Neutrals)
{
if(IsPlayer())
{
int Index = RAND() % Neutrals;
return Hit(Neutral[Index], NeutralPos[Index], Direction);
}
else
return false;
}
}
if(!IsPlayer())
for(int c = 0; c < Squares; ++c)
if(MoveToSquare[c]->IsScary(this))
return false;
if(Pets == 1)
{
if(IsPlayer() && !ivanconfig::GetBeNice()
&& Pet[0]->IsMasochist() && HasSadistAttackMode()
&& game::TruthQuestion("Do you want to punish " + Pet[0]->GetObjectPronoun() + "? [y/N]"))
return Hit(Pet[0], PetPos[0], Direction, SADIST_HIT);
else
return (Important
&& (CanMoveOn(MoveToSquare[0])
|| (IsPlayer()
&& game::GoThroughWallsCheatIsActive()))
&& Displace(Pet[0]));
}
else if(Pets)
return false;
if((CanMove() && CanMoveOn(MoveToSquare[0]))
|| (game::GoThroughWallsCheatIsActive() && IsPlayer()))
{
Move(MoveTo, false, Run);
if(IsEnabled() && GetPos() == GoingTo)
TerminateGoingTo();
return true;
}
else
for(int c = 0; c < Squares; ++c)
{
olterrain* Terrain = MoveToSquare[c]->GetOLTerrain();
if(Terrain && Terrain->CanBeOpened())
{
if(CanOpen())
{
if(Terrain->IsLocked())
{
if(IsPlayer())
{
/* not sure if this is better than "the door is locked", but I guess it _might_ be slightly better */
ADD_MESSAGE("The %s is locked.", Terrain->GetNameSingular().CStr());
if(!IsPlayerAutoPlay())return false;
}
if(Important && CheckKick())
{
room* Room = MoveToSquare[c]->GetRoom();
if(!Room || Room->AllowKick(this, MoveToSquare[c]))
{
int HP = Terrain->GetHP();
if(CanBeSeenByPlayer())
ADD_MESSAGE("%s kicks %s.", CHAR_NAME(DEFINITE), Terrain->CHAR_NAME(DEFINITE));
Kick(MoveToSquare[c], Direction);
olterrain* NewTerrain = MoveToSquare[c]->GetOLTerrain();
if(NewTerrain == Terrain && Terrain->GetHP() == HP) // BUG!
{
Illegal.insert(MoveTo);
CreateRoute();
}
return true;
}
}
}
else
return MoveToSquare[c]->Open(this);
}
else
{
if(IsPlayer())
{
ADD_MESSAGE("This monster type cannot open doors.");
return false;
}
else if(Important)
{
Illegal.insert(MoveTo);
return CreateRoute();
}
}
}
}
return false;
}
else
{
if(IsPlayer() && !IsStuck() && GetLevel()->IsOnGround()
&& game::TruthQuestion(CONST_S("Do you want to leave ")
+ game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex())
+ "? [y/N]"))
{
if(HasPetrussNut() && !HasGoldenEagleShirt())
{
game::PlayVictoryMusic();
game::TextScreen(CONST_S("An undead and sinister voice greets you as you leave the city behind:\n\n"
"\"MoRtAl! ThOu HaSt SlAuGhTeReD pEtRuS aNd PlEaSeD mE!\nfRoM tHiS dAy On, "
"ThOu ArT tHe DeArEsT sErVaNt Of AlL eViL!\"\n\nYou are victorious!"));
game::GetCurrentArea()->SendNewDrawRequest();
game::DrawEverything();
ShowAdventureInfo();
festring Msg = CONST_S("killed Petrus and became the Avatar of Chaos");
PLAYER->AddScoreEntry(Msg, 3, false);
game::End(Msg);
return true;
}
if(game::TryTravel(WORLD_MAP, WORLD_MAP, game::GetCurrentDungeonIndex()))
return true;
}
return false;
}
}
else
{
/** No multitile support */
if(CanMove()
&& GetArea()->IsValidPos(MoveTo)
&& (CanMoveOn(GetNearWSquare(MoveTo))
|| game::GoThroughWallsCheatIsActive()))
{
if(!game::GoThroughWallsCheatIsActive())
{
charactervector& V = game::GetWorldMap()->GetPlayerGroup();
truth Discard = false;
for(uint c = 0; c < V.size(); ++c)
if(!V[c]->CanMoveOn(GetNearWSquare(MoveTo)))
{
if(!Discard)
{
ADD_MESSAGE("One or more of your team members cannot cross this terrain.");
if(!game::TruthQuestion("Discard them? [y/N]"))
return false;
Discard = true;
}
if(Discard)
delete V[c];
V.erase(V.begin() + c--);
}
}
Move(MoveTo, false);
return true;
}
else
return false;
}
}
void character::CreateCorpse(lsquare* Square)
{
if(!BodyPartsDisappearWhenSevered() && !game::AllBodyPartsVanish())
{
corpse* Corpse = corpse::Spawn(0, NO_MATERIALS);
Corpse->SetDeceased(this);
Square->AddItem(Corpse);
Disable();
}
else
SendToHell();
}
bool bSafePrayOnce=false;
void character::AutoPlayAITeleport(bool bDeathCountBased)
{
bool bTeleportNow=false;
if(bDeathCountBased){ // this is good to prevent autoplay AI getting stuck endless dieing
static int iDieMax=10;
static int iDieTeleportCountDown=iDieMax;
if(iDieTeleportCountDown==0){ //this helps on defeating not so strong enemies in spot
if(IsPlayerAutoPlay())
bTeleportNow=true;
iDieTeleportCountDown=iDieMax;
bSafePrayOnce=true;
}else{
static v2 v2DiePos(0,0);
if(v2DiePos==GetPos()){
iDieTeleportCountDown--;
}else{
v2DiePos=GetPos();
iDieTeleportCountDown=iDieMax;
}
}
}
if(bTeleportNow)
Move(GetLevel()->GetRandomSquare(this), true); //not using teleport function to avoid prompts, but this code is from there TODO and should be in sync! create TeleportRandomDirectly() ?
}
void character::Die(ccharacter* Killer, cfestring& Msg, ulong DeathFlags)
{
/* Note: This function musn't delete any objects, since one of these may be
the one currently processed by pool::Be()! */
if(!IsEnabled())
return;
RemoveTraps();
if(IsPlayer())
{
ADD_MESSAGE("You die.");
if(game::WizardModeIsActive())
{
game::DrawEverything();
bool bInstaResurrect=false;
if(!bInstaResurrect && IsPlayerAutoPlay())bInstaResurrect=true;
if(!bInstaResurrect && !game::TruthQuestion(CONST_S("Do you want to do this, cheater? [y/n]"), REQUIRES_ANSWER))bInstaResurrect=true;
if(bInstaResurrect)
{
RestoreBodyParts();
ResetSpoiling();
if(IsBurning())
{
doforbodypartswithparam<truth>()(this, &bodypart::Extinguish, false);
doforbodyparts()(this, &bodypart::ResetThermalEnergies);
doforbodyparts()(this, &bodypart::ResetBurning);
}
RestoreHP();
RestoreStamina();
ResetStates();
SetNP(SATIATED_LEVEL);
SendNewDrawRequest();
if(IsPlayerAutoPlay())AutoPlayAITeleport(true);
return;
}
}
}
else if(CanBeSeenByPlayer() && !(DeathFlags & DISALLOW_MSG))
ProcessAndAddMessage(GetDeathMessage());
else if(DeathFlags & FORCE_MSG)
ADD_MESSAGE("You sense the death of something.");
if(IsBurning()) // do this anyway, it stops the corpse from emitating and continuing to propagate weirdness
{
doforbodypartswithparam<truth>()(this, &bodypart::Extinguish, false);
doforbodyparts()(this, &bodypart::ResetThermalEnergies);
doforbodyparts()(this, &bodypart::ResetBurning);
}
if(!(DeathFlags & FORBID_REINCARNATION))
{
if(StateIsActivated(LIFE_SAVED)
&& CanMoveOn(!game::IsInWilderness() ? GetSquareUnder() : PLAYER->GetSquareUnder()))
{
SaveLife();
return;
}
if(SpecialSaveLife())
return;
}
else if(StateIsActivated(LIFE_SAVED))
RemoveLifeSavers();
Flags |= C_IN_NO_MSG_MODE;
bonesghost* Ghost = 0;
if(IsPlayer())
{
game::RemoveSaves();
if(!game::IsInWilderness())
{
Ghost = game::CreateGhost();
Ghost->Disable();
}
}
square* SquareUnder[MAX_SQUARES_UNDER];
lsquare** LSquareUnder = reinterpret_cast<lsquare**>(SquareUnder);
memset(SquareUnder, 0, sizeof(SquareUnder));
Disable();
if(IsPlayer() || !game::IsInWilderness())
{
for(int c = 0; c < SquaresUnder; ++c)
SquareUnder[c] = GetSquareUnder(c);
Remove();
}
else
{
charactervector& V = game::GetWorldMap()->GetPlayerGroup();
V.erase(std::find(V.begin(), V.end(), this));
}
if(!game::IsInWilderness())
{
if(!StateIsActivated(POLYMORPHED))
{
if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
game::SignalDeath(this, Killer, Msg);
if(!(DeathFlags & DISALLOW_CORPSE))
CreateCorpse(LSquareUnder[0]);
else
SendToHell();
}
else
{
if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
game::SignalDeath(GetPolymorphBackup(), Killer, Msg);
GetPolymorphBackup()->CreateCorpse(LSquareUnder[0]);
GetPolymorphBackup()->Flags &= ~C_POLYMORPHED;
SetPolymorphBackup(0);
SendToHell();
}
}
else
{
if(!IsPlayer() && !IsTemporary() && !Msg.IsEmpty())
game::SignalDeath(this, Killer, Msg);
SendToHell();
}
if(IsPlayer())
{
if(!game::IsInWilderness())
for(int c = 0; c < GetSquaresUnder(); ++c)
LSquareUnder[c]->SetTemporaryEmitation(GetEmitation());
game::SRegionAroundDisable();
game::PlayDefeatMusic();
ShowAdventureInfo();
if(!game::IsInWilderness())
for(int c = 0; c < GetSquaresUnder(); ++c)
LSquareUnder[c]->SetTemporaryEmitation(0);
}
if(!game::IsInWilderness())
{
if(GetSquaresUnder() == 1)
{
stack* StackUnder = LSquareUnder[0]->GetStack();
GetStack()->MoveItemsTo(StackUnder);
doforbodypartswithparam<stack*>()(this, &bodypart::DropEquipment, StackUnder);
}
else
{
while(GetStack()->GetItems())
GetStack()->GetBottom()->MoveTo(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
for(int c = 0; c < BodyParts; ++c)
{
bodypart* BodyPart = GetBodyPart(c);
if(BodyPart)
BodyPart->DropEquipment(LSquareUnder[RAND_N(GetSquaresUnder())]->GetStack());
}
}
}
if(GetTeam()->GetLeader() == this)
GetTeam()->SetLeader(0);
Flags &= ~C_IN_NO_MSG_MODE;
if(IsPlayer())
{
if(game::GetXinrochTombStoryState() == 2)
{
festring MsgBut = CONST_S("delivered the Shadow Veil to the Necromancer and continued to further adventures, but was ");
festring NewMsg = MsgBut << Msg;
AddScoreEntry(NewMsg, 2, true);
}
else
AddScoreEntry(Msg);
if(!game::IsInWilderness())
{
Ghost->PutTo(LSquareUnder[0]->GetPos());
Ghost->Enable();
game::CreateBone();
}
game::TextScreen(CONST_S("Unfortunately you died."), ZERO_V2, WHITE, true, true, &game::ShowDeathSmiley);
game::End(Msg);
}
}
void character::AddMissMessage(ccharacter* Enemy) const
{
festring Msg;
if(Enemy->IsPlayer())
Msg = GetDescription(DEFINITE) + " misses you!";
else if(IsPlayer())
Msg = CONST_S("You miss ") + Enemy->GetDescription(DEFINITE) + '!';
else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
Msg = GetDescription(DEFINITE) + " misses " + Enemy->GetDescription(DEFINITE) + '!';
else
return;
ADD_MESSAGE("%s", Msg.CStr());
}
void character::AddBlockMessage(ccharacter* Enemy, citem* Blocker, cfestring& HitNoun, truth Partial) const
{
festring Msg;
festring BlockVerb = (Partial ? " to partially block the " : " to block the ") + HitNoun;
if(IsPlayer())
Msg << "You manage" << BlockVerb << " with your " << Blocker->GetName(UNARTICLED) << '!';
else if(Enemy->IsPlayer() || Enemy->CanBeSeenByPlayer())
{
if(CanBeSeenByPlayer())
Msg << GetName(DEFINITE) << " manages" << BlockVerb << " with "
<< GetPossessivePronoun() << ' ' << Blocker->GetName(UNARTICLED) << '!';
else
Msg << "Something manages" << BlockVerb << " with something!";
}
else
return;
ADD_MESSAGE("%s", Msg.CStr());
}
void character::AddPrimitiveHitMessage(ccharacter* Enemy, cfestring& FirstPersonHitVerb,
cfestring& ThirdPersonHitVerb, int BodyPart) const
{
festring Msg;
festring BodyPartDescription;
if(BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
if(Enemy->IsPlayer())
Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << " you" << BodyPartDescription << '!';
else if(IsPlayer())
Msg << "You " << FirstPersonHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
Msg << GetDescription(DEFINITE) << ' ' << ThirdPersonHitVerb << ' '
<< Enemy->GetDescription(DEFINITE) + BodyPartDescription << '!';
else
return;
ADD_MESSAGE("%s", Msg.CStr());
}
cchar*const HitVerb[] = { "strike", "slash", "stab" };
cchar*const HitVerb3rdPersonEnd[] = { "s", "es", "s" };
void character::AddWeaponHitMessage(ccharacter* Enemy, citem* Weapon, int BodyPart, truth Critical) const
{
festring Msg;
festring BodyPartDescription;
if(BodyPart && (Enemy->CanBeSeenByPlayer() || Enemy->IsPlayer()))
BodyPartDescription << " in the " << Enemy->GetBodyPartName(BodyPart);
int FittingTypes = 0;
int DamageFlags = Weapon->GetDamageFlags();
int DamageType = 0;
for(int c = 0; c < DAMAGE_TYPES; ++c)
if(1 << c & DamageFlags)
{
if(!FittingTypes || !RAND_N(FittingTypes + 1))
DamageType = c;
++FittingTypes;
}
if(!FittingTypes)
ABORT("No damage flags specified for %s!", Weapon->CHAR_NAME(UNARTICLED));
festring NewHitVerb = Critical ? " critically " : " ";
NewHitVerb << HitVerb[DamageType];
cchar*const E = HitVerb3rdPersonEnd[DamageType];
if(Enemy->IsPlayer())
{
Msg << GetDescription(DEFINITE) << NewHitVerb << E << " you" << BodyPartDescription;
if(CanBeSeenByPlayer())
Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
Msg << '!';
}
else if(IsPlayer())
Msg << "You" << NewHitVerb << ' ' << Enemy->GetDescription(DEFINITE) << BodyPartDescription << '!';
else if(CanBeSeenByPlayer() || Enemy->CanBeSeenByPlayer())
{
Msg << GetDescription(DEFINITE) << NewHitVerb << E << ' '
<< Enemy->GetDescription(DEFINITE) << BodyPartDescription;
if(CanBeSeenByPlayer())
Msg << " with " << GetPossessivePronoun() << ' ' << Weapon->GetName(UNARTICLED);
Msg << '!';
}
else
return;
ADD_MESSAGE("%s", Msg.CStr());
}
truth character::HasHeadOfElpuri() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsHeadOfElpuri())
return true;
return combineequipmentpredicates()(this, &item::IsHeadOfElpuri, 1);
}
truth character::HasPetrussNut() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsPetrussNut())
return true;
return combineequipmentpredicates()(this, &item::IsPetrussNut, 1);
}
truth character::HasGoldenEagleShirt() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsGoldenEagleShirt())
return true;
return combineequipmentpredicates()(this, &item::IsGoldenEagleShirt, 1);
}
truth character::HasEncryptedScroll() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsEncryptedScroll())
return true;
return combineequipmentpredicates()(this, &item::IsEncryptedScroll, 1);
}
truth character::HasShadowVeil() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsShadowVeil())
return true;
return combineequipmentpredicates()(this, &item::IsShadowVeil, 1);
}
truth character::HasLostRubyFlamingSword() const
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsLostRubyFlamingSword())
return true;
return combineequipmentpredicates()(this, &item::IsLostRubyFlamingSword, 1);
}
truth character::RemoveEncryptedScroll()
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsEncryptedScroll())
{
item* Item = *i;
Item->RemoveFromSlot();
Item->SendToHell();
return true;
}
for(int c = 0; c < GetEquipments(); ++c)
{
item* Item = GetEquipment(c);
if(Item && Item->IsEncryptedScroll())
{
Item->RemoveFromSlot();
Item->SendToHell();
return true;
}
}
return false;
}
truth character::RemoveShadowVeil()
{
for(stackiterator i = GetStack()->GetBottom(); i.HasItem(); ++i)
if(i->IsShadowVeil())
{
item* Item = *i;
Item->RemoveFromSlot();
Item->SendToHell();
return true;
}
for(int c = 0; c < GetEquipments(); ++c)
{
item* Item = GetEquipment(c);
if(Item && Item->IsShadowVeil())
{
Item->RemoveFromSlot();
Item->SendToHell();
return true;
}
}
return false;
}
truth character::ReadItem(item* ToBeRead)
{
if(ToBeRead->CanBeRead(this))
{
int LocalReadDifficulty = ToBeRead->GetBurnLevel() * sqrt(abs(ToBeRead->GetReadDifficulty())) / 2;
if(GetAttribute(INTELLIGENCE) < LocalReadDifficulty)
{
ADD_MESSAGE("%s is completely unreadable.", ToBeRead->CHAR_NAME(DEFINITE));
return false;
}
if(!GetLSquareUnder()->IsDark() || game::GetSeeWholeMapCheatMode())
{
if(StateIsActivated(CONFUSED) && !(RAND() & 7))
{
if(!ToBeRead->IsDestroyable(this))
ADD_MESSAGE("You read some words of %s and understand exactly nothing.", ToBeRead->CHAR_NAME(DEFINITE));
else
{
ADD_MESSAGE("%s is very confusing. Or perhaps you are just too confused?", ToBeRead->CHAR_NAME(DEFINITE));
ActivateRandomState(SRC_CONFUSE_READ, 1000 + RAND() % 1500);
ToBeRead->RemoveFromSlot();
ToBeRead->SendToHell();
}
EditAP(-1000);
return true;
}
if(ToBeRead->Read(this))
{
if(!game::WizardModeIsActive())
{
/* This AP is used to take the stuff out of backpack */
DexterityAction(5);
}
return true;
}
else
return false;
}
else
{
if(IsPlayer())
ADD_MESSAGE("It's too dark here to read.");
return false;
}
}
else
{
if(IsPlayer())
ADD_MESSAGE("You can't read this.");
return false;
}
}
void character::CalculateBurdenState()
{
int OldBurdenState = BurdenState;
long SumOfMasses = GetCarriedWeight();
long CarryingStrengthUnits = long(GetCarryingStrength()) * 2500;
if(SumOfMasses > (CarryingStrengthUnits << 1) + CarryingStrengthUnits)
BurdenState = OVER_LOADED;
else if(SumOfMasses > CarryingStrengthUnits << 1)
BurdenState = STRESSED;
else if(SumOfMasses > CarryingStrengthUnits)
BurdenState = BURDENED;
else
BurdenState = UNBURDENED;
if(!IsInitializing() && BurdenState != OldBurdenState)
CalculateBattleInfo();
}
void character::Save(outputfile& SaveFile) const
{
SaveFile << static_cast<ushort>(GetType());
Stack->Save(SaveFile);
SaveFile << ID;
int c;
for(c = 0; c < BASE_ATTRIBUTES; ++c)
SaveFile << BaseExperience[c];
SaveFile << ExpModifierMap;
SaveFile << NP << AP << Stamina << GenerationDanger << ScienceTalks
<< CounterToMindWormHatch;
SaveFile << TemporaryState << EquipmentState << Money << MyVomitMaterial << GoingTo << RegenerationCounter << Route << Illegal;
SaveFile.Put(!!IsEnabled());
SaveFile << HomeData << BlocksSinceLastTurn << CommandFlags;
SaveFile << WarnFlags << static_cast<ushort>(Flags);
for(c = 0; c < BodyParts; ++c)
SaveFile << BodyPartSlot[c] << OriginalBodyPartID[c];
SaveLinkedList(SaveFile, TrapData);
SaveFile << Action; DBG1(Action);
for(c = 0; c < STATES; ++c)
SaveFile << TemporaryStateCounter[c];
if(GetTeam())
{
SaveFile.Put(true);
SaveFile << Team->GetID();
}
else
SaveFile.Put(false);
if(GetTeam() && GetTeam()->GetLeader() == this)
SaveFile.Put(true);
else
SaveFile.Put(false);
SaveFile << AssignedName << PolymorphBackup;
for(c = 0; c < AllowedWeaponSkillCategories; ++c)
SaveFile << CWeaponSkill[c];
SaveFile << static_cast<ushort>(GetConfig());
for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
SaveFile << MemorizedEquippedItemIDs[c];
}
void character::Load(inputfile& SaveFile)
{
LoadSquaresUnder();
int c;
Stack->Load(SaveFile);
SaveFile >> ID;
game::AddCharacterID(this, ID);
for(c = 0; c < BASE_ATTRIBUTES; ++c)
SaveFile >> BaseExperience[c];
SaveFile >> ExpModifierMap;
SaveFile >> NP >> AP >> Stamina >> GenerationDanger >> ScienceTalks
>> CounterToMindWormHatch;
SaveFile >> TemporaryState >> EquipmentState >> Money >> MyVomitMaterial >> GoingTo >> RegenerationCounter >> Route >> Illegal;
if(!SaveFile.Get())
Disable();
SaveFile >> HomeData >> BlocksSinceLastTurn >> CommandFlags;
SaveFile >> WarnFlags;
WarnFlags &= ~WARNED;
Flags |= ReadType<ushort>(SaveFile) & ~ENTITY_FLAGS;
for(c = 0; c < BodyParts; ++c)
{
SaveFile >> BodyPartSlot[c] >> OriginalBodyPartID[c];
item* BodyPart = *BodyPartSlot[c];
if(BodyPart)
BodyPart->Disable();
}
LoadLinkedList(SaveFile, TrapData);
SaveFile >> Action;
if(Action)
Action->SetActor(this);
for(c = 0; c < STATES; ++c)
SaveFile >> TemporaryStateCounter[c];
if(SaveFile.Get())
SetTeam(game::GetTeam(ReadType<ulong>(SaveFile)));
if(SaveFile.Get())
GetTeam()->SetLeader(this);
SaveFile >> AssignedName >> PolymorphBackup;
for(c = 0; c < AllowedWeaponSkillCategories; ++c)
SaveFile >> CWeaponSkill[c];
databasecreator<character>::InstallDataBase(this, ReadType<ushort>(SaveFile));
if(game::GetCurrentSavefileVersion()>=132){
for(c = 0; c < MAX_EQUIPMENT_SLOTS; c++)
SaveFile >> MemorizedEquippedItemIDs[c];
}
/////////////// loading ended /////////////////////////
if(IsEnabled() && !game::IsInWilderness())
for(c = 1; c < GetSquaresUnder(); ++c)
GetSquareUnder(c)->SetCharacter(this);
}
truth character::Engrave(cfestring& What)
{
GetLSquareUnder()->Engrave(What);
return true;
}
truth character::MoveRandomly()
{
if(!IsEnabled())
return false;
double DirChange = femath::NormalDistributedRand(0.03);
RandomMoveDir += DirChange;
WrapFRef(RandomMoveDir, 0., 8.);
int Unit = Sign(DirChange);
if(!Unit) Unit = RAND_2 ? 1 : -1;
int Aim;
if(femath::RandReal(1) < RandomMoveDir - floor(RandomMoveDir))
Aim = floor(RandomMoveDir);
else
Aim = Wrap(static_cast<int>(ceil(RandomMoveDir)), 0, 8);
for(int Delta = 0; abs(Delta) <= 4;)
{
int Dir = Wrap(Aim + Delta, 0, 8);
v2 ToTry = game::GetClockwiseMoveVector(Dir);
if(GetLevel()->IsValidPos(GetPos() + ToTry))
{
lsquare* Square = GetNearLSquare(GetPos() + ToTry);
if(!Square->IsDangerous(this)
&& !Square->IsScary(this)
&& TryMove(ToTry, false, false))
{
if(Delta)
RandomMoveDir = Dir;
return true;
}
}
Delta = -Delta;
if(!Sign(Delta) || Sign(Delta) == Unit)
Delta += Unit;
}
RandomMoveDir = femath::RandReal(8);
return false;
}
truth character::TestForPickup(item* ToBeTested) const
{
if(MakesBurdened(ToBeTested->GetWeight() + GetCarriedWeight()))
return false;
return true;
}
void character::AddScoreEntry(cfestring& Description, double Multiplier, truth AddEndLevel) const
{
if(!game::WizardModeIsReallyActive())
{
highscore HScore(game::GetUserDataDir() + HIGH_SCORE_FILENAME);
if(!HScore.CheckVersion())
{
if(game::Menu(0, v2(RES.X >> 1, RES.Y >> 1),
CONST_S("The highscore version doesn't match.\rDo you want to erase "
"previous records and start a new file?\rNote, if you answer "
"no, the score of your current game will be lost!\r"),
CONST_S("Yes\rNo\r"), LIGHT_GRAY))
return;
HScore.Clear();
}
festring Desc = game::GetPlayerName();
Desc << ", " << Description;
if(AddEndLevel)
{
if(game::IsInWilderness())
Desc << " in the world map";
else
Desc << " in " << game::GetCurrentDungeon()->GetLevelDescription(game::GetCurrentLevelIndex());
}
HScore.Add(long(game::GetScore() * Multiplier), Desc);
HScore.Save();
}
}
truth character::CheckDeath(cfestring& Msg, ccharacter* Murderer, ulong DeathFlags)
{
if(!IsEnabled())
return true;
if(game::IsSumoWrestling() && IsDead())
{
game::EndSumoWrestling(!!IsPlayer());
return true;
}
if(DeathFlags & FORCE_DEATH || IsDead())
{
if(Murderer && Murderer->IsPlayer() && GetTeam()->GetKillEvilness())
game::DoEvilDeed(GetTeam()->GetKillEvilness());
festring SpecifierMsg;
int SpecifierParts = 0;
if(GetPolymorphBackup())
{
SpecifierMsg << " polymorphed into ";
id::AddName(SpecifierMsg, INDEFINITE);
++SpecifierParts;
}
if(!(DeathFlags & IGNORE_TRAPS) && IsStuck())
{
if(SpecifierParts++)
SpecifierMsg << " and";
SpecifierMsg << " caught in " << GetTrapDescription();
}
if(GetAction()
&& !(DeathFlags & IGNORE_UNCONSCIOUSNESS
&& GetAction()->IsUnconsciousness()))
{
festring ActionMsg = GetAction()->GetDeathExplanation();
if(!ActionMsg.IsEmpty())
{
if(SpecifierParts > 1)
SpecifierMsg = ActionMsg << ',' << SpecifierMsg;
else
{
if(SpecifierParts)
SpecifierMsg << " and";
SpecifierMsg << ActionMsg;
}
++SpecifierParts;
}
}
festring NewMsg = Msg;
if(Murderer == this)
{
SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + GetPossessivePronoun(false) + " own");
SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + GetObjectPronoun(false) + "self");
SEARCH_N_REPLACE(NewMsg, "@k", GetObjectPronoun(false) + "self");
}
else
{
SEARCH_N_REPLACE(NewMsg, "@bkp", CONST_S("by ") + Murderer->GetName(INDEFINITE) + "'s");
SEARCH_N_REPLACE(NewMsg, "@bk", CONST_S("by ") + Murderer->GetName(INDEFINITE));
SEARCH_N_REPLACE(NewMsg, "@k", CONST_S("by ") + Murderer->GetName(INDEFINITE));
}
if(SpecifierParts)
NewMsg << " while" << SpecifierMsg;
if(IsPlayer() && game::WizardModeIsActive())
ADD_MESSAGE("Death message: %s. Score: %ld.", NewMsg.CStr(), game::GetScore());
Die(Murderer, NewMsg, DeathFlags);
return true;
}
else
return false;
}
truth character::CheckStarvationDeath(cfestring& Msg)
{
if(GetNP() < 1 && UsesNutrition() && !(StateIsActivated(FASTING)))
return CheckDeath(Msg, 0, FORCE_DEATH);
else
return false;
}
void character::ThrowItem(int Direction, item* ToBeThrown)
{
if(Direction > 7)
ABORT("Throw in TOO odd direction...");
ToBeThrown->Fly(this, Direction, GetAttribute(ARM_STRENGTH),
ToBeThrown->IsWeapon(this) && !ToBeThrown->IsBroken());
}
void character::HasBeenHitByItem(character* Thrower, item* Thingy, int Damage, double ToHitValue, int Direction)
{
if(IsPlayer())
ADD_MESSAGE("%s hits you.", Thingy->CHAR_NAME(DEFINITE));
else if(CanBeSeenByPlayer())
ADD_MESSAGE("%s hits %s.", Thingy->CHAR_NAME(DEFINITE), CHAR_NAME(DEFINITE));
int BodyPart = ChooseBodyPartToReceiveHit(ToHitValue, DodgeValue);
int WeaponSkillHits = Thrower ? CalculateWeaponSkillHits(Thrower) : 0;
int DoneDamage = ReceiveBodyPartDamage(Thrower, Damage, PHYSICAL_DAMAGE, BodyPart, Direction);
truth Succeeded = (GetBodyPart(BodyPart) && HitEffect(Thrower, Thingy, Thingy->GetPos(), THROW_ATTACK,
BodyPart, Direction, !DoneDamage, false, DoneDamage)) || DoneDamage;
if(Succeeded && Thrower)
Thrower->WeaponSkillHit(Thingy, THROW_ATTACK, WeaponSkillHits);
festring DeathMsg = CONST_S("killed by a flying ") + Thingy->GetName(UNARTICLED);
if(CheckDeath(DeathMsg, Thrower))
return;
if(Thrower)
{
if(Thrower->CanBeSeenByPlayer())
DeActivateVoluntaryAction(CONST_S("The attack of ") + Thrower->GetName(DEFINITE) + CONST_S(" interrupts you."));
else
DeActivateVoluntaryAction(CONST_S("The attack interrupts you."));
}
else
DeActivateVoluntaryAction(CONST_S("The hit interrupts you."));
}
truth character::DodgesFlyingItem(item* Item, double ToHitValue)
{
return !Item->EffectIsGood() && RAND() % int(100 + ToHitValue / DodgeValue * 100) < 100;
}
character* AutoPlayLastChar=NULL;
const int iMaxWanderTurns=20;
const int iMinWanderTurns=3;
/**
* 5 seems good, broken cheap weapons, stones, very cheap weapons non broken etc
* btw, lantern price is currently 10.
*/
static int iMaxValueless = 5;
v2 v2KeepGoingTo=v2(0,0);
v2 v2TravelingToAnotherDungeon=v2(0,0);
int iWanderTurns=iMinWanderTurns;
bool bAutoPlayUseRandomNavTargetOnce=false;
std::vector<v2> vv2DebugDrawSqrPrevious;
v2 v2LastDropPlayerWasAt=v2(0,0);
std::vector<v2> vv2FailTravelToTargets;
std::vector<v2> vv2WrongGoingTo;
void character::AutoPlayAIReset(bool bFailedToo)
{ DBG7(bFailedToo,iWanderTurns,DBGAV2(v2KeepGoingTo),DBGAV2(v2TravelingToAnotherDungeon),DBGAV2(v2LastDropPlayerWasAt),vv2FailTravelToTargets.size(),vv2DebugDrawSqrPrevious.size());
v2KeepGoingTo=v2(0,0); //will retry
v2TravelingToAnotherDungeon=v2(0,0);
iWanderTurns=0; // warning: this other code was messing the logic ---> if(iWanderTurns<iMinWanderTurns)iWanderTurns=iMinWanderTurns; //to wander just a bit looking for random spot from where Route may work
bAutoPlayUseRandomNavTargetOnce=false;
v2LastDropPlayerWasAt=v2(0,0);
vv2DebugDrawSqrPrevious.clear();
PLAYER->TerminateGoingTo();
if(bFailedToo){
vv2FailTravelToTargets.clear();
vv2WrongGoingTo.clear();
}
}
truth character::AutoPlayAISetAndValidateKeepGoingTo(v2 v2KGTo)
{
v2KeepGoingTo=v2KGTo;
bool bOk=true;
if(bOk){
lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo);
if(!CanTheoreticallyMoveOn(lsqr))
bOk=false;
// olterrain* olt = game::GetCurrentLevel()->GetLSquare(v2KeepGoingTo)->GetOLTerrain();
// if(olt){
// if(bOk && !CanMoveOn(olt)){
// DBG4(DBGAV2(v2KeepGoingTo),"olterrain? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr());
// bOk=false;
// }
//
// /****
// * keep these commented for awhile, may be useful later
// *
// if(bOk && olt->IsWall()){ //TODO this may be unnecessary cuz of above test
// //TODO is this a bug in the CanMoveOn() code? navigation AI is disabled when player is ghost TODO confirm about ethereal state, ammy of phasing
// DBG4(DBGAV2(v2KeepGoingTo),"walls? fixing it...",olt->GetNameSingular().CStr(),PLAYER->GetPanelName().CStr());
// bOk=false;
// }
//
// if(bOk && (olt->GetWalkability() & ETHEREAL)){ //TODO this may be too much unnecessary test
// bOk=false;
// }
// */
// }
}
if(bOk){
SetGoingTo(v2KeepGoingTo); DBG3(DBGAV2(GetPos()),DBGAV2(GoingTo),DBGAV2(v2KeepGoingTo));
CreateRoute();
if(Route.empty()){
TerminateGoingTo(); //redundant?
bOk=false;
}
}
if(!bOk){
DBG1("RouteCreationFailed");
vv2FailTravelToTargets.push_back(v2KeepGoingTo); DBG3("BlockGoToDestination",DBGAV2(v2KeepGoingTo),vv2FailTravelToTargets.size());
bAutoPlayUseRandomNavTargetOnce=true;
AutoPlayAIReset(false); //v2KeepGoingTo is reset here too
}
return bOk;
}
void character::AutoPlayAIDebugDrawSquareRect(v2 v2SqrPos, col16 color, int iPrintIndex, bool bWide, bool bKeepColor)
{
static v2 v2ScrPos=v2(0,0); //static to avoid instancing
static int iAddPos;iAddPos=bWide?2:1;
static int iSubBorder;iSubBorder=bWide?3:2;
if(game::OnScreen(v2SqrPos)){
v2ScrPos=game::CalculateScreenCoordinates(v2SqrPos);
DOUBLE_BUFFER->DrawRectangle(
v2ScrPos.X+iAddPos, v2ScrPos.Y+iAddPos,
v2ScrPos.X+TILE_SIZE-iSubBorder, v2ScrPos.Y+TILE_SIZE-iSubBorder,
color, bWide);
if(iPrintIndex>-1)
FONT->Printf(DOUBLE_BUFFER, v2(v2ScrPos.X+1,v2ScrPos.Y+5), DARK_GRAY, "%d", iPrintIndex);
if(!bKeepColor)
vv2DebugDrawSqrPrevious.push_back(v2SqrPos);
}
}
const int iVisitAgainMax=10;
int iVisitAgainCount=iVisitAgainMax;
std::vector<lsquare*> vv2AllDungeonSquares;
bool character::AutoPlayAICheckAreaLevelChangedAndReset()
{
static area* areaPrevious=NULL;
area* Area = game::GetCurrentArea();
if(Area != areaPrevious){
areaPrevious=Area;
iVisitAgainCount=iVisitAgainMax;
vv2DebugDrawSqrPrevious.clear();
vv2AllDungeonSquares.clear();
if(!game::IsInWilderness())
for(int iY=0;iY<game::GetCurrentLevel()->GetYSize();iY++){ for(int iX=0;iX<game::GetCurrentLevel()->GetXSize();iX++){
vv2AllDungeonSquares.push_back(game::GetCurrentLevel()->GetLSquare(iX, iY));
}}
return true;
}
return false;
}
void character::AutoPlayAIDebugDrawOverlay()
{
if(!game::WizardModeIsActive())return;
AutoPlayAICheckAreaLevelChangedAndReset();
// redraw previous to clean them
area* Area = game::GetCurrentArea(); //got the Area to draw in the wilderness too and TODO navigate there one day
std::vector<v2> vv2DebugDrawSqrPreviousCopy(vv2DebugDrawSqrPrevious);
for(int i=0;i<vv2DebugDrawSqrPreviousCopy.size();i++){
// Area->GetSquare(vv2DebugDrawSqrPrevious[i])->SendNewDrawRequest();
// square* sqr = Area->GetSquare(vv2DebugDrawSqrPrevious[i]);
// if(sqr)sqr->SendStrongNewDrawRequest(); //TODO sqr NULL?
AutoPlayAIDebugDrawSquareRect(vv2DebugDrawSqrPreviousCopy[i],DARK_GRAY);
}
// draw new ones
vv2DebugDrawSqrPrevious.clear(); //empty before fillup below
for(int i=0;i<vv2FailTravelToTargets.size();i++)
AutoPlayAIDebugDrawSquareRect(vv2FailTravelToTargets[i],RED,i==(vv2FailTravelToTargets.size()-1),i,true);
if(!PLAYER->Route.empty())
for(int i=0;i<PLAYER->Route.size();i++)
AutoPlayAIDebugDrawSquareRect(PLAYER->Route[i],GREEN);
if(!v2KeepGoingTo.Is0())
AutoPlayAIDebugDrawSquareRect(v2KeepGoingTo,BLUE,PLAYER->Route.size(),true);
else if(iWanderTurns>0)
AutoPlayAIDebugDrawSquareRect(PLAYER->GetPos(),YELLOW,iWanderTurns);
for(int i=0;i<vv2WrongGoingTo.size();i++)
AutoPlayAIDebugDrawSquareRect(vv2WrongGoingTo[i],BLUE,i,false,true);
}
truth character::AutoPlayAIDropThings()
{
// level* lvl = game::GetCurrentLevel(); DBG1(lvl);
// area* Area = game::GetCurrentArea();
/**
* unburden
*/
bool bDropSomething = false;
static item* eqDropChk=NULL;
item* eqBroken=NULL;
for(int i=0;i<GetEquipments();i++){
eqDropChk=GetEquipment(i);
if(eqDropChk!=NULL && eqDropChk->IsBroken()){ DBG2("chkDropBroken",eqDropChk);
eqBroken=eqDropChk;
bDropSomething=true;
break;
}
}
if(!bDropSomething && GetBurdenState() == STRESSED){
if(clock()%100<5){ //5% chance to drop something weighty randomly every turn
bDropSomething=true; DBGLN;
}
}
if(!bDropSomething && GetBurdenState() == OVER_LOADED){
bDropSomething=true;
}
if(bDropSomething){ DBG1("DropSomething");
item* dropMe=NULL;
if(eqBroken!=NULL)dropMe=eqBroken;
item* heaviest=NULL;
item* cheapest=NULL;
// bool bFound=false;
// for(int k=0;k<2;k++){
// if(dropMe!=NULL)break;
// static item* eqDropChk=NULL;
// for(int i=0;i<GetEquipments();i++){
// eqDropChk=GetEquipment(i);
// if(eqDropChk!=NULL && eqDropChk->IsBroken()){
// dropMe=eqDropChk;
// break;
// }
// }
if(dropMe==NULL){
static itemvector vit;vit.clear();GetStack()->FillItemVector(vit);
for(int i=0;i<vit.size();i++){ DBG4("CurrentChkToDrop",vit[i]->GetName(DEFINITE).CStr(),vit[i]->GetTruePrice(),vit[i]->GetWeight());
if(vit[i]->IsEncryptedScroll())continue;
// if(!bPlayerHasLantern && dynamic_cast<lantern*>(vit[i])!=NULL){
// bPlayerHasLantern=true; //will keep only the 1st lantern
// continue;
// }
if(vit[i]->IsBroken()){ //TODO use repair scroll?
dropMe=vit[i];
break;
}
if(heaviest==NULL)heaviest=vit[i];
if(cheapest==NULL)cheapest=vit[i];
// switch(k){
// case 0: //better not implement this as a user function as that will remove the doubt about items values what is another fun challenge :)
if(vit[i]->GetTruePrice() < cheapest->GetTruePrice()) //cheapest
cheapest=vit[i];
// break;
// case 1: //this could be added as user function to avoid browsing the drop list, but may not be that good...
if(vit[i]->GetWeight() > heaviest->GetWeight()) //heaviest
heaviest=vit[i];
// break;
// }
}
}
if(heaviest!=NULL && cheapest!=NULL){
if(dropMe==NULL && heaviest==cheapest)
dropMe=heaviest;
if(dropMe==NULL && cheapest->GetTruePrice()<=iMaxValueless){ DBG2("DropValueless",cheapest->GetName(DEFINITE).CStr());
dropMe=cheapest;
}
if(dropMe==NULL){
// the worst price VS weight will be dropped
float fC = cheapest ->GetTruePrice()/(float)cheapest ->GetWeight();
float fW = heaviest->GetTruePrice()/(float)heaviest->GetWeight(); DBG3("PriceVsWeightRatio",fC,fW);
if(fC < fW){
dropMe = cheapest;
}else{
dropMe = heaviest;
}
}
if(dropMe==NULL)
dropMe = clock()%2==0 ? heaviest : cheapest;
}
// chose a throw direction
if(dropMe!=NULL){
static std::vector<int> vv2DirBase;static bool bDummyInit = [](){for(int i=0;i<8;i++)vv2DirBase.push_back(i);return true;}();
std::vector<int> vv2Dir(vv2DirBase);
int iDirOk=-1;
v2 v2DropAt(0,0);
lsquare* lsqrDropAt=NULL;
for(int i=0;i<8;i++){
int k = clock()%vv2Dir.size(); //random chose from remaining TODO could be where there is NPC foe
int iDir = vv2Dir[k]; //collect direction value
vv2Dir.erase(vv2Dir.begin() + k); //remove using the chosen index to prepare next random choice
v2 v2Dir = game::GetMoveVector(iDir);
v2 v2Chk = GetPos() + v2Dir;
if(game::GetCurrentLevel()->IsValidPos(v2Chk)){
lsquare* lsqrChk=game::GetCurrentLevel()->GetLSquare(v2Chk);
if(lsqrChk->IsFlyable()){
iDirOk = iDir;
v2DropAt = v2Chk;
lsqrDropAt=lsqrChk;
break;
}
}
};DBGLN;
if(iDirOk==-1){iDirOk=clock()%8;DBG2("RandomDir",iDirOk);}DBGLN; //TODO should just drop may be? unless hitting w/e is there could help
if(iDirOk>-1){DBG2("KickOrThrow",iDirOk);
static itemcontainer* itc;itc = dynamic_cast<itemcontainer*>(dropMe);DBGLN;
static humanoid* h;h = dynamic_cast<humanoid*>(this);DBGLN;
DBG8("CanKickLockedChest",lsqrDropAt,itc,itc?itc->IsLocked():-1,CanKick(),h,h?h->GetLeftLeg():0,h?h->GetRightLeg():0);
if(lsqrDropAt && itc && itc->IsLocked() && CanKick() && h && h->GetLeftLeg() && h->GetRightLeg()){DBGLN;
dropMe->MoveTo(lsqrDropAt->GetStack());DBGLN; //drop in front..
Kick(lsqrDropAt,iDirOk,true);DBGLN; // ..to kick it
}else{DBGLN;
ThrowItem(iDirOk, dropMe); DBG5("DropThrow",iDirOk,dropMe->GetName(DEFINITE).CStr(),dropMe->GetTruePrice(),dropMe->GetWeight());
}
}else{DBGLN;
dropMe->MoveTo(GetLSquareUnder()->GetStack());DBGLN; //just drop
}
v2LastDropPlayerWasAt=GetPos();DBGSV2(v2LastDropPlayerWasAt);
return true;
}
DBG1("AutoPlayNeedsImprovement:DropItem");
ADD_MESSAGE("%s says \"I need more intelligence to drop trash...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI
//TODO stop autoplay mode? if not, something random may happen some time and wont reach here ex.: spoil, fire, etc..
}
return false;
}
bool character::IsAutoplayAICanPickup(item* it,bool bPlayerHasLantern)
{
if(!it->CanBeSeenBy(this))return false;
if(!it->IsPickable(this))return false;
if(it->GetSquaresUnder()!=1)return false; //avoid big corpses 2x2
if(!bPlayerHasLantern && it->IsOnFire(this)){
//ok
}else{
if(it->IsBroken())return false;
if(it->GetTruePrice()<=iMaxValueless)return false; //mainly to avoid all rocks from broken walls
if(clock()%3!=0 && it->GetSpoilLevel()>0)return false; //some spoiled may be consumed to randomly test diseases flows
}
return true;
}
truth character::AutoPlayAIEquipAndPickup(bool bPlayerHasLantern)
{
static humanoid* h;h = dynamic_cast<humanoid*>(this);
if(h==NULL)return false;
if(h->AutoPlayAIequip())
return true;
if(GetBurdenState()!=OVER_LOADED){ DBG4(CommandFlags&DONT_CHANGE_EQUIPMENT,this,GetNameSingular().CStr(),GetSquareUnder());
if(v2LastDropPlayerWasAt!=GetPos()){
static bool bHoarder=true; //TODO wizard autoplay AI config exclusive felist
if(CheckForUsefulItemsOnGround(false))
if(!bHoarder)
return true;
//just pick up any useful stuff
static itemvector vit;vit.clear();GetStackUnder()->FillItemVector(vit);
for(uint c = 0; c < vit.size(); ++c){
if(!IsAutoplayAICanPickup(vit[c],bPlayerHasLantern))continue;
static itemcontainer* itc;itc = dynamic_cast<itemcontainer*>(vit[c]);
if(itc && !itc->IsLocked()){ //get items from unlocked container
static itemvector vitItc;vitItc.clear();itc->GetContained()->FillItemVector(vitItc);
for(uint d = 0; d < vitItc.size(); ++d)
vitItc[d]->MoveTo(itc->GetLSquareUnder()->GetStack());
continue;
}
vit[c]->MoveTo(GetStack()); DBG2("pickup",vit[c]->GetNameSingular().CStr());
// if(GetBurdenState()==OVER_LOADED)ThrowItem(clock()%8,ItemVector[c]);
// return true;
if(!bHoarder)
return true;
}
}
}
return false;
}
static const int iMoreThanMaxDist=10000000; //TODO should be max integer but this will do for now in 2018 :)
truth character::AutoPlayAITestValidPathTo(v2 v2To)
{
return AutoPlayAIFindWalkDist(v2To) < iMoreThanMaxDist;
}
int character::AutoPlayAIFindWalkDist(v2 v2To)
{
static bool bUseSimpleDirectDist=false; //very bad navigation this is
if(bUseSimpleDirectDist)return (v2To - GetPos()).GetLengthSquare();
static v2 GoingToBkp;GoingToBkp = GoingTo; //IsGoingSomeWhere() ? GoingTo : v2(0,0);
SetGoingTo(v2To);
CreateRoute();
static int iDist;iDist=Route.size();
TerminateGoingTo();
if(GoingToBkp!=ERROR_V2){ DBG2("Warning:WrongUsage:ShouldBeGoingNoWhere",DBGAV2(GoingToBkp));
SetGoingTo(GoingToBkp);
CreateRoute();
}
return iDist>0?iDist:iMoreThanMaxDist;
}
truth character::AutoPlayAINavigateDungeon(bool bPlayerHasLantern)
{
/**
* navigate the unknown dungeon
*/
std::vector<v2> v2Exits;
if(v2KeepGoingTo.Is0()){ DBG1("TryNewMoveTarget");
// target undiscovered squares to explore
v2 v2PreferedTarget(0,0);
int iNearestLanterOnFloorDist = iMoreThanMaxDist;
v2 v2PreferedLanternOnFloorTarget(0,0);
v2 v2NearestUndiscovered(0,0);
int iNearestUndiscoveredDist=iMoreThanMaxDist;
std::vector<v2> vv2UndiscoveredValidPathSquares;
lsquare* lsqrNearestSquareWithWallLantern=NULL;
lsquare* lsqrNearestDropWallLanternAt=NULL;
stack* stkNearestDropWallLanternAt = NULL;
int iNearestSquareWithWallLanternDist=iMoreThanMaxDist;
item* itNearestWallLantern=NULL;
/***************************************************************
* scan whole dungeon squares
*/
for(int iY=0;iY<game::GetCurrentLevel()->GetYSize();iY++){ for(int iX=0;iX<game::GetCurrentLevel()->GetXSize();iX++){
lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(iX,iY);
olterrain* olt = lsqr->GetOLTerrain();
if(olt && (olt->GetConfig() == STAIRS_UP || olt->GetConfig() == STAIRS_DOWN)){
v2Exits.push_back(v2(lsqr->GetPos())); DBGSV2(v2Exits[v2Exits.size()-1]);
}
stack* stkSqr = lsqr->GetStack();
static itemvector vit;vit.clear();stkSqr->FillItemVector(vit);
bool bAddValidTargetSquare=true;
// find nearest wall lantern
if(!bPlayerHasLantern && olt && olt->IsWall()){
for(int n=0;n<vit.size();n++){
if(vit[n]->IsLanternOnWall() && !vit[n]->IsBroken()){
static stack* stkDropWallLanternAt;stkDropWallLanternAt = lsqr->GetStackOfAdjacentSquare(vit[n]->GetSquarePosition());
static lsquare* lsqrDropWallLanternAt;lsqrDropWallLanternAt =
stkDropWallLanternAt?stkDropWallLanternAt->GetLSquareUnder():NULL;
if(stkDropWallLanternAt && lsqrDropWallLanternAt && CanTheoreticallyMoveOn(lsqrDropWallLanternAt)){
int iDist = AutoPlayAIFindWalkDist(lsqrDropWallLanternAt->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
if(lsqrNearestSquareWithWallLantern==NULL || iDist<iNearestSquareWithWallLanternDist){
iNearestSquareWithWallLanternDist=iDist;
lsqrNearestSquareWithWallLantern=lsqr;
itNearestWallLantern=vit[n]; DBG3(iNearestSquareWithWallLanternDist,DBGAV2(lsqr->GetPos()),DBGAV2(GetPos()));
lsqrNearestDropWallLanternAt=lsqrDropWallLanternAt;
stkNearestDropWallLanternAt=stkDropWallLanternAt;
}
}
break;
}
}
}
if(bAddValidTargetSquare && !CanTheoreticallyMoveOn(lsqr))
bAddValidTargetSquare=false;
bool bIsFailToTravelSquare=false;
if(bAddValidTargetSquare){
for(int j=0;j<vv2FailTravelToTargets.size();j++)
if(vv2FailTravelToTargets[j]==lsqr->GetPos()){
bAddValidTargetSquare=false;
bIsFailToTravelSquare=true;
break;
}
}
if(!bIsFailToTravelSquare){
// if(bAddValidTargetSquare && v2PreferedTarget.Is0() && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){
if(bAddValidTargetSquare && (lsqr->HasBeenSeen() || !bPlayerHasLantern)){
bool bVisitAgain=false;
if(iVisitAgainCount>0 || !bPlayerHasLantern){
if(stkSqr!=NULL && stkSqr->GetItems()>0){
for(int n=0;n<vit.size();n++){ DBG1(vit[n]);DBG1(vit[n]->GetID());DBG1(vit[n]->GetType());DBG3("VisitAgain:ChkItem",vit[n]->GetNameSingular().CStr(),vit.size());
if(vit[n]->IsBroken())continue; DBGLN;
static bool bIsLanternOnFloor;bIsLanternOnFloor = dynamic_cast<lantern*>(vit[n])!=NULL;// || vit[n]->IsOnFire(this); DBGLN;
if( // if is useful to the AutoPlay AI endless tests
vit[n]->IsShield (this) ||
vit[n]->IsWeapon (this) ||
vit[n]->IsArmor (this) ||
vit[n]->IsAmulet (this) ||
vit[n]->IsZappable(this) ||
vit[n]->IsRing (this) ||
bIsLanternOnFloor
)
if(IsAutoplayAICanPickup(vit[n],bPlayerHasLantern))
{
bVisitAgain=true;
if(bIsLanternOnFloor && !bPlayerHasLantern){
static int iDist;iDist = AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
if(iDist<iNearestLanterOnFloorDist){
iNearestLanterOnFloorDist=iDist;
v2PreferedLanternOnFloorTarget = lsqr->GetPos(); DBG2("PreferLanternAt",DBGAV2(lsqr->GetPos()))
}
}else{
iVisitAgainCount--;
}
DBG4(bVisitAgain,DBGAV2(lsqr->GetPos()),iVisitAgainCount,bIsLanternOnFloor);
break;
}
}
}
}
if(!bVisitAgain)bAddValidTargetSquare=false;
}
}
if(bAddValidTargetSquare)
if(!CanTheoreticallyMoveOn(lsqr)) //if(olt && !CanMoveOn(olt))
bAddValidTargetSquare=false;
if(bAddValidTargetSquare){ DBG2("addValidSqr",DBGAV2(lsqr->GetPos()));
static int iDist;iDist=AutoPlayAIFindWalkDist(lsqr->GetPos()); //(lsqr->GetPos() - GetPos()).GetLengthSquare();
if(iDist<iMoreThanMaxDist) //add only valid paths
vv2UndiscoveredValidPathSquares.push_back(lsqr->GetPos());
if(iDist<iNearestUndiscoveredDist){
iNearestUndiscoveredDist=iDist;
v2NearestUndiscovered=lsqr->GetPos(); DBG3(iNearestUndiscoveredDist,DBGAV2(lsqr->GetPos()),DBGAV2(GetPos()));
}
}
}} DBG2(DBGAV2(v2PreferedTarget),vv2UndiscoveredValidPathSquares.size());
/***************************************************************
* define prefered navigation target
*/
if(!bPlayerHasLantern && v2PreferedTarget.Is0()){
bool bUseWallLantern=false;
if(!v2PreferedLanternOnFloorTarget.Is0() && lsqrNearestSquareWithWallLantern!=NULL){
if(iNearestLanterOnFloorDist <= iNearestSquareWithWallLanternDist){
v2PreferedTarget=v2PreferedLanternOnFloorTarget;
}else{
bUseWallLantern=true;
}
}else if(!v2PreferedLanternOnFloorTarget.Is0()){
v2PreferedTarget=v2PreferedLanternOnFloorTarget;
}else if(lsqrNearestSquareWithWallLantern!=NULL){
bUseWallLantern=true;
}
if(bUseWallLantern){
/**
* target to nearest wall lantern
* check for lanterns on walls of adjacent squares if none found on floors
*/
itNearestWallLantern->MoveTo(stkNearestDropWallLanternAt); // the AI is prepared to get things from the floor only so "magically" drop it :)
v2PreferedTarget = lsqrNearestDropWallLanternAt->GetPos(); DBG2("PreferWallLanternAt",DBGAV2(lsqrNearestDropWallLanternAt->GetPos()))
}
}
/***************************************************************
* validate and set new navigation target
*/
// DBG9("AllNavigatePossibilities",DBGAV2(v2PreferedTarget),DBGAV2(v2PreferedLanternOnFloorTarget),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2(),DBGAV2());
v2 v2NewKGTo=v2(0,0);
if(v2NewKGTo.Is0()){
//TODO if(!v2PreferedTarget.Is0){ // how can this not be compiled? error: cannot convert ‘v2::Is0’ from type ‘truth (v2::)() const {aka bool (v2::)() const}’ to type ‘bool’
if(v2PreferedTarget.GetLengthSquare()>0)
if(AutoPlayAITestValidPathTo(v2PreferedTarget))
v2NewKGTo=v2PreferedTarget; DBGSV2(v2PreferedTarget);
}
if(v2NewKGTo.Is0()){
if(bAutoPlayUseRandomNavTargetOnce){ //these targets were already path validated and are safe to use!
v2NewKGTo=vv2UndiscoveredValidPathSquares[clock()%vv2UndiscoveredValidPathSquares.size()]; DBG2("RandomTarget",DBGAV2(v2NewKGTo));
bAutoPlayUseRandomNavTargetOnce=false;
}else{ //find nearest
if(!v2NearestUndiscovered.Is0()){
v2NewKGTo=v2NearestUndiscovered; DBGSV2(v2NearestUndiscovered);
}
}
}
if(v2NewKGTo.Is0()){ //no new destination: fully explored
if(v2Exits.size()>0){
if(game::GetCurrentDungeonTurnsCount()==0){ DBG1("Dungeon:FullyExplored:FirstTurn");
iWanderTurns=100+clock()%300; DBG2("WanderALotOnFullyExploredLevel",iWanderTurns); //just move around a lot, some NPC may spawn
}else{
// travel between dungeons if current fully explored
v2 v2Try = v2Exits[clock()%v2Exits.size()];
if(AutoPlayAITestValidPathTo(v2Try))
v2NewKGTo = v2TravelingToAnotherDungeon = v2Try; DBGSV2(v2TravelingToAnotherDungeon);
}
}else{
DBG1("AutoPlayNeedsImprovement:Navigation")
ADD_MESSAGE("%s says \"I need more intelligence to move around...\"", CHAR_NAME(DEFINITE)); // improve the dropping AI
//TODO stop autoplay mode?
}
}
if(v2NewKGTo.Is0()){ DBG1("Desperately:TryAnyRandomTargetNavWithValidPath");
std::vector<lsquare*> vlsqrChk(vv2AllDungeonSquares);
while(vlsqrChk.size()>0){
static int i;i=clock()%vlsqrChk.size();
static v2 v2Chk; v2Chk = vlsqrChk[i]->GetPos();
if(!AutoPlayAITestValidPathTo(v2Chk)){
vlsqrChk.erase(vlsqrChk.begin()+i);
}else{
v2NewKGTo=v2Chk;
break;
}
}
}
if(!v2NewKGTo.Is0()){
AutoPlayAISetAndValidateKeepGoingTo(v2NewKGTo);
}else{
DBG1("TODO:too complex paths are failing... improve CreateRoute()?");
}
}
if(!v2KeepGoingTo.Is0()){
if(v2KeepGoingTo==GetPos()){ DBG3("ReachedDestination",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo));
//wander a bit before following new target destination
iWanderTurns=(clock()%iMaxWanderTurns)+iMinWanderTurns; DBG2("WanderAroundAtReachedDestination",iWanderTurns);
// v2KeepGoingTo=v2(0,0);
// TerminateGoingTo();
AutoPlayAIReset(false);
return true;
}
// CheckForUsefulItemsOnGround(false); DBGSV2(GoingTo);
// CheckForEnemies(false, true, false, false); DBGSV2(GoingTo);
// if(!IsGoingSomeWhere() || v2KeepGoingTo!=GoingTo){ DBG3("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo));
// SetGoingTo(v2KeepGoingTo);
// }
static int iForceGoingToCountDown=10;
static v2 v2GoingToBkp;v2GoingToBkp=GoingTo;
if(!v2KeepGoingTo.IsAdjacent(GoingTo)){
if(iForceGoingToCountDown==0){
DBG4("ForceKeepGoingTo",DBGAV2(v2KeepGoingTo),DBGAV2(GoingTo),DBGAV2(GetPos()));
if(!AutoPlayAISetAndValidateKeepGoingTo(v2KeepGoingTo)){
static int iSetFailTeleportCountDown=10;
iSetFailTeleportCountDown--;
vv2WrongGoingTo.push_back(v2GoingToBkp);
if(iSetFailTeleportCountDown==0){
AutoPlayAITeleport(false);
AutoPlayAIReset(true); //refresh to test/try it all again
iSetFailTeleportCountDown=10;
}
}
DBGSV2(GoingTo);
return true;
}else{
iForceGoingToCountDown--; DBG1(iForceGoingToCountDown);
}
}else{
iForceGoingToCountDown=10;
}
/**
* Determinedly blindly moves towards target, the goal is to Navigate!
*
* this has several possible status if returning false...
* so better do not decide anything based on it?
*/
MoveTowardsTarget(false);
// if(!MoveTowardsTarget(false)){ DBG3("OrFailedGoingTo,OrReachedDestination...",DBGAV2(GoingTo),DBGAV2(GetPos())); // MoveTowardsTarget may break the GoingTo EVEN if it succeeds?????
// TerminateGoingTo();
// v2KeepGoingTo=v2(0,0); //reset only this one to try again
// GetAICommand(); //wander once for randomicity
// }
return true;
}
return false;
}
bool character::AutoPlayAIChkInconsistency()
{
if(GetSquareUnder()==NULL){
DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet());
DBG6("GetSquareUnderIsNULLhow?",IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr());
return true; //to just ignore this turn expecting on next it will be ok.
}
return false;
}
truth character::AutoPlayAIPray()
{
bool bSPO = bSafePrayOnce;
bSafePrayOnce=false;
if(bSPO){}
else if(StateIsActivated(PANIC) && clock()%10==0){
iWanderTurns=1; DBG1("Wandering:InPanic"); // to regain control as soon it is a ghost anymore as it can break navigation when inside walls
}else return false;
// check for known gods
int aiKGods[GODS];
int iKGTot=0;
int aiKGodsP[GODS];
int iKGTotP=0;
static int iPleased=50; //see god::PrintRelation()
for(int c = 1; c <= GODS; ++c){
if(!game::GetGod(c)->IsKnown())continue;
// even known, praying to these extreme ones will be messy if Relation<1000
if(dynamic_cast<valpurus*>(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue;
if(dynamic_cast<mortifer*>(game::GetGod(c))!=NULL && game::GetGod(c)->GetRelation()<1000)continue;
aiKGods[iKGTot++]=c;
if(game::GetGod(c)->GetRelation() > iPleased){
// //TODO could this help?
// switch(game::GetGod(c)->GetBasicAlignment()){ //game::GetGod(c)->GetAlignment();
// case GOOD:
// if(game::GetPlayerAlignment()>=2){}else continue;
// break;
// case NEUTRAL:
// if(game::GetPlayerAlignment()<2 && game::GetPlayerAlignment()>-2){}else continue;
// break;
// case EVIL:
// if(game::GetPlayerAlignment()<=-2){}else continue;
// break;
// }
aiKGodsP[iKGTotP++] = c;
}
}
if(iKGTot==0)return false;
// if(bSPO && iKGTotP==0)return false;
// chose and pray to one god
god* g = NULL;
if(iKGTotP>0 && (bSPO || clock()%10!=0))
g = game::GetGod(aiKGodsP[clock()%iKGTotP]);
else
g = game::GetGod(aiKGods[clock()%iKGTot]);
if(bSPO || clock()%10!=0){ //it may not recover some times to let pray unsafely
int iRecover=0;
if(iKGTotP==0){
if(iRecover==0 && g->GetRelation()==-1000)iRecover=1000; //to test all relation range
if(iRecover==0 && g->GetRelation() <= iPleased)iRecover=iPleased; //to alternate tests on many with low good relation
}
if(iRecover>0)
g->SetRelation(iRecover);
g->AdjustTimer(-1000000000); //TODO filter gods using timer too instead of this reset?
}
g->Pray(); DBG2("PrayingTo",g->GetName());
return true;
}
truth character::AutoPlayAICommand(int& rKey)
{
DBGLN;if(AutoPlayAIChkInconsistency())return true;
DBGSV2(GetPos());
if(AutoPlayLastChar!=this){
AutoPlayAIReset(true);
AutoPlayLastChar=this;
}
DBGLN;if(AutoPlayAIChkInconsistency())return true;
if(AutoPlayAICheckAreaLevelChangedAndReset())
AutoPlayAIReset(true);
static bool bDummy_initDbg = [](){game::AddDebugDrawOverlayFunction(&AutoPlayAIDebugDrawOverlay);return true;}();
truth bPlayerHasLantern=false;
static itemvector vit;vit.clear();GetStack()->FillItemVector(vit);
for(uint i=0;i<vit.size();i++){
if(dynamic_cast<lantern*>(vit[i])!=NULL || vit[i]->IsOnFire(this)){
bPlayerHasLantern=true; //will keep only the 1st lantern
break;
}
}
DBGLN;if(AutoPlayAIChkInconsistency())return true;
AutoPlayAIPray();
//TODO this doesnt work??? -> if(IsPolymorphed()){ //to avoid some issues TODO but could just check if is a ghost
// if(dynamic_cast<humanoid*>(this) == NULL){ //this avoid some issues TODO but could just check if is a ghost
// if(StateIsActivated(ETHEREAL_MOVING)){ //this avoid many issues
static bool bPreviousTurnWasGhost=false;
if(dynamic_cast<ghost*>(this) != NULL){ DBG1("Wandering:Ghost"); //this avoid many issues mainly related to navigation
iWanderTurns=1; // to regain control as soon it is a ghost anymore as it can break navigation when inside walls
bPreviousTurnWasGhost=true;
}else{
if(bPreviousTurnWasGhost){
AutoPlayAIReset(true); //this may help on navigation
bPreviousTurnWasGhost=false;
return true;
}
}
DBGLN;if(AutoPlayAIChkInconsistency())return true;
if(AutoPlayAIDropThings())
return true;
DBGLN;if(AutoPlayAIChkInconsistency())return true;
if(AutoPlayAIEquipAndPickup(bPlayerHasLantern))
return true;
if(iWanderTurns>0){
if(!IsPlayer() || game::GetAutoPlayMode()==0 || !IsPlayerAutoPlay()){ //redundancy: yep
DBG9(this,GetNameSingular().CStr(),IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),IsTemporary(),IsPet());
DBG5(IsHeadless(),IsPlayer(),game::GetAutoPlayMode(),IsPlayerAutoPlay(),GetName(DEFINITE).CStr());
ABORT("autoplay is inconsistent %d %d %d %d %d %s %d %s %d %d %d %d %d",
IsPolymorphed(),IsHuman(),IsHumanoid(),IsPolymorphable(),IsPlayerKind(),
GetNameSingular().CStr(),game::GetAutoPlayMode(),GetName(DEFINITE).CStr(),
IsTemporary(),IsPet(),IsHeadless(),IsPlayer(),IsPlayerAutoPlay());
}
GetAICommand(); DBG2("Wandering",iWanderTurns); //fallback to default TODO never reached?
iWanderTurns--;
return true;
}
/***************************************************************************************************
* WANDER above here
* NAVIGATE below here
***************************************************************************************************/
/**
* travel between dungeons
*/
if(!v2TravelingToAnotherDungeon.Is0() && GetPos() == v2TravelingToAnotherDungeon){
bool bTravel=false;
lsquare* lsqr = game::GetCurrentLevel()->GetLSquare(v2TravelingToAnotherDungeon);
// square* sqr = Area->GetSquare(v2TravelingToAnotherDungeon);
olterrain* ot = lsqr->GetOLTerrain();
// oterrain* ot = sqr->GetOTerrain();
if(ot){
if(ot->GetConfig() == STAIRS_UP){
rKey='<';
bTravel=true;
}
if(ot->GetConfig() == STAIRS_DOWN){
rKey='>';
bTravel=true;
}
}
if(bTravel){ DBG3("travel",DBGAV2(v2TravelingToAnotherDungeon),rKey);
AutoPlayAIReset(true);
return false; //so the new/changed key will be used as command, otherwise it would be ignored
}
}
static const int iDesperateResetCountDownDefault=10;
static const int iDesperateEarthQuakeCountDownDefault=iDesperateResetCountDownDefault*5;
static int iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
if(AutoPlayAINavigateDungeon(bPlayerHasLantern)){
iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
return true;
}else{
if(iDesperateEarthQuakeCountDown==0){
iDesperateEarthQuakeCountDown=iDesperateEarthQuakeCountDownDefault;
scrollofearthquake::Spawn()->FinishReading(this);
DBG1("UsingTerribleEarthquakeSolution"); // xD
}else{
iDesperateEarthQuakeCountDown--;
DBG1(iDesperateEarthQuakeCountDown);
}
}
/****************************************
* Twighlight zone
*/
ADD_MESSAGE("%s says \"I need more intelligence to do things by myself...\"", CHAR_NAME(DEFINITE)); DBG1("TODO: AI needs improvement");
static int iDesperateResetCountDown=iDesperateResetCountDownDefault;
if(iDesperateResetCountDown==0){
iDesperateResetCountDown=iDesperateResetCountDownDefault;
AutoPlayAIReset(true);
// AFTER THE RESET!!!
iWanderTurns=iMaxWanderTurns; DBG2("DesperateResetToSeeIfAIWorksAgain",iWanderTurns);
}else{
GetAICommand(); DBG2("WanderingDesperatelyNotKnowingWhatToDo",iDesperateResetCountDown); // :)
iDesperateResetCountDown--;
}
return true;
}
void character::GetPlayerCommand()
{
truth HasActed = false;
while(!HasActed)
{
graphics::SetAllowStretchedBlit(); //overall great/single location to re-enable stretched blit!
game::DrawEverything();
if(!StateIsActivated(FEARLESS) && game::GetDangerFound())
{
if(game::GetDangerFound() > 500.)
{
if(game::GetCausePanicFlag())
{
game::SetCausePanicFlag(false);
BeginTemporaryState(PANIC, 500 + RAND_N(500));
}
game::AskForKeyPress(CONST_S("You are horrified by your situation! [press any key to continue]"));
}
else if(ivanconfig::GetWarnAboutDanger())
{
if(game::GetDangerFound() > 50.)
game::AskForKeyPress(CONST_S("You sense great danger! [press any key to continue]"));
else
game::AskForKeyPress(CONST_S("You sense danger! [press any key to continue]"));
}
game::SetDangerFound(0);
}
game::SetIsInGetCommand(true);
int Key = GET_KEY();
game::SetIsInGetCommand(false);
if(Key != '+' && Key != '-' && Key != 'M') // gum (these are the messages keys M=ShowHistory +-=ScrollHistUpDown)
msgsystem::ThyMessagesAreNowOld();
truth ValidKeyPressed = false;
int c;
#ifdef WIZARD
if(IsPlayerAutoPlay()){
bool bForceStop = false;
if(game::GetAutoPlayMode()>=2)
bForceStop = globalwindowhandler::IsKeyPressed(SDL_SCANCODE_ESCAPE);
if(!bForceStop && Key=='.'){ // pressed or simulated
if(game::IsInWilderness()){
Key='>'; //blindly tries to go back to the dungeon safety :) TODO target and move to other dungeons/towns in the wilderness
}else{
HasActed = AutoPlayAICommand(Key); DBG2("Simulated",Key);
if(HasActed)ValidKeyPressed = true; //valid simulated action
}
}else{
/**
* if the user hits any key during the autoplay mode that runs by itself, it will be disabled.
* at non auto mode, can be moved around but cannot rest or will move by itself
*/
if(game::GetAutoPlayMode()>=2 && (Key!='~' || bForceStop)){
game::DisableAutoPlayMode();
AutoPlayAIReset(true); // this will help on re-randomizing things, mainly paths
}
}
}
#endif
if(!HasActed){
for(c = 0; c < DIRECTION_COMMAND_KEYS; ++c)
if(Key == game::GetMoveCommandKey(c))
{
bool bWaitNeutralMove=false;
HasActed = TryMove(ApplyStateModification(game::GetMoveVector(c)), true, game::PlayerIsRunning(), &bWaitNeutralMove);
if(HasActed){
game::CheckAddAutoMapNote();
game::CheckAutoPickup();
}
if(!HasActed && bWaitNeutralMove){
//cant access.. HasActed = commandsystem::NOP(this);
Key = '.'; //TODO request NOP()'s key instead of this '.' hardcoded here. how?
}
ValidKeyPressed = true;
}
for(c = 1; commandsystem::GetCommand(c); ++c)
if(Key == commandsystem::GetCommand(c)->GetKey())
{
if(game::IsInWilderness() && !commandsystem::GetCommand(c)->IsUsableInWilderness())
ADD_MESSAGE("This function cannot be used while in wilderness.");
else
if(!game::WizardModeIsActive() && commandsystem::GetCommand(c)->IsWizardModeFunction())
ADD_MESSAGE("Activate wizardmode to use this function.");
else{
game::RegionListItemEnable(commandsystem::IsForRegionListItem(c));
game::RegionSilhouetteEnable(commandsystem::IsForRegionSilhouette(c));
HasActed = commandsystem::GetCommand(c)->GetLinkedFunction()(this);
game::RegionListItemEnable(false);
game::RegionSilhouetteEnable(false);
}
ValidKeyPressed = true;
break;
}
}
if(!ValidKeyPressed)
ADD_MESSAGE("Unknown key. Press '?' for a list of commands.");
}
game::IncreaseTurn();
}
void character::Vomit(v2 Pos, int Amount, truth ShowMsg)
{
if(!CanVomit())
return;
if(ShowMsg)
{
if(IsPlayer())
ADD_MESSAGE("You vomit.");
else if(CanBeSeenByPlayer())
ADD_MESSAGE("%s vomits.", CHAR_NAME(DEFINITE));
}
if(VomittingIsUnhealthy())
{
EditExperience(ARM_STRENGTH, -75, 1 << 9);
EditExperience(LEG_STRENGTH, -75, 1 << 9);
}
if(IsPlayer())
{
EditNP(-2500 - RAND() % 2501);
CheckStarvationDeath(CONST_S("vomited himself to death"));
}
if(StateIsActivated(PARASITE_TAPE_WORM) && !(RAND() & 7))
{
if(IsPlayer())
ADD_MESSAGE("You notice a dead broad tapeworm among your former stomach contents.");
DeActivateTemporaryState(PARASITE_TAPE_WORM);
}
if(!game::IsInWilderness())
GetNearLSquare(Pos)->ReceiveVomit(this,
liquid::Spawn(GetMyVomitMaterial(), long(sqrt(GetBodyVolume()) * Amount / 1000)));
}
truth character::Polymorph(character* NewForm, int Counter)
{
if(NewForm == NULL)
ABORT("Unable to polymorph into NULL!");
if(!IsPolymorphable() || (!IsPlayer() && game::IsInWilderness()))
{
delete NewForm;
return false;
}
RemoveTraps();
if(GetAction())
GetAction()->Terminate(false);
NewForm->SetAssignedName("");
if(IsPlayer())
ADD_MESSAGE("Your body glows in a crimson light. You transform into %s!", NewForm->CHAR_NAME(INDEFINITE));
else if(CanBeSeenByPlayer())
ADD_MESSAGE("%s glows in a crimson light and %s transforms into %s!",
CHAR_NAME(DEFINITE), GetPersonalPronoun().CStr(), NewForm->CHAR_NAME(INDEFINITE));
Flags |= C_IN_NO_MSG_MODE;
NewForm->Flags |= C_IN_NO_MSG_MODE;
NewForm->ChangeTeam(GetTeam());
NewForm->GenerationDanger = GenerationDanger;
if(GetTeam()->GetLeader() == this)
GetTeam()->SetLeader(NewForm);
v2 Pos = GetPos();
Remove();
NewForm->PutToOrNear(Pos);
NewForm->SetAssignedName(GetAssignedName());
NewForm->ActivateTemporaryState(POLYMORPHED);
NewForm->SetTemporaryStateCounter(POLYMORPHED, Counter);
if(TemporaryStateIsActivated(POLYMORPHED))
{
NewForm->SetPolymorphBackup(GetPolymorphBackup());
SetPolymorphBackup(0);
SendToHell();
}
else
{
NewForm->SetPolymorphBackup(this);
Flags |= C_POLYMORPHED;
Disable();
}
GetStack()->MoveItemsTo(NewForm->GetStack());
NewForm->SetMoney(GetMoney());
DonateEquipmentTo(NewForm);
Flags &= ~C_IN_NO_MSG_MODE;
NewForm->Flags &= ~C_IN_NO_MSG_MODE;
NewForm->CalculateAll();
if(IsPlayer())
{
Flags &= ~C_PLAYER;
game::SetPlayer(NewForm);
game::SendLOSUpdateRequest();
UpdateESPLOS();
}
NewForm->TestWalkability();
return true;
}
void character::BeKicked(character* Kicker, item* Boot, bodypart* Leg, v2 HitPos, double KickDamage,
double ToHitValue, int Success, int Direction, truth Critical, truth ForceHit)
{
switch(TakeHit(Kicker, Boot, Leg, HitPos, KickDamage, ToHitValue,
Success, KICK_ATTACK, Direction, Critical, ForceHit))
{
case HAS_HIT:
case HAS_BLOCKED:
case DID_NO_DAMAGE:
if(IsEnabled() && (!CheckBalance(KickDamage) || (Boot && Boot->IsKicking())))
{
if(IsPlayer())
ADD_MESSAGE("The kick throws you off balance.");
else if(Kicker->IsPlayer())
ADD_MESSAGE("The kick throws %s off balance.", CHAR_DESCRIPTION(DEFINITE));
v2 FallToPos = GetPos() + game::GetMoveVector(Direction);
FallTo(Kicker, FallToPos);
}
}
}
/* Return true if still in balance */
truth character::CheckBalance(double KickDamage)
{
return !CanMove()
|| IsStuck()
|| !KickDamage
|| (!IsFlying()
&& KickDamage * 5 < RAND() % GetSize());
}
void character::FallTo(character* GuiltyGuy, v2 Where)
{
EditAP(-500);
lsquare* MoveToSquare[MAX_SQUARES_UNDER];
int Squares = CalculateNewSquaresUnder(MoveToSquare, Where);
if(Squares)
{
truth NoRoom = false;
for(int c = 0; c < Squares; ++c)
{
olterrain* Terrain = MoveToSquare[c]->GetOLTerrain();
if(Terrain && !CanMoveOn(Terrain))
{
NoRoom = true;
break;
}
}
if(NoRoom)
{
if(HasHead())
{
if(IsPlayer())
ADD_MESSAGE("You hit your head on the wall.");
else if(CanBeSeenByPlayer())
ADD_MESSAGE("%s hits %s head on the wall.", CHAR_NAME(DEFINITE), GetPossessivePronoun().CStr());
}
ReceiveDamage(GuiltyGuy, 1 + RAND() % 5, PHYSICAL_DAMAGE, HEAD);
CheckDeath(CONST_S("killed by hitting a wall due to being kicked @bk"), GuiltyGuy);
}
else
{
if(IsFreeForMe(MoveToSquare[0]))
Move(Where, true);
// Place code that handles characters bouncing to each other here
}
}
}
truth character::CheckCannibalism(cmaterial* What) const
{
return GetTorso()->GetMainMaterial()->IsSameAs(What);
}
void character::StandIdleAI()
{
SeekLeader(GetLeader());
if(CheckForEnemies(true, true, true))
return;
if(CheckForUsefulItemsOnGround())
return;
if(FollowLeader(GetLeader()))
return;
if(CheckForDoors())
return;
if(MoveTowardsHomePos())
return;
if(CheckSadism())
return;
EditAP(-1000);
}
truth character::LoseConsciousness(int Counter, truth HungerFaint)
{
if(!AllowUnconsciousness())
return false;
action* Action = GetAction();
if(Action)
{
if(HungerFaint && !Action->AllowUnconsciousness())
return false;
if(Action->IsUnconsciousness())
{
static_cast<unconsciousness*>(Action)->RaiseCounterTo(Counter);
return true;
}
else
Action->Terminate(false);
}
if(IsPlayer())
ADD_MESSAGE("You lose consciousness.");
else if(CanBeSeenByPlayer())
ADD_MESSAGE("%s loses consciousness.", CHAR_NAME(DEFINITE));
unconsciousness* Unconsciousness = unconsciousness::Spawn(this);
Unconsciousness->SetCounter(Counter);
SetAction(Unconsciousness);
return true;
}
void character::DeActivateTemporaryState(long What)
{
if(PolymorphBackup)
PolymorphBackup->TemporaryState &= ~What;
TemporaryState &= ~What;
}
void character::DeActivateVoluntaryAction(cfestring& Reason)
{
if(GetAction() && GetAction()->IsVoluntary())
{
if(IsPlayer())
{
if(Reason.GetSize())
ADD_MESSAGE("%s", Reason.CStr());
if(game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription() + "? [y/N]"))
GetAction()->ActivateInDNDMode();
else
GetAction()->Terminate(false);
}
else
GetAction()->Terminate(false);
}
}
void character::ActionAutoTermination()
{
if(!GetAction() || !GetAction()->IsVoluntary() || GetAction()->InDNDMode())
return;
v2 Pos = GetPos();
for(int c = 0; c < game::GetTeams(); ++c)
if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
for(character* p : game::GetTeam(c)->GetMember())
if(p->IsEnabled()
&& p->CanBeSeenBy(this, false, true)
&& (p->CanMove() || p->GetPos().IsAdjacent(Pos))
&& p->CanAttack())
{
if(IsPlayer())
{
ADD_MESSAGE("%s seems to be hostile.", p->CHAR_NAME(DEFINITE));
if(game::TruthQuestion(CONST_S("Continue ") + GetAction()->GetDescription() + "? [y/N]"))
GetAction()->ActivateInDNDMode();
else
GetAction()->Terminate(false);
}
else
GetAction()->Terminate(false);
return;
}
}
truth character::CheckForEnemies(truth CheckDoors, truth CheckGround, truth MayMoveRandomly, truth RunTowardsTarget)
{
if(!IsEnabled())
return false;
truth HostileCharsNear = false;
character* NearestChar = 0;
long NearestDistance = 0x7FFFFFFF;
v2 Pos = GetPos();
for(int c = 0; c < game::GetTeams(); ++c)
if(GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
for(character* p : game::GetTeam(c)->GetMember())
if(p->IsEnabled() && GetAttribute(WISDOM) < p->GetAttackWisdomLimit())
{
long ThisDistance = Max<long>(abs(p->GetPos().X - Pos.X), abs(p->GetPos().Y - Pos.Y));
if(ThisDistance <= GetLOSRangeSquare())
HostileCharsNear = true;
if((ThisDistance < NearestDistance
|| (ThisDistance == NearestDistance && !(RAND() % 3)))
&& p->CanBeSeenBy(this, false, IsGoingSomeWhere())
&& (!IsGoingSomeWhere() || HasClearRouteTo(p->GetPos())))
{
NearestChar = p;
NearestDistance = ThisDistance;
}
}
if(NearestChar)
{
if(GetAttribute(INTELLIGENCE) >= 10 || IsSpy())
game::CallForAttention(GetPos(), 100);
if(SpecialEnemySightedReaction(NearestChar))
return true;
if(IsExtraCoward() && !StateIsActivated(PANIC) && NearestChar->GetRelativeDanger(this) >= 0.5 && !StateIsActivated(FEARLESS))
{
if(CanBeSeenByPlayer())
ADD_MESSAGE("%s sees %s.", CHAR_NAME(DEFINITE), NearestChar->CHAR_DESCRIPTION(DEFINITE));
BeginTemporaryState(PANIC, 500 + RAND() % 500);
}
if(!IsRetreating())
{
if(CheckGround && NearestDistance > 2 && CheckForUsefulItemsOnGround(false))
return true;
SetGoingTo(NearestChar->GetPos());