aboutsummaryrefslogtreecommitdiff
path: root/src/crmain.cc
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-06 19:36:18 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-06 19:36:18 -0300
commit76f6ecb7c809c6e93447efb91bdf50f2a9d50d6b (patch)
treeece58733660361874acc912208d6482d639e556e /src/crmain.cc
parent850fa1c0e128a4fe05ffdbdabc9dad25a7530a3f (diff)
downloadgame-76f6ecb7c809c6e93447efb91bdf50f2a9d50d6b.tar.gz
game-76f6ecb7c809c6e93447efb91bdf50f2a9d50d6b.zip
some work on `crmain.cc` and `cract.cc` + merge creature headers
Diffstat (limited to 'src/crmain.cc')
-rw-r--r--src/crmain.cc581
1 files changed, 563 insertions, 18 deletions
diff --git a/src/crmain.cc b/src/crmain.cc
index d962bf9..49348d8 100644
--- a/src/crmain.cc
+++ b/src/crmain.cc
@@ -1,22 +1,23 @@
#include "cr.hh"
+#include "config.hh"
#include "enums.hh"
+#include "info.hh"
#include "stubs.hh"
-static uint32 NextCreatureID;
-static matrix<uint32> *FirstChainCreature;
-static TCreature *HashList[1000];
-
-static int FirstFreeCreature;
-static vector<TCreature*> CreatureList(0, 10000, 1000);
-
-static priority_queue<uint32, uint32> ToDoQueue(5000, 1000);
-//static priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100);
-
TRaceData RaceData[MAX_RACES];
int KilledCreatures[MAX_RACES];
int KilledPlayers[MAX_RACES];
+priority_queue<uint32, uint32> ToDoQueue(5000, 1000);
+//priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100);
+
+static uint32 NextCreatureID;
+static int FirstFreeCreature;
+static TCreature *HashList[1000];
+static matrix<uint32> *FirstChainCreature;
+static vector<TCreature*> CreatureList(0, 10000, 1000);
+
// TCreature
// =============================================================================
TCreature::TCreature(void) :
@@ -67,8 +68,286 @@ TCreature::TCreature(void) :
}
}
-void TCreature::Attack(void){
- this->Combat.Attack();
+TCreature::~TCreature(void){
+ // TODO(fusion): Bruuhh... these exceptions...
+ if(this->IsDead){
+ int Race = this->Race;
+ int PoolLiquid = LIQUID_NONE;
+ if(RaceData[Race].Blood == BT_BLOOD){
+ PoolLiquid = LIQUID_BLOOD;
+ }else if(RaceData[Race].Blood == BT_SLIME){
+ PoolLiquid = LIQUID_SLIME;
+ }
+
+ if(PoolLiquid != LIQUID_NONE){
+ try{
+ CreatePool(GetMapContainer(this->CrObject),
+ GetSpecialObject(BLOOD_POOL),
+ PoolLiquid);
+ }catch(RESULT r){
+ if(r != NOROOM && r != DESTROYED){
+ error("TCreature::~TCreature: Kann Blutlache nicht setzen (Exc %d, Pos [%d,%d,%d]).\n",
+ r, this->posx, this->posy, this->posz);
+ }
+ }
+ }
+
+ ObjectType CorpseType = (this->Sex == 1) // MALE ?
+ ? RaceData[Race].MaleCorpse
+ : RaceData[Race].FemaleCorpse;
+
+ if(CorpseType.getFlag(MAGICFIELD)){
+ Object Obj = GetFirstObject(this->posx, this->posy, this->posz);
+ while(Obj != NONE){
+ Object Next = Obj.getNextObject();
+ if(Obj.getObjectType().getFlag(MAGICFIELD)){
+ try{
+ Delete(Obj, -1);
+ }catch(RESULT r){
+ error("TCreature::~TCreature: Exception %d beim Löschen eines Feldes.\n", r);
+ }
+ }
+ Obj = Next;
+ }
+ }
+
+ try{
+ Object Con = GetMapContainer(this->posx, this->posy, this->posz);
+ Object Corpse = Create(Con, CorpseType, 0);
+ Log("game", "Tod von %s: LoseInventory=%d.\n", this->Name, this->LoseInventory);
+
+ if(this->Type == PLAYER){
+ char Help[128];
+ sprintf(Help, "You recognize %s", this->Name);
+ if(this->Murderer[0] != 0){
+ if(this->Sex == 1){ // MALE ?
+ strcat(Help, ". He was killed by ");
+ }else{
+ strcat(Help, ". She was killed by ");
+ }
+ strcat(Help, this->Murderer);
+ }
+ Change(Corpse, TEXTSTRING, AddDynamicString(Help));
+ }
+
+ if(this->LoseInventory != 0){ // LOSE_INVENTORY_NONE ?
+ for(int Position = 1; Position <= 10; Position += 1){
+ Object Item = GetBodyObject(this->ID, Position);
+ if(Item == NONE){
+ continue;
+ }
+
+ if(this->LoseInventory != 2 // LOSE_INVENTORY_ALL ?
+ && !Item.getObjectType().getFlag(CONTAINER)
+ && random(0, 9) != 0){
+ continue;
+ }
+
+ Move(0, Item, Corpse, -1, false, NONE);
+ }
+ }
+
+ if(this->Type == PLAYER && this->LoseInventory != 2){ // LOSE_INVENTORY_ALL ?
+ ((TPlayer*)this)->SaveInventory();
+ }
+ }catch(RESULT r){
+ error("TCreature::~TCreature: Kann Leiche/Inventory nicht ablegen (Exc %d, Pos [%d,%d,%d], %s).\n",
+ r, this->posx, this->posy, this->posz, this->Name);
+ }
+ }
+
+ if(this->CrObject != NONE && this->CrObject.exists()){
+ this->DelOnMap();
+ }
+
+ this->ToDoClear();
+
+ if(this->Type == PLAYER && this->Connection != NULL){
+ this->Connection->Logout(30, true);
+ }
+
+ this->DelInCrList();
+
+ if(this->ID != 0){
+ this->DelID();
+ }
+
+ for(TKnownCreature *KnownCreature = this->FirstKnowingConnection;
+ KnownCreature != NULL;
+ KnownCreature = KnownCreature->Next){
+ if(KnownCreature->CreatureID != this->ID){
+ error("TCreature::~TCreature: Verkettungsfehler bei Kreatur %u.\n", this->ID);
+ }
+ KnownCreature->State = KNOWNCREATURE_FREE;
+ }
+}
+
+void TCreature::SetID(uint32 CharacterID){
+ if(this->ID != 0){
+ error("TCreature::SetID: ID ist schon gesetzt.\n");
+ }
+
+ uint32 CreatureID = 0;
+ if(CharacterID == 0){
+ bool Found = false;
+ for(int Attempts = 0; Attempts < 16; Attempts += 1){
+ CreatureID = NextCreatureID++;
+ if(GetCreature(CreatureID) == NULL){
+ Found = true;
+ break;
+ }
+ }
+
+ if(!Found){
+ error("TCreature::SetID: 16x hintereinander doppelte ID."
+ " Verwende nun doppelte ID %d\n", CreatureID);
+ }
+ }else{
+ CreatureID = CharacterID;
+ if(GetCreature(CreatureID) != NULL){
+ error("TCreature::SetID: Doppelte Character-ID %d gefunden.\n", CharacterID);
+ }
+ }
+
+ uint32 ListIndex = CreatureID % NARRAY(HashList);
+ this->ID = CreatureID;
+ this->NextHashEntry = HashList[ListIndex];
+ HashList[ListIndex] = this;
+}
+
+void TCreature::DelID(void){
+ uint32 ListIndex = this->ID % NARRAY(HashList);
+ TCreature *First = HashList[ListIndex];
+ if(First == NULL){
+ error("TCreature::DelID: Hasheintrag nicht gefunden id = %d\n", this->ID);
+ return;
+ }
+
+ if(First->ID == this->ID){
+ HashList[ListIndex] = this->NextHashEntry;
+ }else{
+ TCreature *Prev = First;
+ TCreature *Current = First->NextHashEntry;
+ while(true){
+ if(Current == NULL){
+ error("TCreature::DelID: id=%d nicht gefunden.\n", this->ID);
+ return;
+ }
+
+ if(Current->ID == this->ID){
+ Prev->NextHashEntry = this->NextHashEntry;
+ break;
+ }
+
+ Prev = Current;
+ Current = Current->NextHashEntry;
+ }
+ }
+}
+
+void TCreature::SetInCrList(void){
+ *CreatureList.at(FirstFreeCreature) = this;
+ FirstFreeCreature += 1;
+}
+
+void TCreature::DelInCrList(void){
+ // TODO(fusion): See note in `ProcessCreatures`.
+ for(int Index = 0; Index < FirstFreeCreature; Index += 1){
+ TCreature **Current = CreatureList.at(Index);
+ if(*Current == this){
+ TCreature **Last = CreatureList.at(FirstFreeCreature - 1);
+ *Current = *Last;
+ *Last = NULL;
+ FirstFreeCreature -= 1;
+
+ // TODO(fusion): The original function wouldn't break here. Maybe it
+ // is possible to have duplicates in `CreatureList`?
+ //break;
+ }
+ }
+}
+
+void TCreature::StartLogout(bool Force, bool StopFight){
+ this->LoggingOut = true;
+ if(Force || LagDetected()){
+ this->LogoutAllowed = true;
+ }
+
+ if(this->Type == PLAYER && this->Connection != NULL){
+ this->Connection->Logout(0, true);
+ }
+
+ this->Combat.StopAttack(StopFight ? 0 : 60);
+}
+
+int TCreature::LogoutPossible(void){
+ if(!this->LogoutAllowed && !this->IsDead && !GameEnding()){
+ if(this->EarliestLogoutRound > RoundNr && !LagDetected()){
+ return 1; // LOGOUT_COMBAT ?
+ }
+
+ if(IsNoLogoutField(this->posx, this->posy, this->posz)){
+ return 2; // LOGOUT_FIELD ?
+ }
+
+ this->LogoutAllowed = true;
+ }
+
+ return 0; // LOGOUT_OK ?
+}
+
+void TCreature::BlockLogout(int Delay, bool BlockProtectionZone){
+ if(WorldType == NON_PVP){
+ BlockProtectionZone = false;
+ }
+
+ if(this->Type == PLAYER && !CheckRight(this->ID, NO_LOGOUT_BLOCK)){
+ if(BlockProtectionZone || this->EarliestProtectionZoneRound > RoundNr){
+ uint32 EarliestProtectionZoneRound = RoundNr + Delay;
+ if(this->EarliestProtectionZoneRound < EarliestProtectionZoneRound){
+ this->EarliestProtectionZoneRound = EarliestProtectionZoneRound;
+ }
+ }else if(this->Connection == NULL){
+ // NOTE(fusion): This is a failsafe to avoid extending the earliest
+ // logout round of a player that got disconnected in combat.
+ return;
+ }
+
+ uint32 EarliestLogoutRound = RoundNr + Delay;
+ if(this->EarliestLogoutRound < EarliestLogoutRound){
+ this->EarliestLogoutRound = EarliestLogoutRound;
+ }
+
+ ((TPlayer*)this)->CheckState();
+ }
+}
+
+int TCreature::GetHealth(void){
+ int MaxHitPoints = this->Skills[SKILL_HITPOINTS]->Max;
+ if(MaxHitPoints <= 0){
+ if(!this->IsDead){
+ error("TCreature::GetHealth: MaxHitpoints von %s ist %d, obwohl sie nicht tot ist.\n",
+ this->Name, MaxHitPoints);
+ }
+ return 0;
+ }
+
+ int CurrentHitPoints = this->Skills[SKILL_HITPOINTS]->Get();
+ int Health = CurrentHitPoints * 100 / MaxHitPoints;
+ if(Health <= 0){
+ Health = (int)(CurrentHitPoints != 0);
+ }
+ return Health;
+}
+
+int TCreature::GetSpeed(void){
+ TSkill *GoStrength = this->Skills[SKILL_GO_STRENGTH];
+ if(GoStrength == NULL){
+ error("TCreature::GetSpeed: Kein Skill GOSTRENGTH vorhanden.\n");
+ return 0;
+ }
+
+ return GoStrength->Get() * 2 + 80;
}
int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){
@@ -462,13 +741,143 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){
return Damage;
}
+void TCreature::Death(void){
+ this->IsDead = true;
+ this->LoggingOut = true;
+}
+
+bool TCreature::MovePossible(int x, int y, int z, bool Execute, bool Jump){
+ bool Result;
+
+ if(Jump){
+ Result = JumpPossible(x, y, z, false);
+ }else{
+ Result = CoordinateFlag(x, y, z, BANK)
+ && !CoordinateFlag(x, y, z, UNPASS);
+ }
+
+ if(Result && !Execute && CoordinateFlag(x, y, z, AVOID)){
+ Result = false;
+ }
+
+ return Result;
+}
+
+bool TCreature::IsPeaceful(void){
+ return true;
+}
+
+uint32 TCreature::GetMaster(void){
+ return 0;
+}
+
+void TCreature::TalkStimulus(uint32 SpeakerID, const char *Text){
+ // no-op
+}
+
+void TCreature::DamageStimulus(uint32 AttackerID, int Damage, int DamageType){
+ // no-op
+}
+
+void TCreature::IdleStimulus(void){
+ // no-op
+}
+
+void TCreature::CreatureMoveStimulus(uint32 CreatureID, int Type){
+ if(CreatureID == 0 || CreatureID == this->ID
+ || this->IsDead
+ || this->Combat.AttackDest != CreatureID
+ || this->Combat.ChaseMode != CHASE_MODE_CLOSE
+ || this->Combat.EarliestAttackTime <= (ServerMilliseconds + 200)){
+ return;
+ }
+
+ // TODO(fusion): Find out what `Type` is here.
+ if(Type != 2 // STIMULUS_TYPE??
+ || !this->LockToDo
+ || this->ActToDo >= this->NrToDo
+ || this->ToDoList.at(this->ActToDo)->Code != TDAttack){
+ return;
+ }
+
+ TCreature *Target = GetCreature(this->Combat.AttackDest);
+ if(Target == NULL){
+ return;
+ }
+
+ int Distance = ObjectDistance(this->CrObject, Target->CrObject);
+ if(Distance <= 1){
+ return;
+ }
+
+ // TODO(fusion): Review this.
+ try{
+ if(this->ToDoClear() && this->Type == PLAYER){
+ SendSnapback(this->Connection);
+ }
+ this->ToDoWait(200);
+ this->ToDoAttack();
+ this->ToDoStart();
+ }catch(RESULT r){
+ if(this->Type == PLAYER){
+ SendResult(this->Connection, r);
+ }
+ this->ToDoClear();
+ this->ToDoWait(this->Combat.EarliestAttackTime);
+ this->ToDoStart();
+ }
+}
+
+void TCreature::AttackStimulus(uint32 AttackerID){
+ // no-op
+}
+
// Creature Management
// =============================================================================
bool IsCreaturePlayer(uint32 CreatureID){
return CreatureID < 0x40000000;
}
-//
+TCreature *GetCreature(uint32 CreatureID){
+ if(CreatureID == 0){
+ return NULL;
+ }
+
+ TCreature *Creature = HashList[CreatureID % NARRAY(HashList)];
+ while(Creature != NULL && Creature->ID != CreatureID){
+ Creature = Creature->NextHashEntry;
+ }
+
+ return Creature;
+}
+
+TCreature *GetCreature(Object Obj){
+ return GetCreature(Obj.getCreatureID());
+}
+
+void InsertChainCreature(TCreature *Creature, int CoordX, int CoordY){
+ if(Creature == NULL){
+ // TODO(fusion): Maybe a typo on the name of the function? I thought it
+ // could be some type of macro because there was no function name mismatch
+ // until now.
+ error("DeleteChainCreature: Übegebene Kreatur existiert nicht.\n");
+ return;
+ }
+
+ if(CoordX == 0){
+ CoordX = Creature->posx;
+ }
+
+ if(CoordY == 0){
+ CoordY = Creature->posy;
+ }
+
+ int ChainX = CoordX / 16;
+ int ChainY = CoordY / 16;
+ uint32 *FirstID = FirstChainCreature->at(ChainX, ChainY);
+ Creature->NextChainCreature = *FirstID;
+ *FirstID = Creature->ID;
+}
void DeleteChainCreature(TCreature *Creature){
if(Creature == NULL){
@@ -479,9 +888,9 @@ void DeleteChainCreature(TCreature *Creature){
// NOTE(fusion): All creatures in each 16x16 region form a creature linked
// list, despite its current floor. Whether that is a good idea is a whole
// other matter.
- int BlockX = Creature->posx / 16;
- int BlockY = Creature->posy / 16;
- uint32 *FirstID = FirstChainCreature->at(BlockX, BlockY);
+ int ChainX = Creature->posx / 16;
+ int ChainY = Creature->posy / 16;
+ uint32 *FirstID = FirstChainCreature->at(ChainX, ChainY);
if(*FirstID == Creature->ID){
*FirstID = Creature->NextChainCreature;
@@ -509,8 +918,112 @@ void DeleteChainCreature(TCreature *Creature){
}
}
+void MoveChainCreature(TCreature *Creature, int CoordX, int CoordY){
+ if(Creature == NULL){
+ error("DeleteChainCreature: Übegebene Kreatur existiert nicht.\n");
+ return;
+ }
+
+ int NewChainX = CoordX / 16;
+ int NewChainY = CoordY / 16;
+ int OldChainX = Creature->posx / 16;
+ int OldChainY = Creature->posy / 16;
+
+ if(NewChainX != OldChainX || NewChainY != OldChainY){
+ DeleteChainCreature(Creature);
+ InsertChainCreature(Creature, CoordX, CoordY);
+ }
+}
+
+void ProcessCreatures(void){
+ for(int Index = 0; Index < FirstFreeCreature; Index += 1){
+ TCreature *Creature = *CreatureList.at(Index);
+ if(Creature == NULL){
+ error("ProcessCreatures: Kreatur %d existiert nicht.\n", Index);
+ continue;
+ }
+
+ // TODO(fusion): It is weird that we do check the connection all the time
+ // and most of the time it is redundant because functions will check if
+ // it's NULL before attempting anything.
+
+ int FoodRegen = Creature->Skills[SKILL_FED]->Get();
+ if(FoodRegen > 0 && (RoundNr % FoodRegen) == 0 && !Creature->IsDead
+ && !IsProtectionZone(Creature->posx, Creature->posy, Creature->posz)){
+ Creature->Skills[SKILL_HITPOINTS]->Change(1);
+ Creature->Skills[SKILL_MANA]->Change(4);
+ if(Creature->Type == PLAYER){
+ SendPlayerData(Creature->Connection);
+ }
+ }
+
+ if(Creature->Type == PLAYER){
+ if(Creature->Connection != NULL){
+ ((TPlayer*)Creature)->CheckState();
+ }
+
+ if(Creature->EarliestLogoutRound != 0 && Creature->EarliestLogoutRound <= RoundNr){
+ ((TPlayer*)Creature)->ClearPlayerkillingMarks();
+ Creature->EarliestLogoutRound = 0;
+ }
+ }
+
+ if(!Creature->IsDead && Creature->Skills[SKILL_HITPOINTS]->Get() <= 0){
+ error("ProcessCreatures: Kreatur %s ist nicht tot, obwohl sie keine HP mehr hat.\n", Creature->Name);
+ Creature->Death();
+ }
+
+ if(Creature->LoggingOut && Creature->LogoutPossible() == 0){ // LOGOUT_POSSIBLE ?
+ if(Creature->IsDead && Creature->Skills[SKILL_HITPOINTS]->Get() > 0){
+ error("ProcessCreatures: Kreatur %s hat HP, obwohl sie tot ist.\n", Creature->Name);
+ Creature->Skills[SKILL_HITPOINTS]->Set(0);
+ }
+
+ // TODO(fusion): Creatures are removed from `CreatureList` with a swap
+ // and pop. Since we're iterating it RIGHT NOW, we need to process the
+ // the current index AGAIN because it'll now contain the creature that
+ // was previously at the end of the list. The annoying part here is that
+ // this removal occurs implicitly in the creature's destructor.
+ delete Creature;
+ Index -= 1;
+ }
+ }
+}
+
+void ProcessSkills(void){
+ for(int Index = 0; Index < FirstFreeCreature; Index += 1){
+ TCreature *Creature = *CreatureList.at(Index);
+ if(Creature == NULL){
+ error("ProcessSkills: Kreatur %d existiert nicht.\n", Index);
+ continue;
+ }
+
+ Creature->ProcessSkills();
+ }
+}
+
+void MoveCreatures(int Delay){
+ ServerMilliseconds += Delay;
+ while(ToDoQueue.Entries > 0){
+ auto Entry = *ToDoQueue.Entry->at(1);
+ uint32 ExecutionTime = Entry.Key;
+ uint32 CreatureID = Entry.Data;
+ if(ExecutionTime > ServerMilliseconds){
+ break;
+ }
+
+ ToDoQueue.deleteMin();
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature != NULL){
+ Creature->Execute();
+ }
+ }
+}
+
// Kill Statistics
// =============================================================================
+void InitKillStatistics(void);//TODO
+void ExitKillStatistics(void);//TODO
void AddKillStatistics(int AttackerRace, int DefenderRace){
// NOTE(fusion): I think the race name can be "human" only for players,
// which means we're probably tracking how many creatures are killed by
@@ -572,9 +1085,13 @@ TRaceData::TRaceData(void) :
this->Spells = 0;
}
+bool IsRaceValid(int Race){
+ return Race >= 1 && Race < MAX_RACES;
+}
+
int GetRaceByName(const char *RaceName){
int Result = 0;
- for(int Race = 1; Race < NARRAY(RaceData); Race += 1){
+ for(int Race = 1; Race < MAX_RACES; Race += 1){
if(stricmp(RaceName, RaceData[Race].Name) == 0){
Result = Race;
break;
@@ -583,7 +1100,35 @@ int GetRaceByName(const char *RaceName){
return Result;
}
-// Raid
+void LoadRaces(void); //TODO
+
+// Monster Raid
// =============================================================================
+void LoadMonsterRaids(void); //TODO
+// Initialization
+// =============================================================================
+void InitCr(void){
+ NextCreatureID = 0x40000000;
+ FirstFreeCreature = 0;
+ FirstChainCreature = new matrix<uint32>(
+ SectorXMin * 2, SectorXMax * 2 + 1,
+ SectorYMin * 2, SectorYMax * 2 + 1,
+ 0);
+ LoadRaces();
+ LoadMonsterRaids();
+ InitCrskill();
+ InitPlayer();
+ InitNonplayer();
+ InitKillStatistics();
+}
+
+void ExitCr(void){
+ ExitKillStatistics();
+ ExitPlayer();
+ ExitNonplayer();
+ ExitCrskill();
+
+ delete FirstChainCreature;
+}