diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-06-06 19:36:18 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-06-06 19:36:18 -0300 |
| commit | 76f6ecb7c809c6e93447efb91bdf50f2a9d50d6b (patch) | |
| tree | ece58733660361874acc912208d6482d639e556e /src/crmain.cc | |
| parent | 850fa1c0e128a4fe05ffdbdabc9dad25a7530a3f (diff) | |
| download | game-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.cc | 581 |
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; +} |
