diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-06-24 15:02:38 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-06-24 15:02:38 -0300 |
| commit | 8bc2e5dc3a523af471065752a534d20d878c60b0 (patch) | |
| tree | d39080ec0f265227fd326588c82742771d370228 /src | |
| parent | bdfcbb2f0edee2679bfe588df55e9f99bd837510 (diff) | |
| download | game-8bc2e5dc3a523af471065752a534d20d878c60b0.tar.gz game-8bc2e5dc3a523af471065752a534d20d878c60b0.zip | |
most of `crnonpl.cc`
Diffstat (limited to 'src')
| -rw-r--r-- | src/cr.hh | 109 | ||||
| -rw-r--r-- | src/crmain.cc | 15 | ||||
| -rw-r--r-- | src/crnonpl.cc | 2110 | ||||
| -rw-r--r-- | src/crplayer.cc | 2 | ||||
| -rw-r--r-- | src/enums.hh | 107 | ||||
| -rw-r--r-- | src/stubs.hh | 2 |
6 files changed, 2321 insertions, 24 deletions
@@ -8,6 +8,9 @@ #include "map.hh" struct TCreature; +struct TMonster; +struct TNPC; +struct TPlayer; // Creature Data // ============================================================================= @@ -620,7 +623,10 @@ struct TCreature: TSkillBase { // TNonPlayer // ============================================================================= struct TNonplayer: TCreature { - //TNonplayer(void); + TNonplayer(void); + ~TNonplayer(void) override; + void SetInList(void); + void DelInList(void); // DATA // ================= @@ -630,46 +636,93 @@ struct TNonplayer: TCreature { // TNPC // ============================================================================= -// TODO(fusion): Most of these are probably contained in `crnonpl.cc` because -// `TNPC` has a pointer to `TBehaviourDatabase`. -struct TNode { +struct TBehaviourNode { int Type; int Data; - TNode *Left; - TNode *Right; + TBehaviourNode *Left; + TBehaviourNode *Right; }; -struct TCondition { +struct TBehaviourCondition { + bool set(int Type, void *Data); + void clear(void); + + // DATA + // ================= int Type; uint32 Text; - TNode *Expression; + TBehaviourNode *Expression; int Property; int Number; }; -struct TAction { +struct TBehaviourAction { + bool set(int Type, void *Data, void *Data2, void *Data3, void *Data4); + void clear(void); + + // DATA + // ================= int Type; uint32 Text; int Number; - TNode *Expression; - TNode *Expression2; - TNode *Expression3; + TBehaviourNode *Expression; + TBehaviourNode *Expression2; + TBehaviourNode *Expression3; }; struct TBehaviour { - vector<TCondition> Condition; - vector<TAction> Action; + TBehaviour(void); + ~TBehaviour(void); + bool addCondition(int Type, void *Data); + bool addAction(int Type, void *Data, void *Data2, void *Data3, void *Data4); + + // TODO(fusion): Same as `TChannel` in `operate.hh`. + TBehaviour(const TBehaviour &Other); + void operator=(const TBehaviour &Other); + + // DATA + // ================= + vector<TBehaviourCondition> Condition; + vector<TBehaviourAction> Action; int Conditions; int Actions; }; struct TBehaviourDatabase { + TBehaviourDatabase(TReadScriptFile *Script); + + // TODO(fusion): These could/should be standalone functions. + TBehaviourNode *readValue(TReadScriptFile *Script); + TBehaviourNode *readFactor(TReadScriptFile *Script); + TBehaviourNode *readTerm(TReadScriptFile *Script); + int evaluate(TNPC *Npc, TBehaviourNode *Node, int *Parameters); + + void react(TNPC *Npc, const char *Text, SITUATION Situation); + + // DATA + // ================= vector<TBehaviour> Behaviour; int Behaviours; }; -struct TNPC { - //TNPC(void); +struct TNPC: TNonplayer { + TNPC(const char *FileName); + void GiveTo(ObjectType Type, int Amount); + void GetFrom(ObjectType Type, int Amount); + void GiveMoney(int Amount); + void GetMoney(int Amount); + void Enqueue(uint32 InterlocutorID, const char *Text); + void TurnToInterlocutor(void); + void ChangeState(STATE NewState, bool Stimulus); + + // VIRTUAL FUNCTIONS + // ================= + ~TNPC(void) override; + bool MovePossible(int x, int y, int z, bool Execute, bool Jump) override; + void TalkStimulus(uint32 SpeakerID, const char *Text) override; + void DamageStimulus(uint32 AttackerID, int Damage, int DamageType) override; + void IdleStimulus(void) override; + void CreatureMoveStimulus(uint32 CreatureID, int Type) override; // DATA // ================= @@ -689,8 +742,24 @@ struct TNPC { // TMonster // ============================================================================= +struct TMonsterhome { + int Race; + int x; + int y; + int z; + int Radius; + int MaxMonsters; + int ActMonsters; + int RegenerationTime; + int Timer; +}; + struct TMonster: TNonplayer { - //TMonster(void); + TMonster(int Race, int x, int y, int z, int Home, uint32 MasterID); + + // VIRTUAL FUNCTIONS + // ================= + ~TMonster(void) override; // DATA //TNonplayer super_TNonplayer; // IMPLICIT @@ -871,6 +940,12 @@ int GetSkillByName(const char *Name); // crnonpl.cc // ============================================================================= +void StartMonsterhomeTimer(int Nr); +void LoadMonsterhomes(void); +void ProcessMonsterhomes(void); +void NotifyMonsterhomeOfDeath(int Nr); +bool MonsterhomeInRange(int Nr, int x, int y, int z); +void ChangeNPCState(TCreature *Npc, int NewState, bool Stimulus); void InitNonplayer(); void ExitNonplayer(); diff --git a/src/crmain.cc b/src/crmain.cc index 6193515..3d45827 100644 --- a/src/crmain.cc +++ b/src/crmain.cc @@ -12,15 +12,17 @@ TRaceData RaceData[MAX_RACES]; priority_queue<uint32, uint32> ToDoQueue(5000, 1000); -static uint32 NextCreatureID; -static int FirstFreeCreature; static TCreature *HashList[1000]; static matrix<uint32> *FirstChainCreature; static vector<TCreature*> CreatureList(0, 10000, 1000, NULL); -static priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100); +static int FirstFreeCreature; +static uint32 NextCreatureID; + static int KilledCreatures[MAX_RACES]; static int KilledPlayers[MAX_RACES]; +static priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100); + // TFindCreatures // ============================================================================= TFindCreatures::TFindCreatures(int RadiusX, int RadiusY, int CenterX, int CenterY, int Mask){ @@ -125,14 +127,19 @@ TCreature::TCreature(void) : this->ID = 0; this->NextHashEntry = NULL; this->NextChainCreature = 0; + this->Name[0] = 0; this->Murderer[0] = 0; + this->OrgOutfit = {}; + this->Outfit = {}; this->startx = 0; this->starty = 0; this->startz = 0; this->posx = 0; this->posy = 0; this->posz = 0; - this->Direction = 0; + this->Sex = 1; + this->Race = 0; + this->Direction = DIRECTION_SOUTH; this->Radius = INT_MAX; this->IsDead = false; this->LoseInventory = LOSE_INVENTORY_ALL; diff --git a/src/crnonpl.cc b/src/crnonpl.cc new file mode 100644 index 0000000..433718a --- /dev/null +++ b/src/crnonpl.cc @@ -0,0 +1,2110 @@ +#include "cr.hh" +#include "config.hh" +#include "containers.hh" +#include "info.hh" +#include "operate.hh" + +#include "stubs.hh" + +static vector<TNonplayer*> NonplayerList(0, 10000, 1000, NULL); +static int FirstFreeNonplayer; + +static vector<TMonsterhome> Monsterhome(1, 5000, 1000); +static int Monsterhomes; + +static store<TBehaviourNode, 256> BehaviourNodeTable; + +// Behaviour Database +// ============================================================================= +static TBehaviourNode *NewBehaviourNode(int Type, TBehaviourNode *Left, TBehaviourNode *Right){ + TBehaviourNode *Node = BehaviourNodeTable.getFreeItem(); + Node->Type = Type; + Node->Data = 0; + Node->Left = Left; + Node->Right = Right; + return Node; +} + +static TBehaviourNode *NewBehaviourNode(int Type, int Data){ + TBehaviourNode *Node = BehaviourNodeTable.getFreeItem(); + Node->Type = Type; + Node->Data = Data; + Node->Left = NULL; + Node->Right = NULL; + return Node; +} + +static void DeleteBehaviourNode(TBehaviourNode *Node){ + if(Node != NULL){ + DeleteBehaviourNode(Node->Left); + DeleteBehaviourNode(Node->Right); + BehaviourNodeTable.putFreeItem(Node); + } +} + +bool TBehaviourCondition::set(int Type, void *Data){ + if(Type != BEHAVIOUR_CONDITION_SHORTCIRCUIT && Data == NULL){ + error("TBehaviourCondition::set: Data ist NULL.\n"); + this->Type = BEHAVIOUR_CONDITION_NONE; + return false; + } + + this->Type = Type; + switch(Type){ + case BEHAVIOUR_CONDITION_TEXT:{ + this->Text = AddDynamicString((const char*)Data); + break; + } + + case BEHAVIOUR_CONDITION_PROPERTY:{ + const char *Property = (const char*)Data; + this->Property = BEHAVIOUR_PROPERTY_NONE; + if(strcmp(Property, "address") == 0){ + this->Property = BEHAVIOUR_PROPERTY_ADDRESS; + }else if(strcmp(Property, "busy") == 0){ + this->Property = BEHAVIOUR_PROPERTY_BUSY; + }else if(strcmp(Property, "vanish") == 0){ + this->Property = BEHAVIOUR_PROPERTY_VANISH; + }else if(strcmp(Property, "male") == 0){ + this->Property = BEHAVIOUR_PROPERTY_MALE; + }else if(strcmp(Property, "female") == 0){ + this->Property = BEHAVIOUR_PROPERTY_FEMALE; + }else if(strcmp(Property, "knight") == 0){ + this->Property = BEHAVIOUR_PROPERTY_KNIGHT; + }else if(strcmp(Property, "paladin") == 0){ + this->Property = BEHAVIOUR_PROPERTY_PALADIN; + }else if(strcmp(Property, "sorcerer") == 0){ + this->Property = BEHAVIOUR_PROPERTY_SORCERER; + }else if(strcmp(Property, "druid") == 0){ + this->Property = BEHAVIOUR_PROPERTY_DRUID; + }else if(strcmp(Property, "premium") == 0){ + this->Property = BEHAVIOUR_PROPERTY_PREMIUM; + }else if(strcmp(Property, "promoted") == 0){ + this->Property = BEHAVIOUR_PROPERTY_PROMOTED; + }else if(strcmp(Property, "pzblock") == 0){ + this->Property = BEHAVIOUR_PROPERTY_PZBLOCK; + }else if(strcmp(Property, "nonpvp") == 0){ + this->Property = BEHAVIOUR_PROPERTY_NONPVP; + }else if(strcmp(Property, "pvpenforced") == 0){ + this->Property = BEHAVIOUR_PROPERTY_PVPENFORCED; + } + + if(this->Property == BEHAVIOUR_PROPERTY_NONE){ + return false; + } + break; + } + + case BEHAVIOUR_CONDITION_PARAMETER:{ + this->Number = *(int*)Data; + break; + } + + case BEHAVIOUR_CONDITION_EXPRESSION:{ + this->Expression = (TBehaviourNode*)Data; + break; + } + + case BEHAVIOUR_CONDITION_SHORTCIRCUIT:{ + // no-op + break; + } + + default:{ + error("TBehaviourCondition::set: Ungültiger Bedingungstyp %d\n", Type); + return false; + } + } + + return true; +} + +void TBehaviourCondition::clear(void){ + if(this->Type == BEHAVIOUR_CONDITION_TEXT){ + DeleteDynamicString(this->Text); + }else if(this->Type == BEHAVIOUR_CONDITION_EXPRESSION){ + DeleteBehaviourNode(this->Expression); + } + + this->Type = BEHAVIOUR_CONDITION_NONE; +} + +bool TBehaviourAction::set(int Type, void *Data, void *Data2, void *Data3, void *Data4){ + this->Type = Type; + switch(Type){ + case BEHAVIOUR_ACTION_REPLY:{ + this->Text = AddDynamicString((const char*)Data); + break; + } + + case BEHAVIOUR_ACTION_SET_VARIABLE: + case BEHAVIOUR_ACTION_SET_SKILL: + case BEHAVIOUR_ACTION_FUNCTION1:{ + this->Number = *(int*)Data; + this->Expression = (TBehaviourNode*)Data2; + break; + } + + case BEHAVIOUR_ACTION_FUNCTION2: + case BEHAVIOUR_ACTION_SET_SKILL_TIMER:{ + this->Number = *(int*)Data; + this->Expression = (TBehaviourNode*)Data2; + this->Expression2 = (TBehaviourNode*)Data3; + break; + } + + case BEHAVIOUR_ACTION_FUNCTION0: + case BEHAVIOUR_ACTION_CHANGESTATE:{ + this->Number = *(int*)Data; + break; + } + + case BEHAVIOUR_ACTION_REPEAT:{ + // no-op + break; + } + + case BEHAVIOUR_ACTION_FUNCTION3:{ + this->Number = *(int*)Data; + this->Expression = (TBehaviourNode*)Data2; + this->Expression2 = (TBehaviourNode*)Data3; + this->Expression3 = (TBehaviourNode*)Data4; + break; + } + + default:{ + error("TAction::set: Ungültiger Aktionstyp %d\n", Type); + return false; + } + } + + return true; +} + +void TBehaviourAction::clear(void){ + switch(this->Type){ + case BEHAVIOUR_ACTION_REPLY:{ + DeleteDynamicString(this->Text); + break; + } + + case BEHAVIOUR_ACTION_SET_VARIABLE: + case BEHAVIOUR_ACTION_SET_SKILL: + case BEHAVIOUR_ACTION_FUNCTION1:{ + DeleteBehaviourNode(this->Expression); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION2: + case BEHAVIOUR_ACTION_SET_SKILL_TIMER:{ + DeleteBehaviourNode(this->Expression); + DeleteBehaviourNode(this->Expression2); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION3:{ + DeleteBehaviourNode(this->Expression); + DeleteBehaviourNode(this->Expression2); + DeleteBehaviourNode(this->Expression3); + break; + } + + default:{ + break; + } + } + + this->Type = BEHAVIOUR_ACTION_NONE; +} + +TBehaviour::TBehaviour(void) : + Condition(0, 5, 5), + Action(0, 5, 5) +{ + this->Conditions = 0; + this->Actions = 0; +} + +TBehaviour::~TBehaviour(void){ + for(int i = 0; i < this->Conditions; i += 1){ + this->Condition.at(i)->clear(); + } + + for(int i = 0; i < this->Actions; i += 1){ + this->Action.at(i)->clear(); + } +} + +bool TBehaviour::addCondition(int Type, void *Data){ + bool Result = this->Condition.at(this->Conditions)->set(Type, Data); + if(Result){ + this->Conditions += 1; + } + return Result; +} + +bool TBehaviour::addAction(int Type, void *Data, void *Data2, void *Data3, void *Data4){ + bool Result = this->Action.at(this->Actions)->set(Type, Data, Data2, Data3, Data4); + if(Result){ + this->Actions += 1; + } + return Result; +} + +TBehaviour::TBehaviour(const TBehaviour &Other) : + TBehaviour() +{ + this->operator=(Other); +} + +void TBehaviour::operator=(const TBehaviour &Other){ + this->Conditions = Other.Conditions; + for(int i = 0; i < Other.Conditions; i += 1){ + *this->Condition.at(i) = Other.Condition.copyAt(i); + } + + this->Actions = Other.Actions; + for(int i = 0; i < Other.Actions; i += 1){ + *this->Action.at(i) = Other.Action.copyAt(i); + } +} + + +TBehaviourDatabase::TBehaviourDatabase(TReadScriptFile *Script) : + Behaviour(0, 50, 25) +{ + this->Behaviours = 0; + Script->readSymbol('{'); + Script->nextToken(); + while(Script->Token != SPECIAL || Script->getSpecial() != '}'){ + TBehaviour *Behaviour = this->Behaviour.at(this->Behaviours); + + // NOTE(fusion): Optional conditions. + if(Script->Token != SPECIAL || Script->getSpecial() != 'I'){ + while(true){ + bool Ok = false; + if(Script->Token == STRING){ + Ok = Behaviour->addCondition(BEHAVIOUR_CONDITION_TEXT, Script->getString()); + }else if(Script->Token == IDENTIFIER){ + Ok = Behaviour->addCondition(BEHAVIOUR_CONDITION_PROPERTY, Script->getIdentifier()); + }else if(Script->Token == SPECIAL){ + if(Script->getSpecial() == '!'){ + Ok = Behaviour->addCondition(BEHAVIOUR_CONDITION_SHORTCIRCUIT, NULL); + }else if(Script->getSpecial() == '%'){ + int Parameter = Script->readNumber(); + if(Parameter != 1 && Parameter != 2){ + Script->error("illegal ordinal number"); + } + Ok = Behaviour->addCondition(BEHAVIOUR_CONDITION_PARAMETER, &Parameter); + } + } + + if(!Ok){ + TBehaviourNode *Left = this->readTerm(Script); + Script->nextToken(); + if(Script->Token != SPECIAL){ + Script->error("relational operator expected"); + } + + int Operator = BEHAVIOUR_NODE_NONE; + switch(Script->getSpecial()){ + case '<': Operator = BEHAVIOUR_NODE_CMP_LT; break; + case '>': Operator = BEHAVIOUR_NODE_CMP_GT; break; + case '=': Operator = BEHAVIOUR_NODE_CMP_EQ; break; + case 'N': Operator = BEHAVIOUR_NODE_CMP_NEQ; break; + case 'L': Operator = BEHAVIOUR_NODE_CMP_LE; break; + case 'G': Operator = BEHAVIOUR_NODE_CMP_GE; break; + default:{ + Script->error("relational operator expected"); + break; + } + } + + Script->nextToken(); + TBehaviourNode *Right = this->readTerm(Script); + Behaviour->addCondition(BEHAVIOUR_CONDITION_EXPRESSION, + NewBehaviourNode(Operator, Right, Left)); + } + + Script->nextToken(); + if(Script->Token == SPECIAL && Script->getSpecial() == ','){ + Script->nextToken(); + }else{ + break; + } + } + } + + if(Script->Token != SPECIAL || Script->getSpecial() != 'I'){ + Script->error("'->' expected"); + } + + // NOTE(fusion): Required Actions. + Script->nextToken(); + while(true){ + if(Script->Token == STRING){ + Behaviour->addAction(BEHAVIOUR_ACTION_REPLY, Script->getString(), NULL, NULL, NULL); + }else if(Script->Token == IDENTIFIER){ + int Type = -1; + int Data = 0; + const char *Identifier = Script->getIdentifier(); + if(strcmp(Identifier, "topic") == 0){ + Type = BEHAVIOUR_ACTION_SET_VARIABLE; + Data = 1; // BEHAVIOUR_VARIABLE_TOPIC ? + }else if(strcmp(Identifier, "price") == 0){ + Type = BEHAVIOUR_ACTION_SET_VARIABLE; + Data = 2; // BEHAVIOUR_VARIABLE_PRICE ? + }else if(strcmp(Identifier, "amount") == 0){ + Type = BEHAVIOUR_ACTION_SET_VARIABLE; + Data = 3; // BEHAVIOUR_VARIABLE_AMOUNT ? + }else if(strcmp(Identifier, "type") == 0){ + Type = BEHAVIOUR_ACTION_SET_VARIABLE; + Data = 4; // BEHAVIOUR_VARIABLE_TYPE + }else if(strcmp(Identifier, "data") == 0){ + Type = BEHAVIOUR_ACTION_SET_VARIABLE; + Data = 6; // BEHAVIOUR_VARIABLE_DATA + }else if(strcmp(Identifier, "hp") == 0){ + Type = BEHAVIOUR_ACTION_SET_SKILL; + Data = SKILL_HITPOINTS; + }else if(strcmp(Identifier, "poison") == 0){ + Type = BEHAVIOUR_ACTION_SET_SKILL_TIMER; + Data = SKILL_POISON; + }else if(strcmp(Identifier, "burning") == 0){ + Type = BEHAVIOUR_ACTION_SET_SKILL_TIMER; + Data = SKILL_BURNING; + }else if(strcmp(Identifier, "setquestvalue") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION2; + Data = 3; // BEHAVIOUR_FUNCTION2_SETQUESTVALUE ? + }else if(strcmp(Identifier, "effectme") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 1; // BEHAVIOUR_FUNCTION1_EFFECTME ? + }else if(strcmp(Identifier, "effectopp") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 2; // BEHAVIOUR_FUNCTION1_EFFECTOPP ? + }else if(strcmp(Identifier, "profession") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 3; // BEHAVIOUR_FUNCTION1_SETPROFESSION ? + }else if(strcmp(Identifier, "teachspell") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 4; // BEHAVIOUR_FUNCTION1_TEACHSPELL ? + }else if(strcmp(Identifier, "summon") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 5; // BEHAVIOUR_FUNCTION1_SUMMON ? + }else if(strcmp(Identifier, "create") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 6; // BEHAVIOUR_FUNCTION1_CREATE ? + }else if(strcmp(Identifier, "delete") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION1; + Data = 7; // BEHAVIOUR_FUNCTION1_DELETE ? + }else if(strcmp(Identifier, "createmoney") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION0; + Data = 1; // BEHAVIOUR_FUNCTION0_CREATEMONEY ? + }else if(strcmp(Identifier, "deletemoney") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION0; + Data = 2; // BEHAVIOUR_FUNCTION0_DELETEMONEY ? + }else if(strcmp(Identifier, "queue") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION0; + Data = 3; // BEHAVIOUR_FUNCTION0_ENQUEUE ? + }else if(strcmp(Identifier, "teleport") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION3; + Data = 1; // BEHAVIOUR_FUNCTION3_TELEPORT ? + }else if(strcmp(Identifier, "startposition") == 0){ + Type = BEHAVIOUR_ACTION_FUNCTION3; + Data = 2; // BEHAVIOUR_FUNCTION3_SETSTART ? + }else if(strcmp(Identifier, "idle") == 0){ + Type = BEHAVIOUR_ACTION_CHANGESTATE; + Data = IDLE; + }else if(strcmp(Identifier, "nop") == 0){ + Type = BEHAVIOUR_ACTION_NONE; + Data = 0; + } + + switch(Type){ + case BEHAVIOUR_ACTION_NONE:{ + // no-op + break; + } + + case BEHAVIOUR_ACTION_SET_VARIABLE: + case BEHAVIOUR_ACTION_SET_SKILL:{ + Script->readSymbol('='); + Script->nextToken(); + TBehaviourNode *Value = this->readTerm(Script); + + Behaviour->addAction(Type, &Data, Value, NULL, NULL); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION1:{ + Script->readSymbol('('); + Script->nextToken(); + TBehaviourNode *Param = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Behaviour->addAction(Type, &Data, Param, NULL, NULL); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION2: + case BEHAVIOUR_ACTION_SET_SKILL_TIMER:{ + Script->readSymbol('('); + + Script->nextToken(); + TBehaviourNode *Param1 = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ','){ + Script->error(", expected"); + } + + Script->nextToken(); + TBehaviourNode *Param2 = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Behaviour->addAction(Type, &Data, Param1, Param2, NULL); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION0: + case BEHAVIOUR_ACTION_CHANGESTATE:{ + Behaviour->addAction(Type, &Data, NULL, NULL, NULL); + break; + } + + case BEHAVIOUR_ACTION_FUNCTION3:{ + Script->readSymbol('('); + + Script->nextToken(); + TBehaviourNode *Param1 = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ','){ + Script->error(", expected"); + } + + Script->nextToken(); + TBehaviourNode *Param2 = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ','){ + Script->error(", expected"); + } + + Script->nextToken(); + TBehaviourNode *Param3 = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Behaviour->addAction(Type, &Data, Param1, Param2, Param3); + break; + } + + default:{ + Script->error("unknown identifier"); + break; + } + } + }else if(Script->Token == SPECIAL && Script->getSpecial() == '*'){ + if(this->Behaviours == 0){ + Script->error("no previous pattern"); + } + Behaviour->addAction(BEHAVIOUR_ACTION_REPEAT, NULL, NULL, NULL, NULL); + }else{ + Script->error("illegal action"); + } + + Script->nextToken(); + if(Script->Token == SPECIAL && Script->getSpecial() == ','){ + Script->nextToken(); + }else{ + break; + } + } + + this->Behaviours += 1; + } +} + +TBehaviourNode *TBehaviourDatabase::readValue(TReadScriptFile *Script){ + TBehaviourNode *Node = NULL; + if(Script->Token == NUMBER){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_NUMBER, Script->readNumber()); + }else if(Script->Token == IDENTIFIER){ + const char *Identifier = Script->getIdentifier(); + if(strcmp(Identifier, "topic") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_TOPIC, 0); + }else if(strcmp(Identifier, "price") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_PRICE, 0); + }else if(strcmp(Identifier, "amount") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_AMOUNT, 0); + }else if(strcmp(Identifier, "level") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_SKILL, SKILL_LEVEL); + }else if(strcmp(Identifier, "magiclevel") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_SKILL, SKILL_MAGIC_LEVEL); + }else if(strcmp(Identifier, "hp") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_SKILL, SKILL_HITPOINTS); + }else if(strcmp(Identifier, "poison") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_SKILL, SKILL_POISON); + }else if(strcmp(Identifier, "burning") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_SKILL, SKILL_BURNING); + }else if(strcmp(Identifier, "count") == 0){ + Script->readSymbol('('); + Script->nextToken(); + TBehaviourNode *Left = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Node = NewBehaviourNode(BEHAVIOUR_NODE_COUNT, Left, NULL); + }else if(strcmp(Identifier, "countmoney") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_COUNTMONEY, 0); + }else if(strcmp(Identifier, "type") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_TYPE, 0); + }else if(strcmp(Identifier, "data") == 0){ + Node = NewBehaviourNode(BEHAVIOUR_NODE_DATA, 0); + }else if(strcmp(Identifier, "spellknown") == 0){ + Script->readSymbol('('); + Script->nextToken(); + TBehaviourNode *Left = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Node = NewBehaviourNode(BEHAVIOUR_NODE_SPELLKNOWN, Left, NULL); + }else if(strcmp(Identifier, "spelllevel") == 0){ + Script->readSymbol('('); + Script->nextToken(); + TBehaviourNode *Left = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Node = NewBehaviourNode(BEHAVIOUR_NODE_SPELLLEVEL, Left, NULL); + }else if(strcmp(Identifier, "random") == 0){ + Script->readSymbol('('); + + Script->nextToken(); + TBehaviourNode *Left = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ','){ + Script->error(", expected"); + } + + Script->nextToken(); + TBehaviourNode *Right = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Node = NewBehaviourNode(BEHAVIOUR_NODE_RANDOM, Left, Right); + }else if(strcmp(Identifier, "questvalue") == 0){ + Script->readSymbol('('); + Script->nextToken(); + TBehaviourNode *Left = this->readTerm(Script); + if(Script->Token != SPECIAL || Script->getSpecial() != ')'){ + Script->error(") expected"); + } + + Node = NewBehaviourNode(BEHAVIOUR_NODE_QUESTVALUE, Left, NULL); + } + }else if(Script->Token == SPECIAL){ + if(Script->getSpecial() != '%'){ + Script->error("illegal character"); + } + Node = NewBehaviourNode(BEHAVIOUR_NODE_PARAMETER, Script->readNumber()); + }else{ + Script->error("illegal value"); + } + + if(Node == NULL){ + Script->error("unknown value"); + } + + Script->nextToken(); + return Node; +} + +TBehaviourNode *TBehaviourDatabase::readFactor(TReadScriptFile *Script){ + TBehaviourNode *Node = this->readValue(Script); + while(Script->Token == SPECIAL && Script->getSpecial() == '*'){ + Script->nextToken(); + Node = NewBehaviourNode(BEHAVIOUR_NODE_MUL, Node, this->readValue(Script)); + } + return Node; +} + +TBehaviourNode *TBehaviourDatabase::readTerm(TReadScriptFile *Script){ + TBehaviourNode *Node = this->readFactor(Script); + while(Script->Token == SPECIAL){ + int Type = BEHAVIOUR_NODE_NONE; + if(Script->getSpecial() == '+'){ + Type = BEHAVIOUR_NODE_ADD; + }else if(Script->getSpecial() == '-'){ + Type = BEHAVIOUR_NODE_SUB; + }else{ + break; + } + + Script->nextToken(); + Node = NewBehaviourNode(Type, Node, this->readFactor(Script)); + } + return Node; +} + +int TBehaviourDatabase::evaluate(TNPC *Npc, TBehaviourNode *Node, int *Parameters){ + if(Npc == NULL){ + error("TBehaviourDatabase::evaluate: NPC existiert nicht.\n"); + return 0; + } + + if(Node == NULL){ + error("TBehaviourDatabase::evaluate: Knoten existiert nicht.\n"); + return 0; + } + + if(Parameters == NULL){ + error("TBehaviourDatabase::evaluate: Zahlen existieren nicht.\n"); + return 0; + } + + uint32 InterlocutorID = Npc->Interlocutor; + TPlayer *Interlocutor = GetPlayer(InterlocutorID); + if(Interlocutor == NULL){ + error("TBehaviourDatabase::evaluate: Gesprächspartner existiert nicht.\n"); + return 0; + } + + int Result = 0; + switch(Node->Type){ + case BEHAVIOUR_NODE_CMP_LT:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left < Right; + break; + } + + case BEHAVIOUR_NODE_CMP_GT:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left > Right; + break; + } + + case BEHAVIOUR_NODE_CMP_EQ:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left == Right; + break; + } + + case BEHAVIOUR_NODE_CMP_NEQ:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left != Right; + break; + } + + case BEHAVIOUR_NODE_CMP_LE:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left <= Right; + break; + } + + case BEHAVIOUR_NODE_CMP_GE:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left >= Right; + break; + } + + case BEHAVIOUR_NODE_ADD:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left + Right; + break; + } + + case BEHAVIOUR_NODE_SUB:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left - Right; + break; + } + + case BEHAVIOUR_NODE_MUL:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = Left * Right; + break; + } + + case BEHAVIOUR_NODE_PARAMETER:{ + if(Node->Data != 1 && Node->Data != 2){ + error("TBehaviourDatabase::evaluate: Ungültiger Zahl-Parameter %d.\n", Node->Data); + }else if(Parameters[Node->Data - 1] < 0){ + error("TBehaviourDatabase::evaluate: Zahl-Parameter %d nicht belegt.\n", Node->Data); + }else{ + Result = Parameters[Node->Data - 1]; + } + break; + } + + case BEHAVIOUR_NODE_NUMBER:{ + Result = Node->Data; + break; + } + + case BEHAVIOUR_NODE_TOPIC:{ + Result = Npc->Topic; + break; + } + + case BEHAVIOUR_NODE_PRICE:{ + Result = Npc->Price; + break; + } + + case BEHAVIOUR_NODE_AMOUNT:{ + Result = Npc->Amount; + break; + } + + case BEHAVIOUR_NODE_SKILL:{ + int SkillNr = Node->Data; + if(SkillNr == SKILL_LEVEL + || SkillNr == SKILL_MAGIC_LEVEL + || SkillNr == SKILL_HITPOINTS){ + Result = Interlocutor->Skills[SkillNr]->Get(); + }else if(SkillNr == SKILL_POISON + || SkillNr == SKILL_BURNING){ + Result = Interlocutor->Skills[SkillNr]->TimerValue(); + }else{ + error("TBehaviourDatabase::evaluate: Ungültiger Skill %d.\n", SkillNr); + } + break; + } + + case BEHAVIOUR_NODE_COUNT:{ + int TypeID = this->evaluate(Npc, Node->Left, Parameters); + Result = CountInventoryObjects(InterlocutorID, TypeID, Npc->Data); + break; + } + + case BEHAVIOUR_NODE_COUNTMONEY:{ + Result = CountInventoryMoney(InterlocutorID); + break; + } + + case BEHAVIOUR_NODE_TYPE:{ + Result = Npc->TypeID; + break; + } + + case BEHAVIOUR_NODE_DATA:{ + Result = Npc->Data; + break; + } + + case BEHAVIOUR_NODE_SPELLKNOWN:{ + int SpellNr = this->evaluate(Npc, Node->Left, Parameters); + Result = Interlocutor->SpellKnown(SpellNr); + break; + } + + case BEHAVIOUR_NODE_SPELLLEVEL:{ + int SpellNr = this->evaluate(Npc, Node->Left, Parameters); + Result = GetSpellLevel(SpellNr); + break; + } + + case BEHAVIOUR_NODE_RANDOM:{ + int Left = this->evaluate(Npc, Node->Left, Parameters); + int Right = this->evaluate(Npc, Node->Right, Parameters); + Result = random(Left, Right); + break; + } + + case BEHAVIOUR_NODE_QUESTVALUE:{ + int QuestNr = this->evaluate(Npc, Node->Left, Parameters); + Result = Interlocutor->GetQuestValue(QuestNr); + break; + } + + default:{ + error("TBehaviourDatabase::evaluate: Ungültiger Knotentyp %d.\n", Node->Type); + break; + } + } + return Result; +} + +// NOTE(fusion): These smaller functions were inside `TBehaviourDatabase::react` +// but I figured it would be better to pull them out for readability. +static bool CheckBehaviourProperty(int Property, SITUATION Situation, TPlayer *Interlocutor){ + if(Interlocutor == NULL){ + error("CheckBehaviourProperty: Interlocutor ist NULL."); + return false; + } + + bool Result = false; + switch(Property){ + case BEHAVIOUR_PROPERTY_ADDRESS: + Result = (Situation == ADDRESS || Situation == ADDRESSQUEUE); + break; + case BEHAVIOUR_PROPERTY_BUSY: + Result = (Situation == BUSY); + break; + case BEHAVIOUR_PROPERTY_VANISH: + Result = (Situation == VANISH); + break; + case BEHAVIOUR_PROPERTY_MALE: + Result = (Interlocutor->Sex == 1); + break; + case BEHAVIOUR_PROPERTY_FEMALE: + Result = (Interlocutor->Sex == 2); + break; + case BEHAVIOUR_PROPERTY_KNIGHT: + Result = (Interlocutor->GetEffectiveProfession() == PROFESSION_KNIGHT); + break; + case BEHAVIOUR_PROPERTY_PALADIN: + Result = (Interlocutor->GetEffectiveProfession() == PROFESSION_PALADIN); + break; + case BEHAVIOUR_PROPERTY_SORCERER: + Result = (Interlocutor->GetEffectiveProfession() == PROFESSION_SORCERER); + break; + case BEHAVIOUR_PROPERTY_DRUID: + Result = (Interlocutor->GetEffectiveProfession() == PROFESSION_DRUID); + break; + case BEHAVIOUR_PROPERTY_PREMIUM: + Result = CheckRight(Interlocutor->ID, PREMIUM_ACCOUNT); + break; + case BEHAVIOUR_PROPERTY_PROMOTED: + Result = Interlocutor->GetActivePromotion(); + break; + case BEHAVIOUR_PROPERTY_PZBLOCK: + Result = (Interlocutor->EarliestProtectionZoneRound > RoundNr); + break; + case BEHAVIOUR_PROPERTY_NONPVP: + Result = (WorldType == NON_PVP); + break; + case BEHAVIOUR_PROPERTY_PVPENFORCED: + Result = (WorldType == PVP_ENFORCED); + break; + } + + return Result; +} + +static bool FormatNpcResponse(char *Buffer, int BufferSize, + const char *Template, TNPC *Npc, TPlayer *Interlocutor){ + if(Buffer == NULL || BufferSize <= 0){ + error("FormatNpcResponse: Invalid response buffer."); + return false; + } + + if(Template == NULL || Template[0] == 0){ + error("FormatNpcResponse: Template is NULL or empty."); + return false; + } + + if(Npc == NULL){ + error("FormatNpcResponse: Npc is NULL.\n"); + return false; + } + + if(Interlocutor == NULL){ + error("FormatNpcResponse: Interlocutor is NULL.\n"); + return false; + } + + int WritePos = 0; + int ReadPos = 0; + while(Template[ReadPos] != 0){ + if(Template[ReadPos] == '%' && Template[ReadPos + 1] != 0){ + char Help[50] = {}; + switch(Template[ReadPos + 1]){ + case 'N': strcpy(Help, Interlocutor->Name); break; + case 'A': sprintf(Help, "%d", Npc->Amount); break; + case 'P': sprintf(Help, "%d", Npc->Price); break; + case 'T':{ + int Hour, Minute; + GetTime(&Hour, &Minute); + if(Hour < 12){ + sprintf(Help, "%d:%.2d am", Hour, Minute); + }else{ + sprintf(Help, "%d:%.2d pm", (Hour - 12), Minute); + } + break; + } + } + + // NOTE(fusion): Make sure we don't overflow the buffer. + int HelpLen = strlen(Help); + if(HelpLen > 0){ + if((WritePos + HelpLen) > BufferSize){ + HelpLen = BufferSize - WritePos; + } + + if(HelpLen > 0){ + memcpy(&Buffer[WritePos], Help, HelpLen); + WritePos += HelpLen; + } + } + + ReadPos += 2; + }else{ + // NOTE(fusion): Make sure we don't overflow the buffer. + if(WritePos < BufferSize){ + Buffer[WritePos] = Template[ReadPos]; + WritePos += 1; + } + + ReadPos += 1; + } + } + + if(WritePos < BufferSize){ + Buffer[WritePos] = 0; + return true; + }else{ + Buffer[BufferSize - 1] = 0; + return false; + } +} + +void TBehaviourDatabase::react(TNPC *Npc, const char *Text, SITUATION Situation){ + if(Npc == NULL){ + error("TBehaviourDatabase::react: NPC existiert nicht.\n"); + return; + } + + if(Text == NULL){ + error("TBehaviourDatabase::react: Übergebener Text existiert nicht.\n"); + return; + } + + uint32 InterlocutorID = Npc->Interlocutor; + TPlayer *Interlocutor = GetPlayer(InterlocutorID); + if(Interlocutor == NULL){ + error("TBehaviourDatabase::react: Gesprächspartner existiert nicht" + " (Text=%s, Situation=%d).\n", Text, Situation); + return; + } + + int Parameters[2] = {-1, -1}; + int BestMatch = -1; + int MaxConditions = -1; + for(int BehaviourNr = 0; + BehaviourNr < this->Behaviours; + BehaviourNr += 1){ + TBehaviour *Behaviour = this->Behaviour.at(BehaviourNr); + bool Match = true; + bool ShortCircuit = false; + const char *TextPtr = Text; + for(int ConditionNr = 0; + ConditionNr < Behaviour->Conditions && Match; + ConditionNr += 1){ + TBehaviourCondition *Condition = Behaviour->Condition.at(ConditionNr); + if(Condition->Type == BEHAVIOUR_CONDITION_SHORTCIRCUIT){ + ShortCircuit = true; + break; + } + + switch(Condition->Type){ + case BEHAVIOUR_CONDITION_TEXT:{ + const char *Pattern = GetDynamicString(Condition->Text); + const char *Word = SearchForWord(Pattern, TextPtr); + if(Word != NULL){ + TextPtr = Word + strlen(Pattern); + }else{ + Match = false; + } + break; + } + + case BEHAVIOUR_CONDITION_PROPERTY:{ + if(!CheckBehaviourProperty(Condition->Property, Situation, Interlocutor)){ + Match = false; + } + break; + } + + case BEHAVIOUR_CONDITION_PARAMETER:{ + // TODO(fusion): The original function wouldn't check before + // writing to `Parameters` which could be a problem. + const char *Parameter = SearchForNumber(Condition->Number, TextPtr); + if(Parameter != NULL + && Condition->Number >= 1 + && Condition->Number <= NARRAY(Parameters)){ + Parameters[Condition->Number - 1] = atoi(Parameter); + if(Parameters[Condition->Number - 1] > 500){ + Parameters[Condition->Number - 1] = 500; + } + + // TODO(fusion): This could be problematic if the number + // has more than one digit. + TextPtr = Parameter + 1; + }else{ + Match = false; + } + break; + } + + case BEHAVIOUR_CONDITION_EXPRESSION:{ + if(this->evaluate(Npc, Condition->Expression, Parameters) == 0){ + Match = false; + } + break; + } + + case BEHAVIOUR_CONDITION_SHORTCIRCUIT:{ + // TODO(fusion): Select current and stop loop. + break; + } + } + } + + if(ShortCircuit || (Match && Behaviour->Conditions > MaxConditions)){ + BestMatch = BehaviourNr; + MaxConditions = Behaviour->Conditions; + if(ShortCircuit){ + break; + } + } + } + + if(BestMatch == -1){ + return; + } + + if(Situation != BUSY){ + Npc->Topic = 0; + } + + bool Repeat = false; + bool StartToDo = false; + int TalkDelay = 1000; + int BehaviourNr = BestMatch; + do{ + Repeat = false; + TBehaviour *Behaviour = this->Behaviour.at(BehaviourNr); + for(int ActionNr = 0; + ActionNr < Behaviour->Actions; + ActionNr += 1){ + TBehaviourAction *Action = Behaviour->Action.at(ActionNr); + if(Action->Type == BEHAVIOUR_ACTION_REPEAT){ + if(BehaviourNr > 0){ + BehaviourNr -= 1; + Repeat = true; + }else{ + error("TBehaviourDatabase::react (9): Kein vorheriges Muster.\n"); + } + break; + } + + switch(Action->Type){ + case BEHAVIOUR_ACTION_REPLY:{ + char Response[256] = {}; + const char *Template = GetDynamicString(Action->Text); + if(FormatNpcResponse(Response, sizeof(Response), Template, Npc, Interlocutor)){ + Npc->ToDoWait(TalkDelay); + Npc->ToDoTalk(TALK_SAY, NULL, Response, false); + TalkDelay += 3100 + (int)strlen(Response) * 100; + StartToDo = true; + }else{ + Response[20] = 0; + error("TBehaviourDatabase::react: Text von NPC %s wird zu lang (%s...).\n", + Npc->Name, Response); + } + break; + } + + case BEHAVIOUR_ACTION_SET_VARIABLE:{ + int Variable = Action->Number; + int Value = this->evaluate(Npc, Action->Expression, Parameters); + switch(Variable){ + case 1: Npc->Topic = Value; break; + case 2: Npc->Price = Value; break; + case 3: Npc->Amount = Value; break; + case 4: Npc->TypeID = Value; break; + case 6: Npc->Data = Value; break; + default:{ + error("TBehaviourDatabase::react: Ungültige Variable.\n"); + break; + } + } + break; + } + + case BEHAVIOUR_ACTION_SET_SKILL:{ + int SkillNr = Action->Number; + int Value = this->evaluate(Npc, Action->Expression, Parameters); + if(SkillNr == SKILL_HITPOINTS){ + Interlocutor->Skills[SKILL_HITPOINTS]->Set(Value); + if(Interlocutor->Skills[SKILL_HITPOINTS]->Get() <= 0){ + error("TBehaviourDatabase::react: NPC %s tötet Spieler.\n", Npc->Name); + } + }else{ + error("TBehaviourDatabase::react: Ungültiger Skill.\n"); + } + break; + } + + case BEHAVIOUR_ACTION_FUNCTION2:{ + int FunctionNr = Action->Number; + int Param1 = this->evaluate(Npc, Action->Expression, Parameters); + int Param2 = this->evaluate(Npc, Action->Expression2, Parameters); + switch(FunctionNr){ + case 3: Interlocutor->SetQuestValue(Param1, Param2); break; + default:{ + error("TBehaviourDatabase::react (4): Ungültige Funktionsnummer.\n"); + break; + } + } + break; + } + + case BEHAVIOUR_ACTION_FUNCTION1:{ + int FunctionNr = Action->Number; + int Param = this->evaluate(Npc, Action->Expression, Parameters); + switch(FunctionNr){ + case 1: GraphicalEffect(Npc->CrObject, Param); break; + case 2: GraphicalEffect(Interlocutor->CrObject, Param); break; + case 3: Interlocutor->SetProfession(Param); break; + case 4: Interlocutor->LearnSpell(Param); break; + case 5: CreateMonster(Param, Npc->posx, Npc->posy, Npc->posz, 0, 0, true); break; + case 6: Npc->GiveTo(Param, Npc->Amount); break; + case 7: Npc->GetFrom(Param, Npc->Amount); break; + default:{ + error("TBehaviourDatabase::react (5): Ungültige Funktionsnummer.\n"); + break; + } + } + break; + } + + case BEHAVIOUR_ACTION_FUNCTION0:{ + int FunctionNr = Action->Number; + switch(FunctionNr){ + case 1: Npc->GiveMoney(Npc->Price); break; + case 2: Npc->GetMoney(Npc->Price); break; + case 3:{ + if(Situation == BUSY){ + Npc->Enqueue(InterlocutorID, Text); + }else{ + error("TBehaviourDatabase::react (6): falsche Situation für Aktion \"Queue\".\n"); + } + break; + } + default:{ + error("TBehaviourDatabase::react (6): Ungültige Funktionsnummer.\n"); + break; + } + } + break; + } + + case BEHAVIOUR_ACTION_CHANGESTATE:{ + int NewState = Action->Number; + if(!StartToDo){ + Npc->ChangeState((STATE)NewState, false); + if(Situation != ADDRESSQUEUE){ + StartToDo = true; + }else{ + error("TBehaviourDatabase::react: NPC %s reagiert nicht auf Anrede %s.\n", + Npc->Name, Text); + } + }else{ + if(NewState == IDLE){ + Npc->ChangeState(LEAVING, false); + } + Npc->ToDoChangeState(NewState); + } + break; + } + + case BEHAVIOUR_ACTION_SET_SKILL_TIMER:{ + int SkillNr = Action->Number; + int Cycle = this->evaluate(Npc, Action->Expression, Parameters); + int MaxCount = this->evaluate(Npc, Action->Expression2, Parameters); + if(SkillNr == SKILL_POISON || SkillNr == SKILL_BURNING){ + if(Cycle == 0 || Interlocutor->Skills[SkillNr]->TimerValue() < Cycle){ + Interlocutor->SetTimer(SkillNr, Cycle, MaxCount, MaxCount, -1); + if(SkillNr == SKILL_POISON){ + Interlocutor->PoisonDamageOrigin = 0; + }else{ + Interlocutor->FireDamageOrigin = 0; + } + } + }else{ + error("TBehaviourDatabase::react (8): Ungültiger Skill.\n"); + } + break; + } + + case BEHAVIOUR_ACTION_FUNCTION3:{ + int FunctionNr = Action->Number; + int Param1 = this->evaluate(Npc, Action->Expression, Parameters); + int Param2 = this->evaluate(Npc, Action->Expression2, Parameters); + int Param3 = this->evaluate(Npc, Action->Expression3, Parameters); + switch(FunctionNr){ + case 1:{ + print(3, "NPC teleportiert Gesprächspartner nach [%d,%d,%d].\n", + Param1, Param2, Param3); + try{ + Object Dest = GetMapContainer(Param1, Param2, Param3); + Move(0, Interlocutor->CrObject, Dest, -1, false, NONE); + }catch(RESULT r){ + error("TBehaviourDatabase::react (10): Exception %d.\n", r); + } + break; + } + + case 2:{ + print(3, "NPC setzt Startkoordinate für Gesprächspartner auf [%d,%d,%d].\n", + Param1, Param2, Param3); + Interlocutor->startx = Param1; + Interlocutor->starty = Param2; + Interlocutor->startz = Param3; + Interlocutor->SaveData(); + break; + } + + default:{ + error("TBehaviourDatabase::react (10): Ungültige Unternummer.\n"); + break; + } + } + break; + } + } + } + }while(Repeat); + + if(StartToDo){ + Npc->ToDoWait(TalkDelay); + Npc->ToDoStart(); + if(Situation != BUSY){ + Npc->LastTalk = (TalkDelay / 1000) + RoundNr; + } + } +} + +// Monster Homes +// ============================================================================= +void StartMonsterhomeTimer(int Nr){ + if(Nr < 1 || Nr > Monsterhomes){ + error("StartMonsterhomeTimer: Ungültige Monsterhome-Nummer %d.\n", Nr); + return; + } + + TMonsterhome *MH = Monsterhome.at(Nr); + if(MH->Timer > 0){ + error("StartMonsterhomeTimer: Zähler läuft schon.\n"); + return; + } + + if(MH->ActMonsters >= MH->MaxMonsters){ + error("StartMonsterhomeTimer: Maximale Monsterzahl schon erreicht.\n"); + error("# Monsterhome mit Rasse %d an [%d,%d,%d]\n", MH->Race, MH->x, MH->y, MH->z); + return; + } + + int MaxTimer = MH->RegenerationTime; + int NumPlayers = GetNumberOfPlayers(); + if(NumPlayers > 800){ + MaxTimer = (MaxTimer * 2) / 5; + }else if(NumPlayers > 200){ + MaxTimer = (MaxTimer * 200) / ((NumPlayers / 2) + 100); + } + + MH->Timer = random(MaxTimer / 2, MaxTimer); +} + +void LoadMonsterhomes(void){ + print(1, "Initialisiere Monsterhomes ...\n"); + + char FileName[4096]; + snprintf(FileName, sizeof(FileName), "%s/monster.db", DATAPATH); + + TReadScriptFile Script; + Script.open(FileName); + + Monsterhomes = 0; + while(true){ + int Race = Script.readNumber(); + if(Race == 0){ + break; + } + + Monsterhomes += 1; + TMonsterhome *MH = Monsterhome.at(Monsterhomes); + MH->Race = Race; + MH->x = Script.readNumber(); + MH->y = Script.readNumber(); + MH->z = Script.readNumber(); + MH->Radius = Script.readNumber(); + MH->MaxMonsters = Script.readNumber(); + MH->ActMonsters = 0; + MH->RegenerationTime = Script.readNumber(); + MH->Timer = 0; + + if(!IsOnMap(MH->x, MH->y, MH->z)){ + print(1, "WARNUNG: Monsterhome [%d,%d,%d] befindet sich außerhalb der Karte.\n", MH->x, MH->y, MH->z); + } + } + + print(1, "%d Monsterhomes geladen.\n", Monsterhomes); + Script.close(); + + for(int i = 0; i < Monsterhomes; i += 1){ + TMonsterhome *MH = Monsterhome.at(i); + for (int j = 0; j < MH->MaxMonsters; j += 1){ + int SpawnX = MH->x; + int SpawnY = MH->y; + int SpawnZ = MH->z; + int SpawnRadius = MH->Radius; + + if(j == 0){ + if(SpawnRadius > 1){ + SpawnRadius = 1; + } + }else{ + if(SpawnRadius > 10){ + SpawnRadius = 10; + } + + // NOTE(fusion): `SearchSpawnField` performs an extended search + // if the radius is negative. + SpawnRadius = -SpawnRadius; + } + + if(SearchSpawnField(&SpawnX, &SpawnY, &SpawnZ, SpawnRadius, false)){ + CreateMonster(MH->Race, SpawnX, SpawnY, SpawnZ, i, 0, false); + MH->ActMonsters += 1; + } + } + + if(MH->Timer > 0){ + error("LoadMonsterhomes: Timer läuft schon (Rasse %d an [%d,%d,%d]).\n", + MH->Race, MH->x, MH->y, MH->z); + }else if(MH->ActMonsters < MH->MaxMonsters){ + StartMonsterhomeTimer(i); + } + } +} + +void ProcessMonsterhomes(void){ + for(int i = 0; i < Monsterhomes; i += 1){ + TMonsterhome *MH = Monsterhome.at(i); + if(MH->Timer > 0){ + MH->Timer -= 1; + } + + if(MH->Timer > 0){ + continue; + } + + int MaxRadius = MH->Radius; + if(MaxRadius > 10){ + MaxRadius = 10; + } + + TFindCreatures Search(MaxRadius + 9, MaxRadius + 7, MH->x, MH->y, FIND_PLAYERS); + while(true){ + uint32 CharacterID = Search.getNext(); + if(CharacterID == 0){ + break; + } + + TPlayer *Player = GetPlayer(CharacterID); + if(Player == NULL){ + error("ProcessMonsterhomes: Kreatur existiert nicht.\n"); + break; + } + + // TODO(fusion): There is probably a helper function to check whether + // a floor is visible from another. + if(Player->posz <= 7){ + if(MH->z > 7){ + continue; + } + }else{ + if(std::abs(Player->posz - MH->z) > 2){ + continue; + } + } + + int DistanceX = std::abs(Player->posx - MH->x); + int DistanceY = std::abs(Player->posy - MH->y); + int Radius = std::max<int>(DistanceX - 9, DistanceY - 7); + if(Radius < MaxRadius){ + MaxRadius = Radius; + } + } + + if(MaxRadius >= 0){ + int SpawnX = MH->x; + int SpawnY = MH->y; + int SpawnZ = MH->z; + int SpawnRadius = MaxRadius; + + if(MH->ActMonsters == 0){ + if(SpawnRadius > 1){ + SpawnRadius = 1; + } + }else{ + // NOTE(fusion): `SearchSpawnField` performs an extended search + // if the radius is negative. + SpawnRadius = -SpawnRadius; + } + + if(SearchSpawnField(&SpawnX, &SpawnY, &SpawnZ, SpawnRadius, false)){ + CreateMonster(MH->Race, SpawnX, SpawnY, SpawnZ, i, 0, false); + MH->ActMonsters += 1; + + // TODO(fusion): Not sure why this check is here. + if(MH->Timer > 0){ + error("ProcessMonsterhomes: Timer läuft schon (Rasse %d an [%d,%d,%d]).\n", + MH->Race, SpawnX, SpawnY, SpawnZ); + } + } + } + + if(MH->ActMonsters < MH->MaxMonsters){ + StartMonsterhomeTimer(i); + } + } +} + +void NotifyMonsterhomeOfDeath(int Nr){ + if(Nr < 1 || Nr > Monsterhomes){ + error("NotifyMonsterhomeOfDeath: Ungültige Monsterhome-Nummer %d.\n", Nr); + return; + } + + TMonsterhome *MH = Monsterhome.at(Nr); + if(MH->ActMonsters < 1){ + error("NotifyMonsterhomeOfDeath: Monsterhome hat keine lebenden Kreaturen.\n"); + return; + } + + MH->ActMonsters -= 1; + if(MH->ActMonsters >= MH->MaxMonsters){ + error("NotifyMonsterhomeOfDeath: Monsterhome %d hatte zu viele Monster (%d statt %d).\n", + Nr, (MH->ActMonsters + 1), MH->MaxMonsters); + return; + } + + if(MH->Timer == 0){ + StartMonsterhomeTimer(Nr); + } +} + +bool MonsterhomeInRange(int Nr, int x, int y, int z){ + if(Nr == 0){ + return true; + } + + if(Nr < 1 || Nr > Monsterhomes){ + error("MonsterhomeInRange: Ungültige Monsterhome-Nummer %d.\n", Nr); + return false; + } + + TMonsterhome *MH = Monsterhome.at(Nr); + return std::abs(z - MH->z) <= 2 + && std::abs(x - MH->x) <= MH->Radius + && std::abs(y - MH->y) <= MH->Radius; +} + +// TNonplayer +// ============================================================================= +TNonplayer::TNonplayer(void) : TCreature() { + this->State = SLEEPING; +} + +TNonplayer::~TNonplayer(void){ + this->DelInList(); +} + +void TNonplayer::SetInList(void){ + TCreature::SetInCrList(); + *NonplayerList.at(FirstFreeNonplayer) = this; + FirstFreeNonplayer += 1; +} + +void TNonplayer::DelInList(void){ + bool Removed = false; + for(int i = 0; i < FirstFreeNonplayer; i += 1){ + if(*NonplayerList.at(i) == this){ + FirstFreeNonplayer -= 1; + *NonplayerList.at(i) = *NonplayerList.at(FirstFreeNonplayer); + *NonplayerList.at(FirstFreeNonplayer) = NULL; + Removed = true; + break; + } + } + + if(!Removed){ + error("TNonplayer::DelInList: Kreatur nicht gefunden.\n"); + } +} + +// TNPC +// ============================================================================= +TNPC::TNPC(const char *FileName) : + TNonplayer(), + QueuedPlayers(0, 9, 10), + QueuedAddresses(0, 9, 10) +{ + this->Type = NPC; + this->posz = 255; + this->Interlocutor = 0; + this->Topic = 0; + this->Price = 0; + this->Amount = 1; + this->TypeID = 0; + this->Data = 0; + this->LastTalk = 0; + this->QueueLength = 0; + this->Behaviour = NULL; + + TReadScriptFile Script; + Script.open(FileName); + while(true){ + Script.nextToken(); + if(Script.Token == ENDOFFILE){ + Script.close(); + break; + } + + if(Script.Token != IDENTIFIER){ + Script.error("identifier expected"); + } + + char Identifier[MAX_IDENT_LENGTH]; + strcpy(Identifier, Script.getIdentifier()); + Script.readSymbol('='); + + if(strcmp(Identifier, "name") == 0){ + strcpy(this->Name, Script.readString()); + }else if(strcmp(Identifier, "sex") == 0){ + const char *Sex = Script.readIdentifier(); + if(strcmp(Sex, "male") == 0){ + this->Sex = 1; + }else if(strcmp(Sex, "female") == 0){ + this->Sex = 2; + }else{ + Script.error("unknown constant"); + } + }else if(strcmp(Identifier, "race") == 0){ + this->Race = Script.readNumber(); + this->SetSkills(this->Race); + }else if(strcmp(Identifier, "outfit") == 0){ + this->OrgOutfit = ReadOutfit(&Script); + this->Outfit = this->OrgOutfit; + }else if(strcmp(Identifier, "home") == 0){ + Script.readCoordinate(&this->startx, &this->starty, &this->startx); + this->posx = this->startx; + this->posy = this->starty; + this->posz = this->startz; + }else if(strcmp(Identifier, "radius") == 0){ + this->Radius = Script.readNumber(); + }else if(strcmp(Identifier, "gostrength") == 0){ + if(this->Race == 0){ + Script.error("gostrength before race in npc-script-file"); + } + int GoStrength = Script.readNumber(); + this->Skills[SKILL_GO_STRENGTH]->Act = GoStrength; + this->Skills[SKILL_GO_STRENGTH]->Max = GoStrength; + }else if(strcmp(Identifier, "behaviour") == 0){ + if(this->Behaviour != NULL){ + Script.error("behaviour database specified twice for NPC"); + } + this->Behaviour = new TBehaviourDatabase(&Script); + }else{ + Script.error("unknown npc property"); + } + } + + if(this->Name[0] == 0){ + throw "no name specified for NPC"; + } + + if(this->posz == 255){ + throw "no startpoint specified for NPC"; + } + + if(this->Behaviour == NULL){ + throw "no behaviour database specified for NPC"; + } + + if(!IsOnMap(this->posx, this->posy, this->posz)){ + print(1, "WARNUNG: NPC '%s' steht außerhalb der Karte.\n", this->Name); + return; + } + + this->SetID(0); + this->SetInList(); + if(!this->SetOnMap()){ + print(1, "WARNUNG: Kann NPC \'%s\' nicht setzen.\n", this->Name); + return; + } + + this->ToDoYield(); +} + +TNPC::~TNPC(void){ + delete this->Behaviour; +} + +bool TNPC::MovePossible(int x, int y, int z, bool Execute, bool Jump){ + return CoordinateFlag(x, y, z, BANK) + && !CoordinateFlag(x, y, z, UNPASS) + && !CoordinateFlag(x, y, z, AVOID) + && z == this->startz + && std::abs(x - this->startx) <= this->Radius + && std::abs(y - this->starty) <= this->Radius + && !IsHouse(x, y, z); +} + +void TNPC::TalkStimulus(uint32 SpeakerID, const char *Text){ + if(SpeakerID == this->ID){ + return; + } + + if(Text == NULL){ + error("TNPC::TalkStimulus: Übergebener Text existiert nicht.\n"); + return; + } + + if(this->State == TALKING || this->QueueLength != 0){ + uint32 InterlocutorID = this->Interlocutor; + if(SpeakerID == InterlocutorID){ + this->LastTalk = RoundNr; + this->Behaviour->react(this, Text, DEFAULT); + }else{ + this->Interlocutor = SpeakerID; + this->Behaviour->react(this, Text, BUSY); + this->Interlocutor = InterlocutorID; + } + }else{ + this->ToDoClear(); + this->ChangeState(TALKING, false); + this->Interlocutor = SpeakerID; + this->LastTalk = RoundNr; + this->Behaviour->react(this, Text, ADDRESS); + if(this->State == TALKING){ + this->TurnToInterlocutor(); + } + } +} + +void TNPC::DamageStimulus(uint32 AttackerID, int Damage, int DamageType){ + // no-op +} + +void TNPC::IdleStimulus(void){ + if(this->State == TALKING){ + if((this->LastTalk + 30) > RoundNr){ + this->ToDoWait(2000); + this->ToDoStart(); + return; + } + + this->Behaviour->react(this, "", VANISH); + this->ChangeState(IDLE, false); + } + + if(this->State == IDLE){ + while(this->QueueLength > 0){ + uint32 InterlocutorID = *this->QueuedPlayers.at(0); + uint32 Text = *this->QueuedAddresses.at(0); + + // NOTE(fusion): Ordered removal. + this->QueueLength -= 1; + for(int i = 0; i < this->QueueLength; i += 1){ + *this->QueuedPlayers.at(i) = *this->QueuedPlayers.at(i + 1); + *this->QueuedAddresses.at(i) = *this->QueuedAddresses.at(i + 1); + } + + this->ToDoClear(); + + TCreature *Interlocutor = GetCreature(InterlocutorID); + if(Interlocutor != NULL){ + this->ChangeState(TALKING, false); + this->Interlocutor = InterlocutorID; + this->Behaviour->react(this, GetDynamicString(Text), ADDRESSQUEUE); + }else{ + error("TNPC::IdleStimulus: Gesprächspartner existiert nicht.\n"); + } + + DeleteDynamicString(Text); + if(this->State == TALKING){ + this->TurnToInterlocutor(); + this->LastTalk = RoundNr; + return; + } + } + + if(!this->LockToDo){ + TFindCreatures Search(10, 10, this->ID, FIND_PLAYERS); + if(Search.getNext() == 0){ + this->ChangeState(SLEEPING, false); + return; + } + + bool FoundDest = false; + int DestX, DestY, DestZ; + for(int i = 0; i < 10; i += 1){ + DestX = this->posx; + DestY = this->posy; + DestZ = this->posz; + switch(rand() % 4){ + case 0: DestX -= 1; break; + case 1: DestX += 1; break; + case 2: DestY -= 1; break; + case 3: DestY += 1; break; + } + + if(this->MovePossible(DestX, DestY, DestZ, true, false)){ + FoundDest = true; + break; + } + } + + if(!FoundDest){ + this->ToDoWait(2000); + this->ToDoStart(); + return; + } + + try{ + this->ToDoGo(DestX, DestY, DestZ, true, INT_MAX); + this->ToDoWait(2000); + this->ToDoStart(); + }catch(RESULT r){ + error("TNPC::IdleStimulus: Exception %d!\n", r); + } + } + } +} + +void TNPC::CreatureMoveStimulus(uint32 CreatureID, int Type){ + for(int i = 0; i < this->QueueLength; i += 1){ + uint32 QueuedPlayerID = *this->QueuedPlayers.at(i); + if(CreatureID != QueuedPlayerID && CreatureID != this->ID){ + continue; + } + + TCreature *QueuedPlayer = GetCreature(QueuedPlayerID); + if(Type != OBJECT_DELETED + && QueuedPlayer->posz == this->posz + && std::abs(QueuedPlayer->posx - this->posx) < 5 + && std::abs(QueuedPlayer->posy - this->posy) < 4){ + continue; + } + + // NOTE(fusion): Ordered removal. + this->QueueLength -= 1; + for(int j = i; j < this->QueueLength; j += 1){ + *this->QueuedPlayers.at(j) = *this->QueuedPlayers.at(j + 1); + *this->QueuedAddresses.at(j) = *this->QueuedAddresses.at(j + 1); + } + + // NOTE(fusion): The ordered removal has shifted queued players back one + // place so we need to check the current queue position again. + i -= 1; + } + + if(this->State == TALKING || this->State == LEAVING){ + if(CreatureID == this->Interlocutor || CreatureID == this->ID){ + TCreature *Interlocutor = GetCreature(this->Interlocutor); + if(Interlocutor == NULL){ + error("TNPC::CreatureMoveStimulus: Gesprächspartner existiert nicht.\n"); + this->ChangeState(IDLE, true); + return; + } + + if(Type != OBJECT_DELETED){ + this->Rotate(Interlocutor); + } + + if(this->State == TALKING){ + if(Type == OBJECT_DELETED + || Interlocutor->posz != this->posz + || std::abs(Interlocutor->posx - this->posx) >= 5 + || std::abs(Interlocutor->posy - this->posy) >= 4){ + this->Behaviour->react(this, "", VANISH); + this->ChangeState(IDLE, true); + } + } + } + } + + if(CreatureID != this->ID + && this->State == SLEEPING + && Type != OBJECT_DELETED){ + this->ChangeState(IDLE, true); + } +} + +void TNPC::GiveTo(ObjectType Type, int Amount){ + if(Amount == 0){ + return; + } + + if(Type.isMapContainer() || Type.getName(1) == NULL){ + error("TNPC::GiveTo: %s will Objekte vom Typ %d erschaffen.\n", + this->Name, Type.TypeID); + return; + } + + Log("npc", "%s -> %u: %d %s\n", this->Name, this->Interlocutor, Amount, Type.getName(1)); + + if(Type.getFlag(CUMULATIVE)){ + while(Amount > 0){ + int StackSize = std::min<int>(Amount, 100); + CreateAtCreature(this->Interlocutor, Type, StackSize); + Amount -= StackSize; + } + }else{ + while(Amount > 0){ + CreateAtCreature(this->Interlocutor, Type, this->Data); + Amount -= 1; + } + } +} + +void TNPC::GetFrom(ObjectType Type, int Amount){ + if(Amount > 0){ + Log("npc", "%s <- %u: %d %s\n", this->Name, this->Interlocutor, Amount, Type.getName(1)); + DeleteAtCreature(this->Interlocutor, Type, Amount, this->Data); + } +} + +void TNPC::GiveMoney(int Amount){ + int Crystal = (Amount / 10000); + int Platinum = (Amount % 10000) / 100; + int Gold = (Amount % 10000) % 100; + this->GiveTo(GetSpecialObject(MONEY_TENTHOUSAND), Crystal); + this->GiveTo(GetSpecialObject(MONEY_HUNDRED), Platinum); + this->GiveTo(GetSpecialObject(MONEY_ONE), Gold); +} + +void TNPC::GetMoney(int Amount){ + int Crystal = CountInventoryObjects(this->Interlocutor, GetSpecialObject(MONEY_TENTHOUSAND), 0); + int Platinum = CountInventoryObjects(this->Interlocutor, GetSpecialObject(MONEY_HUNDRED), 0); + int Gold = CountInventoryObjects(this->Interlocutor, GetSpecialObject(MONEY_ONE), 0); + CalculateChange(Amount, &Gold, &Platinum, &Crystal); + + ASSERT(Crystal >= 0); + if(Crystal > 0){ + this->GetFrom(GetSpecialObject(MONEY_TENTHOUSAND), Crystal); + } + + if(Platinum > 0){ + this->GetFrom(GetSpecialObject(MONEY_HUNDRED), Platinum); + }else if(Platinum < 0){ + this->GiveTo(GetSpecialObject(MONEY_HUNDRED), -Platinum); + } + + if(Gold > 0){ + this->GetFrom(GetSpecialObject(MONEY_ONE), Gold); + }else if(Gold < 0){ + this->GiveTo(GetSpecialObject(MONEY_ONE), -Gold); + } +} + +void TNPC::Enqueue(uint32 InterlocutorID, const char *Text){ + if(InterlocutorID == 0){ + error("TNPC::Enqueue: Gesprächspartner ist Null.\n"); + return; + } + + if(Text == NULL){ + error("TNPC::Enqueue: Text ist NULL.\n"); + return; + } + + bool AlreadyQueued = false; + for(int i = 0; i < this->QueueLength; i += 1){ + if(*this->QueuedPlayers.at(i) == InterlocutorID){ + AlreadyQueued = true; + break; + } + } + + if(!AlreadyQueued){ + *this->QueuedPlayers.at(this->QueueLength) = InterlocutorID; + *this->QueuedAddresses.at(this->QueueLength) = AddDynamicString(Text); + this->QueueLength += 1; + } +} + +void TNPC::TurnToInterlocutor(void){ + TCreature *Interlocutor = GetCreature(this->Interlocutor); + if(Interlocutor == NULL){ + error("TNPC::TurnToInterlocutor: Gesprächspartner existiert nicht.\n"); + return; + } + + this->Rotate(Interlocutor); +} + +void TNPC::ChangeState(STATE NewState, bool Stimulus){ + this->State = NewState; + if(Stimulus){ + this->ToDoYield(); + } +} + +void ChangeNPCState(TCreature *Npc, int NewState, bool Stimulus){ + if(Npc == NULL){ + error("ChangeNPCState: npc ist NULL.\n"); + return; + } + + if(Npc->Type != NPC){ + error("ChangeNPCState: npc ist kein NPC.\n"); + return; + } + + ((TNPC*)Npc)->ChangeState((STATE)NewState, Stimulus); +} + +// TMonster +// ============================================================================= +TMonster::TMonster(int Race, int x, int y, int z, int Home, uint32 MasterID) : + TNonplayer() +{ + this->Type = MONSTER; + this->Race = Race; + this->startx = x; + this->starty = y; + this->startz = z; + this->posx = x; + this->posx = y; + this->posx = z; + this->State = IDLE; + this->Home = Home; + this->Master = MasterID; + this->Target = 0; + + while(this->Master != 0){ + TCreature *Master = GetCreature(this->Master); + // TODO(fusion): Do we actually want to return here? + if(Master == NULL){ + error("TMonster::TMonster: Master existiert nicht.\n"); + return; + } + + if(Master->Type != MONSTER || ((TMonster*)Master)->Master != 0){ + this->LifeEndRound = Master->LifeEndRound; + Master->SummonedCreatures += 1; + break; + } + + error("TMonster::TMonster: Kinder dürfen keine eigenen Kinder erschaffen.\n"); + this->Master = ((TMonster*)Master)->Master; + } + + if(RaceData[Race].Article[0] == 0){ + strcpy(this->Name, RaceData[Race].Name); + }else{ + snprintf(this->Name, sizeof(this->Name), "%s %s", + RaceData[Race].Article, RaceData[Race].Name); + } + + this->SetSkills(Race); + this->OrgOutfit = RaceData[Race].Outfit; + this->Outfit = RaceData[Race].Outfit; + this->SetID(0); + this->SetInList(); + if(!this->SetOnMap()){ + error("TMonster::TMonster: Kann Monster nicht auf die Karte setzen.\n"); + return; + } + + try{ + if(this->Master == 0 && RaceData[Race].Items > 0){ + Object Bag = Create(GetBodyContainer(this->ID, INVENTORY_BAG), + GetSpecialObject(DEFAULT_CONTAINER), + 0); + for(int i = 0; i < RaceData[Race].Items; i += 1){ + TItemData *ItemData = RaceData[Race].Item.at(i); + if(random(0, 999) > ItemData->Probability){ + continue; + } + + ObjectType ItemType = ItemData->Type; + int Amount = random(1, ItemData->Maximum); + int Repeat = 1; + if(!ItemType.getFlag(CUMULATIVE)){ + Repeat = Amount; + Amount = 0; + } + + for(int j = 0; j < Repeat; j += 1){ + // TODO(fusion): Same as `ProcessMonsterRaids`. + Object Item = NONE; + try{ + if(ItemType.getFlag(WEAPON) + || ItemType.getFlag(SHIELD) + || ItemType.getFlag(BOW) + || ItemType.getFlag(THROW) + || ItemType.getFlag(WAND) + || ItemType.getFlag(WEAROUT) + || ItemType.getFlag(EXPIRE) + || ItemType.getFlag(EXPIRESTOP)){ + Item = Create(Bag, ItemType, 0); + }else{ + Item = CreateAtCreature(this->ID, ItemType, Amount); + } + }catch(RESULT r){ + error("TMonster::TMonster: Exception %d bei Rasse %d, ggf." + " CarryStrength erhöhen.\n", r, Race); + break; + } + + // NOTE(fusion): Prevent items from being dropped onto the map. + if(Item.getContainer().getObjectType().isMapContainer()){ + error("TMonster::TMonster: Objekt fällt auf die Karte." + " CarryStrength für Rasse %d erhöhen.\n", Race); + Delete(Item, -1); + } + } + } + + // NOTE(fusion): `Bag` could be empty if we failed to add any + // items to it in the loop above. + if(GetFirstContainerObject(Bag) == NONE){ + Delete(Bag, -1); + } + } + }catch(RESULT r){ + error("TMonster::TMonster: Exception %d bei Rasse %d.\n", r, Race); + } + + this->Combat.CheckCombatValues(); + this->Combat.SetAttackMode(ATTACK_MODE_BALANCED); + this->ToDoYield(); +} + +TMonster::~TMonster(void){ + if(!this->IsDead){ + GraphicalEffect(this->posx, this->posy, this->posz, EFFECT_POFF); + }else if(this->Master == 0){ + this->Combat.DistributeExperiencePoints(RaceData[this->Race].ExperiencePoints); + } + + if(this->Master != 0){ + TCreature *Master = GetCreature(this->Master); + if(Master != NULL && Master->SummonedCreatures > 0){ + Master->SummonedCreatures -= 1; + } + } + + if(this->Home != 0){ + NotifyMonsterhomeOfDeath(this->Home); + } +} diff --git a/src/crplayer.cc b/src/crplayer.cc index 5c28855..2660c04 100644 --- a/src/crplayer.cc +++ b/src/crplayer.cc @@ -8,8 +8,8 @@ #include "stubs.hh" static Semaphore PlayerMutex(1); -static int FirstFreePlayer; static vector<TPlayer*> PlayerList(0, 100, 10, NULL); +static int FirstFreePlayer; static Semaphore PlayerDataPoolMutex(1); static TPlayerData PlayerDataPool[2000]; diff --git a/src/enums.hh b/src/enums.hh index 7eb2671..6d6baf0 100644 --- a/src/enums.hh +++ b/src/enums.hh @@ -31,6 +31,74 @@ enum AttackMode: uint8 { ATTACK_MODE_DEFENSIVE = 3, }; +enum BehaviourActionType: int { + BEHAVIOUR_ACTION_NONE = 0, + BEHAVIOUR_ACTION_REPLY = 1, + BEHAVIOUR_ACTION_SET_VARIABLE = 2, + BEHAVIOUR_ACTION_SET_SKILL = 3, + BEHAVIOUR_ACTION_FUNCTION2 = 4, + BEHAVIOUR_ACTION_FUNCTION1 = 5, + BEHAVIOUR_ACTION_FUNCTION0 = 6, + BEHAVIOUR_ACTION_CHANGESTATE = 7, + BEHAVIOUR_ACTION_SET_SKILL_TIMER = 8, + BEHAVIOUR_ACTION_REPEAT = 9, + BEHAVIOUR_ACTION_FUNCTION3 = 10, +}; + +enum BehaviourConditionType: int { + BEHAVIOUR_CONDITION_NONE = 0, + BEHAVIOUR_CONDITION_TEXT = 1, + BEHAVIOUR_CONDITION_PROPERTY = 2, + BEHAVIOUR_CONDITION_PARAMETER = 3, + BEHAVIOUR_CONDITION_EXPRESSION = 4, + BEHAVIOUR_CONDITION_SHORTCIRCUIT = 5, +}; + +enum BehaviourNodeType: int { + BEHAVIOUR_NODE_NONE = 0, + BEHAVIOUR_NODE_CMP_LT = 1, + BEHAVIOUR_NODE_CMP_GT = 2, + BEHAVIOUR_NODE_CMP_EQ = 3, + BEHAVIOUR_NODE_CMP_NEQ = 4, + BEHAVIOUR_NODE_CMP_LE = 5, + BEHAVIOUR_NODE_CMP_GE = 6, + BEHAVIOUR_NODE_ADD = 7, + BEHAVIOUR_NODE_SUB = 8, + BEHAVIOUR_NODE_MUL = 9, + BEHAVIOUR_NODE_PARAMETER = 10, + BEHAVIOUR_NODE_NUMBER = 11, + BEHAVIOUR_NODE_TOPIC = 12, + BEHAVIOUR_NODE_PRICE = 13, + BEHAVIOUR_NODE_AMOUNT = 14, + BEHAVIOUR_NODE_SKILL = 15, + BEHAVIOUR_NODE_COUNT = 16, + BEHAVIOUR_NODE_COUNTMONEY = 17, + BEHAVIOUR_NODE_TYPE = 18, + BEHAVIOUR_NODE_DATA = 19, + BEHAVIOUR_NODE_SPELLKNOWN = 20, + BEHAVIOUR_NODE_SPELLLEVEL = 21, + BEHAVIOUR_NODE_RANDOM = 22, + BEHAVIOUR_NODE_QUESTVALUE = 23, +}; + +enum BehaviourProperty: int { + BEHAVIOUR_PROPERTY_NONE = 0, + BEHAVIOUR_PROPERTY_ADDRESS = 1, + BEHAVIOUR_PROPERTY_BUSY = 2, + BEHAVIOUR_PROPERTY_VANISH = 3, + BEHAVIOUR_PROPERTY_MALE = 4, + BEHAVIOUR_PROPERTY_FEMALE = 5, + BEHAVIOUR_PROPERTY_KNIGHT = 6, + BEHAVIOUR_PROPERTY_PALADIN = 7, + BEHAVIOUR_PROPERTY_SORCERER = 8, + BEHAVIOUR_PROPERTY_DRUID = 9, + BEHAVIOUR_PROPERTY_PREMIUM = 10, + BEHAVIOUR_PROPERTY_PROMOTED = 11, + BEHAVIOUR_PROPERTY_PZBLOCK = 12, + BEHAVIOUR_PROPERTY_NONPVP = 13, + BEHAVIOUR_PROPERTY_PVPENFORCED = 14, +}; + enum BloodType: int { BT_BLOOD = 0, BT_SLIME = 1, @@ -58,6 +126,37 @@ enum Color: int { COLOR_WHITE = 215, }; +// TODO(fusion): These are used with `TMoveUseCondition`. Probably add a +// `MOVEUSE` prefix not to mix with `TBehaviourCondition`? +enum ConditionType: int { + CONDITION_ISPOSITION = 0, + CONDITION_ISTYPE = 1, + CONDITION_ISCREATURE = 2, + CONDITION_ISPLAYER = 3, + CONDITION_HASFLAG = 4, + CONDITION_HASTYPEATTRIBUTE = 5, + CONDITION_HASINSTANCEATTRIBUTE = 6, + CONDITION_HASTEXT = 7, + CONDITION_ISPEACEFUL = 8, + CONDITION_MAYLOGOUT = 9, + CONDITION_HASPROFESSION = 10, + CONDITION_HASLEVEL = 11, + CONDITION_HASRIGHT = 12, + CONDITION_HASQUESTVALUE = 13, + CONDITION_TESTSKILL = 14, + CONDITION_COUNTOBJECTS = 15, + CONDITION_COUNTOBJECTSONMAP = 16, + CONDITION_ISOBJECTTHERE = 17, + CONDITION_ISCREATURETHERE = 18, + CONDITION_ISPLAYERTHERE = 19, + CONDITION_ISOBJECTININVENTORY = 20, + CONDITION_ISPROTECTIONZONE = 21, + CONDITION_ISHOUSE = 22, + CONDITION_ISHOUSEOWNER = 23, + CONDITION_ISDRESSED = 24, + CONDITION_RANDOM = 25, +}; + enum CONNECTIONSTATE: int { CONNECTION_FREE = 0, CONNECTION_ASSIGNED = 1, @@ -460,6 +559,14 @@ enum SecureMode: uint8 { SECURE_MODE_ENABLED = 1, }; +enum SITUATION: int { + DEFAULT = 0, + ADDRESS = 1, + ADDRESSQUEUE = 2, + BUSY = 3, + VANISH = 4, +}; + // NOTE(fusion): Not in debug symbols. enum Skill: int { SKILL_LEVEL = 0, diff --git a/src/stubs.hh b/src/stubs.hh index e3b694e..d8b9839 100644 --- a/src/stubs.hh +++ b/src/stubs.hh @@ -21,7 +21,6 @@ extern void RemoveBuddyOrder(TCreature *Creature, uint32 BuddyID); extern void ChangeGuests(uint16 HouseID, TPlayer *Player, const char *Text); extern void ChangeSubowners(uint16 HouseID, TPlayer *Player, const char *Text); extern void ChangeNameDoor(Object Door, TPlayer *Player, const char *Text); -extern void ChangeNPCState(TCreature *Npc, int NewState, bool Stimulus); extern void CharacterDeathOrder(TCreature *Creature, int OldLevel, uint32 Offender, const char *Remark, bool Unjustified); extern void CleanHouseField(int x, int y, int z); @@ -43,7 +42,6 @@ extern void LogoutOrder(TPlayer *Player); extern void PrepareHouseCleanup(void); extern void FinishHouseCleanup(void); extern void PlayerlistOrder(int NumberOfPlayers, char *PlayerNames, int *PlayerLevels, int *PlayerProfessions); -extern void ProcessMonsterhomes(void); extern void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails); extern void ProcessWriterThreadReplies(void); extern void PunishmentOrder(TCreature *Creature, const char *Name, const char *IPAddress, |
