some work on crmain.cc and cract.cc + merge creature headers

This commit is contained in:
fusion32 2025-06-06 19:36:18 -03:00
parent 850fa1c0e1
commit 76f6ecb7c8
15 changed files with 1553 additions and 3575 deletions

View File

@ -15,13 +15,17 @@ endif
HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/config.hh $(SRCDIR)/connection.hh $(SRCDIR)/containers.hh $(SRCDIR)/cr.hh $(SRCDIR)/enums.hh $(SRCDIR)/info.hh $(SRCDIR)/magic.hh $(SRCDIR)/map.hh $(SRCDIR)/objects.hh $(SRCDIR)/script.hh $(SRCDIR)/stubs.hh $(SRCDIR)/thread.hh
$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/config.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/script.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/thread.obj $(BUILDDIR)/time.obj $(BUILDDIR)/util.obj
$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/config.obj $(BUILDDIR)/cract.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/crmain.obj $(BUILDDIR)/crplayer.obj $(BUILDDIR)/crskill.obj $(BUILDDIR)/info.obj $(BUILDDIR)/magic.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/script.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/strings.obj $(BUILDDIR)/thread.obj $(BUILDDIR)/time.obj $(BUILDDIR)/util.obj
$(CC) -c $(CFLAGS) $(LFLAGS) -o $@ $^
$(BUILDDIR)/config.obj: $(SRCDIR)/config.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
$(BUILDDIR)/cract.obj: $(SRCDIR)/cract.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<
$(BUILDDIR)/crcombat.obj: $(SRCDIR)/crcombat.cc $(HEADERS)
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -o $@ $<

12
TODO.md
View File

@ -1,8 +1,12 @@
## TODO NEXT
- merge creature headers into a single one CR.HH
- this should help preventing dependency cycles and centralize creature data
structures and globals in a single place
- CRMAIN.CC, CRPLAYER.CC, CRNONPL.CC, CRACT.CC
- CRACT.CC
- CRMAIN.CC
- TCreature::CreatureMoveStimulus
- FINDCREATURES
- KILL STATS
- RACE
- RAIDS
- CRPLAYER.CC, CRNONPL.CC
- HOUSES.CC
- INFO.CC
- OPERATE.CC

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,9 @@
struct TXTEASymmetricKey {
int (**_vptr.TXTEASymmetricKey)(...); // VTABLE?
uint8 m_SymmetricKey[16];
uchar m_SymmetricKey[16];
};
// NOTE(fusion): Some minimal processing up until here. Then I realized the file would be huge.
//==================================================================================================
//==================================================================================================
//==================================================================================================
struct TDatabasePoolConnection {
TDatabaseConnectionPool *DatabaseConnectionPool;
TDatabaseConnection *DatabaseConnection;
@ -131,16 +125,6 @@ struct TDelayedMail {
int PacketSize;
};
struct TShortwayPoint {
int x;
int y;
int Waypoints;
int Waylength;
int Heuristic;
struct TShortwayPoint *Predecessor;
struct TShortwayPoint *NextToExpand;
};
struct TMonsterhome {
int Race;
int x;
@ -153,8 +137,6 @@ struct TMonsterhome {
int Timer;
};
struct TChannel {
ulong Moderator;
char ModeratorName[30];
@ -394,15 +376,3 @@ struct TRSAPrivateKey {
struct vlong m_DP;
struct vlong m_DQ;
};
struct TShortway {
struct matrix<TShortwayPoint> *Map;
struct TShortwayPoint *FirstToExpand;
struct TCreature *cr;
int VisibleX;
int VisibleY;
int StartX;
int StartY;
int StartZ;
int MinWaypoints;
};

View File

@ -7,50 +7,54 @@
struct TConnection;
struct TKnownCreature {
KNOWNCREATURESTATE State;
uint32 CreatureID;
TKnownCreature *Next;
TConnection *Connection;
KNOWNCREATURESTATE State;
uint32 CreatureID;
TKnownCreature *Next;
TConnection *Connection;
};
struct TConnection {
uint8 InData[2048];
int InDataSize;
bool SigIOPending;
bool WaitingForACK;
uint8 OutData[16384];
uint8 field5_0x4806;
uint8 field6_0x4807;
int NextToSend;
int NextToCommit;
int NextToWrite;
bool Overflow;
bool WillingToSend;
uint8 field12_0x4816;
uint8 field13_0x4817;
TConnection *NextSendingConnection;
uint32 RandomSeed;
void Logout(int Delay, bool StopFight);
// DATA
// =================
uint8 InData[2048];
int InDataSize;
bool SigIOPending;
bool WaitingForACK;
uint8 OutData[16384];
uint8 field5_0x4806;
uint8 field6_0x4807;
int NextToSend;
int NextToCommit;
int NextToWrite;
bool Overflow;
bool WillingToSend;
uint8 field12_0x4816;
uint8 field13_0x4817;
TConnection *NextSendingConnection;
uint32 RandomSeed;
CONNECTIONSTATE State;
pid_t PID;
int Socket;
char IPAddress[16];
// TXTEASymmetricKey SymmetricKey; // TODO
bool ConnectionIsOk;
bool ClosingIsDelayed;
uint8 field23_0x4852;
uint8 field24_0x4853;
uint32 TimeStamp;
uint32 TimeStampAction;
int TerminalType;
int TerminalVersion;
int TerminalOffsetX;
int TerminalOffsetY;
int TerminalWidth;
int TerminalHeight;
uint32 CharacterID;
char Name[31];
uint8 field35_0x4897;
TKnownCreature KnownCreatureTable[150];
pid_t PID;
int Socket;
char IPAddress[16];
// TXTEASymmetricKey SymmetricKey; // TODO
bool ConnectionIsOk;
bool ClosingIsDelayed;
uint8 field23_0x4852;
uint8 field24_0x4853;
uint32 TimeStamp;
uint32 TimeStampAction;
int TerminalType;
int TerminalVersion;
int TerminalOffsetX;
int TerminalOffsetY;
int TerminalWidth;
int TerminalHeight;
uint32 CharacterID;
char Name[31];
uint8 field35_0x4897;
TKnownCreature KnownCreatureTable[150];
};
#endif // TIBIA_CONNECTION_HH_

View File

@ -137,10 +137,10 @@ struct priority_queue{
delete Entry;
}
void insert(K Key, T *Data){
void insert(K Key, T Data){
this->Entries += 1;
int CurrentIndex = this->Entries;
*this->Entry->at(CurrentIndex) = {Key, *Data};
*this->Entry->at(CurrentIndex) = {Key, Data};
while(CurrentIndex > 1){
int ParentIndex = CurrentIndex / 2;
priority_queue_entry<K, T> *Current = this->Entry->at(CurrentIndex);

View File

@ -421,7 +421,7 @@ struct TToDoEntry {
} Turn;
struct{
uint32 Text; // POINTER? Probably a reference from `AddDynamicString`?
uint32 Text;
int Mode;
uint32 Addressee;
bool CheckSpamming;
@ -434,12 +434,45 @@ struct TToDoEntry {
};
struct TCreature: TSkillBase {
// crmain.cc
TCreature(void);
void Attack(void);
int Damage(TCreature *Attacker, int Damage, int DamageType);
void SetID(uint32 CharacterID);
void DelID(void);
void SetInCrList(void);
void DelInCrList(void);
void StartLogout(bool Force, bool StopFight);
int LogoutPossible(void);
void BlockLogout(int Delay, bool BlockProtectionZone);
void ToDoGo(int DestX, int DestY, int DestZ, bool Dest, int MaxSteps);
int GetHealth(void);
int GetSpeed(void);
int Damage(TCreature *Attacker, int Damage, int DamageType);
// cract.cc
bool SetOnMap(void);
bool DelOnMap(void);
void Attack(void);
void Execute(void);
uint32 CalculateDelay(void);
bool ToDoClear(void);
void ToDoAdd(TToDoEntry TD);
void ToDoStop(void);
void ToDoStart(void);
void ToDoYield(void);
void ToDoWait(int Delay);
void ToDoGo(int DestX, int DestY, int DestZ, bool MustReach, int MaxSteps);
void ToDoRotate(int Direction);
void ToDoMove(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum,
int DestX, int DestY, int DestZ, uint8 Count);
void ToDoMove(Object Obj, int DestX, int DestY, int DestZ, uint8 Count);
void ToDoTrade(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum,
uint32 TradePartner);
void ToDoUse(uint8 Count, int ObjX1, int ObjY1, int ObjZ1, ObjectType Type1, uint8 RNum1,
uint8 Dummy, int ObjX2, int ObjY2, int ObjZ2, ObjectType Type2, uint8 RNum2);
void ToDoUse(uint8 Count, Object Obj1, Object Obj2);
void ToDoTurn(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum);
void ToDoAttack(void);
void ToDoTalk(int Mode, const char *Addressee, const char *Text, bool CheckSpamming);
void ToDoChangeState(int NewState);
// VIRTUAL FUNCTIONS
// =================
@ -613,6 +646,9 @@ struct TPlayer: TCreature {
void CheckState(void);
void ClearPlayerkillingMarks(void);
void SaveInventory(void);
// VIRTUAL FUNCTIONS
// =================
// TODO
@ -658,19 +694,42 @@ struct TPlayer: TCreature {
uint32 AddresseesTimes[20];
};
// Creature API
// crmain.cc
// =============================================================================
#define MAX_RACES 512
// crmain.cc
extern TRaceData RaceData[MAX_RACES];
extern int KilledCreatures[MAX_RACES];
extern int KilledPlayers[MAX_RACES];
extern priority_queue<uint32, uint32> ToDoQueue;
// TODO(fusion): These probably belong elsewhere but we should come back to
// this when we're wrapping up creature files.
bool IsCreaturePlayer(uint32 CreatureID);
TCreature *GetCreature(uint32 CreatureID);
TCreature *GetCreature(Object Obj);
void InsertChainCreature(TCreature *Creature, int CoordX, int CoordY);
void DeleteChainCreature(TCreature *Creature);
void MoveChainCreature(TCreature *Creature, int CoordX, int CoordY);
void ProcessCreatures(void);
void ProcessSkills(void);
void MoveCreatures(int Delay);
void AddKillStatistics(int AttackerRace, int DefenderRace);
bool IsRaceValid(int Race);
int GetRaceByName(const char *RaceName);
// crnonpl.cc
// =============================================================================
void InitNonplayer();
void ExitNonplayer();
// crplayer.cc
// =============================================================================
void InitPlayer(void);
void ExitPlayer(void);
// crskill.cc
// =============================================================================
void InitCrskill(void);
void ExitCrskill(void);
#endif //TIBIA_CREATURE_HH_

809
src/cract.cc Normal file
View File

@ -0,0 +1,809 @@
#include "cr.hh"
#include "stubs.hh"
// TShortway
// =============================================================================
struct TShortwayPoint {
TShortwayPoint(void){
this->x = 0;
this->y = 0;
this->Waypoints = -1;
this->Waylength = -1;
this->Heuristic = -1;
this->Predecessor = NULL;
this->NextToExpand = NULL;
}
int x;
int y;
int Waypoints;
int Waylength;
int Heuristic;
TShortwayPoint *Predecessor;
TShortwayPoint *NextToExpand;
};
struct TShortway{
TShortway(TCreature *Creature, int VisibleX, int VisibleY);
~TShortway(void);
void FillMap(void);
void ClearMap(void);
void Expand(TShortwayPoint *Node);
bool Calculate(int DestX, int DestY, bool MustReach, int MaxSteps);
// DATA
// =================
matrix<TShortwayPoint> *Map;
TShortwayPoint *FirstToExpand;
TCreature *Creature;
int VisibleX;
int VisibleY;
int StartX;
int StartY;
int StartZ;
int MinWaypoints;
};
TShortway::TShortway(TCreature *Creature, int VisibleX, int VisibleY){
if(Creature == NULL){
error("TShortway::TShortway: Übergebene Kreatur ist NULL.\n");
return;
}
if(VisibleX < 1 || VisibleX > 100 || VisibleY < 1 || VisibleY > 100){
error("TShortway::TShortway: Ungültiger Sichtbarkeitsbereich %d*%d.\n", VisibleX, VisibleY);
return;
}
this->Creature = Creature;
this->VisibleX = VisibleX;
this->VisibleY = VisibleY;
this->StartX = Creature->posx;
this->StartY = Creature->posy;
this->StartZ = Creature->posz;
this->Map = new matrix<TShortwayPoint>(
-(VisibleX + 1), +(VisibleX + 1),
-(VisibleY + 1), +(VisibleY + 1));
this->FillMap();
}
TShortway::~TShortway(void){
if(this->Map != NULL){
delete this->Map;
}
}
void TShortway::FillMap(void){
this->MinWaypoints = 1000;
for(int X = -this->VisibleX; X <= this->VisibleX; X += 1)
for(int Y = -this->VisibleY; Y <= this->VisibleY; Y += 1){
int FieldX = this->StartX + X;
int FieldY = this->StartY + Y;
int FieldZ = this->StartZ;
int Waypoints = -1;
Object Obj = GetFirstObject(FieldX, FieldY, FieldZ);
if(Obj.exists()){
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(BANK) && !ObjType.getFlag(UNPASS)){
Waypoints = (int)ObjType.getAttribute(WAYPOINTS);
if(Waypoints == 0){
error("TShortway::FillMap: Ungültiger Wegpunkte-Wert %d für Bank %d.\n",
Waypoints, ObjType.TypeID);
Waypoints = -1;
}
if(!this->Creature->MovePossible(FieldX, FieldY, FieldZ, false, false)){
Waypoints = -1;
}
if(Waypoints > 0 && Waypoints < this->MinWaypoints){
this->MinWaypoints = Waypoints;
}
}
}
TShortwayPoint *Node = this->Map->at(X, Y);
Node->x = X;
Node->y = Y;
Node->Waypoints = Waypoints;
}
}
void TShortway::ClearMap(void){
for(int X = -this->VisibleX; X <= this->VisibleX; X += 1)
for(int Y = -this->VisibleY; Y <= this->VisibleY; Y += 1){
TShortwayPoint *Node = this->Map->at(X, Y);
Node->Waylength = INT_MAX;
Node->Heuristic = INT_MAX;
Node->Predecessor = NULL;
Node->NextToExpand = NULL;
}
}
void TShortway::Expand(TShortwayPoint *Node){
if(Node == NULL){
error("TShortway::Expand: Übergebener Knoten ist NULL.\n");
return;
}
this->FirstToExpand = Node->NextToExpand;
int MinNeighborWaylength = Node->Waylength + Node->Waypoints;
if(MinNeighborWaylength >= this->Map->at(0, 0)->Waylength){
return;
}
for(int OffsetX = -1; OffsetX <= 1; OffsetX += 1)
for(int OffsetY = -1; OffsetY <= 1; OffsetY += 1){
if(OffsetX == 0 && OffsetY == 0){
continue;
}
TShortwayPoint *Neighbor = this->Map->at(Node->x + OffsetX, Node->y + OffsetY);
// NOTE(fusion): The minimum neighbor waylength already contains the cost
// of a single step. Diagonal steps are three times more expensive so we
// add waypoints two more times.
int NeighborWaylength = MinNeighborWaylength;
if(OffsetX != 0 && OffsetY != 0){
NeighborWaylength += Node->Waylength * 2;
}
if(NeighborWaylength < Neighbor->Waylength){
Neighbor->Waylength = NeighborWaylength;
Neighbor->Predecessor = Node;
if((Neighbor->x != 0 || Neighbor->y != 0) && Neighbor->Waypoints != -1){
// NOTE(fusion): Remove from expand list if it was already expanded upon.
if(Neighbor->Heuristic != INT_MAX){
TShortwayPoint *Prev = NULL;
TShortwayPoint *Cur = this->FirstToExpand;
while(Cur != NULL && Cur != Neighbor){
Prev = Cur;
Cur = Cur->NextToExpand;
}
if(Cur != Neighbor){
error("TShortway::Expand: Knoten steht nicht in der ExpandList.\n");
}else if(Prev == NULL){
this->FirstToExpand = Neighbor->NextToExpand;
}else{
Prev->NextToExpand = Neighbor->NextToExpand;
}
}
// NOTE(fusion): Compute heuristic using the manhattan distance.
int Distance = std::abs(Neighbor->x) + std::abs(Neighbor->y);
Neighbor->Heuristic = Neighbor->Waylength
+ Neighbor->Waypoints * 1
+ this->MinWaypoints * (Distance - 1);
// NOTE(fusion): Insert into expand list.
{
TShortwayPoint *Prev = NULL;
TShortwayPoint *Cur = this->FirstToExpand;
while(Cur != NULL && Cur->Heuristic < Neighbor->Heuristic){
Prev = Cur;
Cur = Cur->NextToExpand;
}
if(Prev == NULL){
this->FirstToExpand = Neighbor;
}else{
Prev->NextToExpand = Neighbor;
}
Neighbor->NextToExpand = Cur;
}
}
}
}
}
bool TShortway::Calculate(int DestX, int DestY, bool MustReach, int MaxSteps){
if(this->Map == NULL){
error("TShortway::Calculate: Karte existiert nicht.\n");
return false;
}
// NOTE(fusion): Transform dest to relative coordinates.
DestX -= this->StartX;
DestY -= this->StartY;
// NOTE(fusion): Check if already at destination.
if(DestX == 0 && DestY == 0){
return true;
}
// NOTE(fusion): Check if out of range.
if(std::abs(DestX) > this->VisibleX || std::abs(DestY) > this->VisibleY){
return false;
}
// NOTE(fusion): Find shortest path from the destination to the origin.
this->ClearMap();
this->FirstToExpand = this->Map->at(DestX, DestY);
this->FirstToExpand->Waylength = 0;
while(this->FirstToExpand != NULL){
this->Expand(this->FirstToExpand);
}
// NOTE(fusion): Check if the origin was reached from the destination.
TShortwayPoint *Node = this->Map->at(0, 0);
if(Node->Waylength == INT_MAX){
return false;
}
// NOTE(fusion): Walk back from the origin to reconstruct the path.
Node = Node->Predecessor;
while(Node != NULL && MaxSteps > 0){
int MaxDistance = std::max<int>(
std::abs(Node->x - DestX),
std::abs(Node->y - DestY));
if(!MustReach && MaxDistance <= 1){
break;
}
TToDoEntry TD = {};
TD.Code = TDGo;
TD.Go.x = this->StartX + Node->x;
TD.Go.y = this->StartY + Node->y;
TD.Go.z = this->StartZ;
Creature->ToDoAdd(TD);
Node = Node->Predecessor;
MaxSteps -= 1;
}
return true;
}
// TCreature
// =============================================================================
bool TCreature::SetOnMap(void){
int LoginX = this->posx;
int LoginY = this->posy;
int LoginZ = this->posz;
bool Player = (this->Type == PLAYER);
if(!SearchLoginField(&LoginX, &LoginY, &LoginZ, 1, Player)){
bool Found = false;
if(IsHouse(LoginX, LoginY, LoginZ)){
uint16 HouseID = GetHouseID(LoginX, LoginY, LoginZ);
GetExitPosition(HouseID, &LoginX, &LoginY, &LoginZ);
Found = SearchLoginField(&LoginX, &LoginY, &LoginZ, 1, Player);
}
if(!Found){
LoginX = this->startx;
LoginY = this->starty;
LoginZ = this->startz;
}
}
Object Con = GetMapContainer(LoginX, LoginY, LoginZ);
if(Con == NONE){
error("TCreature::SetOnMap: Kartencontainer für Punkt [%d,%d,%d] existiert nicht.\n",
LoginX, LoginY, LoginZ);
return false;
}
this->posx = LoginX;
this->posy = LoginY;
this->posz = LoginZ;
// NOTE(fusion): `Create` automatically sets `this->CrObject` and creates
// its body container slots.
bool Result = true;
try{
Create(Con, TYPEID_CREATURE_CONTAINER, this->ID);
}catch(RESULT r){
error("TCreature::SetOnMap: Kann Kreatur nicht setzen ([%d,%d,%d] - Exception %d).\n",
LoginX, LoginY, LoginZ, r);
if(this->Type == PLAYER){
SendResult(this->Connection, r);
}
Result = false;
}
return Result;
}
bool TCreature::DelOnMap(void){
Object Obj = this->CrObject;
if(Obj == NONE){
return true;
}
// TODO(fusion): I feel `Delete` should also manage `this->CrObject` automatically.
bool Result = true;
this->CrObject = NONE;
try{
Delete(Obj, -1);
}catch(RESULT r){
error("TCreature::DelOnMap: Error Deleting CreatureObject: %d\n", r);
Result = false;
}
return Result;
}
void TCreature::Attack(void){
this->Combat.Attack();
}
//void TCreature::Execute(void);
uint32 TCreature::CalculateDelay(void){
uint32 Delay = 0;
TToDoEntry *TD = this->ToDoList.at(this->ActToDo);
switch(TD->Code){
case TDWait:{
// TODO(fusion): I'm not sure about having `EarliestWalkTime` here.
uint32 WaitTime = TD->Wait.Time;
if(WaitTime < this->EarliestWalkTime){
WaitTime = this->EarliestWalkTime;
}
if(WaitTime > ServerMilliseconds){
Delay = WaitTime - ServerMilliseconds;
}
break;
}
case TDGo:{
if(this->EarliestWalkTime > ServerMilliseconds){
Delay = this->EarliestWalkTime - ServerMilliseconds;
}
break;
}
case TDUse:{
if(TD->Use.Obj2 != 0){
if(this->EarliestMultiuseTime > ServerMilliseconds){
Delay = this->EarliestMultiuseTime - ServerMilliseconds;
}
}
break;
}
case TDAttack:{
uint32 EarliestAttackTime = this->Combat.EarliestAttackTime;
if(EarliestAttackTime < this->EarliestSpellTime){
EarliestAttackTime = this->EarliestSpellTime;
}
if(EarliestAttackTime > ServerMilliseconds){
Delay = EarliestAttackTime - ServerMilliseconds;
}
break;
}
default:{
break;
}
}
return Delay;
}
bool TCreature::ToDoClear(void){
bool SnapbackNecessary = false;
for(int i = 0; i < this->NrToDo; i += 1){
TToDoEntry *TD = this->ToDoList.at(i);
switch(TD->Code){
case TDGo:{
if(this->ActToDo <= i){
SnapbackNecessary = true;
}
break;
}
case TDTalk:{
DeleteDynamicString(TD->Talk.Text);
DeleteDynamicString(TD->Talk.Addressee);
break;
}
case TDChangeState:{
if(this->ActToDo <= i && this->Type == NPC){
ChangeNPCState(this, TD->ChangeState.NewState, false);
}
break;
}
default:{
break;
}
}
}
this->LockToDo = false;
this->ActToDo = 0;
this->NrToDo = 0;
this->Stop = false;
return SnapbackNecessary;
}
void TCreature::ToDoAdd(TToDoEntry TD){
if(this->LockToDo){
if(this->ToDoClear() && this->Type == PLAYER){
SendSnapback(this->Connection);
}
}
*this->ToDoList.at(this->NrToDo) = TD;
this->NrToDo += 1;
}
void TCreature::ToDoStop(void){
if(this->LockToDo){
this->Stop = true;
}else if(this->Type == PLAYER){
SendSnapback(this->Connection);
}
}
void TCreature::ToDoStart(void){
if(this->NrToDo != 0){
this->LockToDo = true;
this->ActToDo = 0;
uint32 Delay = this->CalculateDelay();
if(Delay < 1){
Delay = 1;
}
uint32 NextWakeup = ServerMilliseconds + Delay;
ToDoQueue.insert(NextWakeup, this->ID);
this->NextWakeup = NextWakeup;
}
}
void TCreature::ToDoYield(void){
if(!this->LockToDo){
this->ToDoWait(0);
this->ToDoStart();
}
}
void TCreature::ToDoWait(int Delay){
TToDoEntry TD = {};
TD.Code = TDWait;
TD.Wait.Time = ServerMilliseconds + Delay;
this->ToDoAdd(TD);
}
void TCreature::ToDoGo(int DestX, int DestY, int DestZ, bool MustReach, int MaxSteps){
if(this->posz < DestZ){
throw DOWNSTAIRS;
}else if(this->posz > DestZ){
throw UPSTAIRS;
}
if(this->LockToDo){
TToDoEntry *Last = this->ToDoList.at(this->NrToDo - 1);
if(Last->Code == TDGo
&& Last->Go.x == DestX
&& Last->Go.y == DestY
&& Last->Go.z == DestZ){
// TODO(fusion): Why? Shouldn't we just return here?
throw NOERROR;
}
}
int DistanceX = std::abs(DestX - this->posx);
int DistanceY = std::abs(DestY - this->posy);
int MaxDistance = std::max<int>(DistanceX, DistanceY);
if(MaxDistance == 0 || (!MustReach && MaxDistance <= 1)){
return;
}
// NOTE(fusion): The number of steps between two points is the same as the
// their manhattan distance, if we exclude diagonal movement. We can skip
// the path finder if we know we're step away from the destination.
if(DistanceX + DistanceY == 1){
TToDoEntry TD = {};
TD.Code = TDGo;
TD.Go.x = DestX;
TD.Go.y = DestY;
TD.Go.z = DestZ;
this->ToDoAdd(TD);
}else{
int VisibleX = (this->Type == PLAYER) ? 7 : 10;
int VisibleY = (this->Type == PLAYER) ? 7 : 10;
TShortway Shortway(this, VisibleX, VisibleY);
if(!Shortway.Calculate(DestX, DestY, MustReach, MaxSteps)){
this->ToDoClear();
if(this->Type == PLAYER){
SendSnapback(this->Connection);
}
throw NOWAY;
}
}
}
void TCreature::ToDoRotate(int Direction){
if(Direction != DIRECTION_NORTH
&& Direction != DIRECTION_EAST
&& Direction != DIRECTION_SOUTH
&& Direction != DIRECTION_WEST){
throw ERROR;
}
TToDoEntry TD = {};
TD.Code = TDRotate;
TD.Rotate.Direction = Direction;
this->ToDoAdd(TD);
}
void TCreature::ToDoMove(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum,
int DestX, int DestY, int DestZ, uint8 Count){
Object Obj = GetObject(this->ID, ObjX, ObjY, ObjZ, RNum, Type);
if(!Obj.exists()){
throw NOTACCESSIBLE;
}
// TODO(fusion): This could be an inlined function.
if(ObjX != 0xFFFF){ // SPECIAL_COORDINATE ?
if(this->posz > ObjZ){
throw UPSTAIRS;
}else if(this->posz < ObjZ){
throw DOWNSTAIRS;
}
if(!ObjectInRange(this->ID, Obj, 1)){
this->ToDoGo(ObjX, ObjY, ObjZ, false, INT_MAX);
}
}
int Delay = 100;
if(Obj.getObjectType().isCreatureContainer()){
Object DestBank = GetFirstObject(DestX, DestY, DestZ);
if(DestBank == NONE || !DestBank.getObjectType().getFlag(BANK)){
throw NOTACCESSIBLE;
}
TCreature *Creature = GetCreature(Obj);
if(Creature == NULL){
error("TCreature::ToDoMove: Kreatur existiert nicht.\n");
throw ERROR;
}
Delay = 1000;
if(this->EarliestWalkTime > ServerMilliseconds){
Delay += (int)(this->EarliestWalkTime - ServerMilliseconds);
}
}
this->ToDoWait(Delay);
TToDoEntry TD = {};
TD.Code = TDMove;
TD.Move.Obj = Obj.ObjectID;
TD.Move.x = DestX;
TD.Move.y = DestY;
TD.Move.z = DestZ;
TD.Move.Count = Count;
this->ToDoAdd(TD);
}
void TCreature::ToDoMove(Object Obj, int DestX, int DestY, int DestZ, uint8 Count){
if(!Obj.exists()){
throw NOTACCESSIBLE;
}
if(!ObjectInRange(this->ID, Obj, 1)){
this->ToDoGo(DestX, DestY, DestZ, false, INT_MAX);
}
int Delay = 100;
if(Obj.getObjectType().isCreatureContainer()){
// TODO(fusion): We don't add the delay until the earliest walk time
// like in the other variant above.
Delay = 1000;
}
this->ToDoWait(Delay);
TToDoEntry TD = {};
TD.Code = TDMove;
TD.Move.Obj = Obj.ObjectID;
TD.Move.x = DestX;
TD.Move.y = DestY;
TD.Move.z = DestZ;
TD.Move.Count = Count;
this->ToDoAdd(TD);
}
void TCreature::ToDoTrade(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum,
uint32 TradePartner){
Object Obj = GetObject(this->ID, ObjX, ObjY, ObjZ, RNum, Type);
if(!Obj.exists()){
throw NOTACCESSIBLE;
}
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(UNMOVE)){
throw NOTMOVABLE;
}
if(!ObjType.getFlag(TAKE)){
throw NOTTAKABLE;
}
if(ObjType.isCreatureContainer()){
error("TCreature::ToDoTrade: Objekt ist eine Kreatur.\n");
throw ERROR;
}
if(TradePartner == 0){
error("TCreature::ToDoTrade: Handelspartner ist Null.\n");
throw ERROR;
}
TCreature *Creature = GetCreature(TradePartner);
if(Creature == NULL){
throw PLAYERNOTONLINE;
}
if(Creature->Type != PLAYER){
error("TCreature::ToDoTrade: Handelspartner von %s ist kein Spieler.\n", this->Name);
throw ERROR;
}
// TODO(fusion): This could be an inlined function.
if(ObjX != 0xFFFF){ // SPECIAL_COORDINATE ?
if(this->posz > ObjZ){
throw UPSTAIRS;
}else if(this->posz < ObjZ){
throw DOWNSTAIRS;
}
if(!ObjectInRange(this->ID, Obj, 1)){
this->ToDoGo(ObjX, ObjY, ObjZ, false, INT_MAX);
}
}
TToDoEntry TD = {};
TD.Code = TDTrade;
TD.Trade.Obj = Obj.ObjectID;
TD.Trade.Partner = TradePartner;
this->ToDoAdd(TD);
}
void TCreature::ToDoUse(uint8 Count, int ObjX1, int ObjY1, int ObjZ1, ObjectType Type1, uint8 RNum1,
uint8 Dummy, int ObjX2, int ObjY2, int ObjZ2, ObjectType Type2, uint8 RNum2){
Object Obj1 = GetObject(this->ID, ObjX1, ObjY1, ObjZ1, RNum1, Type1);
if(!Obj1.exists()){
throw NOTACCESSIBLE;
}
Object Obj2 = NONE;
if(Count >= 2){
Obj2 = GetObject(this->ID, ObjX2, ObjY2, ObjZ2, RNum2, Type2);
if(!Obj2.exists()){
throw NOTACCESSIBLE;
}
}
// TODO(fusion): This could be an inlined function.
if(ObjX1 != 0xFFFF){ // SPECIAL_COORDINATE ?
if(this->posz > ObjZ1){
throw UPSTAIRS;
}else if(this->posz < ObjZ1){
throw DOWNSTAIRS;
}
if(!ObjectInRange(this->ID, Obj1, 1)){
this->ToDoGo(ObjX1, ObjY1, ObjZ1, false, INT_MAX);
}
}
this->ToDoWait(100);
TToDoEntry TD = {};
TD.Code = TDUse;
TD.Use.Obj1 = Obj1.ObjectID;
TD.Use.Obj2 = Obj2.ObjectID;
// TODO(fusion): This is the value sent by the client on single object use
// that contains the next free container index.
TD.Use.Dummy = Dummy;
this->ToDoAdd(TD);
}
void TCreature::ToDoUse(uint8 Count, Object Obj1, Object Obj2){
if(!Obj1.exists()){
throw NOTACCESSIBLE;
}
if(Count <= 1){
Obj2 = NONE;
}else if(!Obj2.exists()){
throw NOTACCESSIBLE;
}
if(!ObjectInRange(this->ID, Obj1, 1)){
int ObjX1, ObjY1, ObjZ1;
GetObjectCoordinates(Obj1, &ObjX1, &ObjY1, &ObjZ1);
this->ToDoGo(ObjX1, ObjY1, ObjZ1, false, INT_MAX);
}
this->ToDoWait(100);
TToDoEntry TD = {};
TD.Code = TDUse;
TD.Use.Obj1 = Obj1.ObjectID;
TD.Use.Obj2 = Obj2.ObjectID;
// TODO(fusion): Not sure why we set this value at all.
TD.Use.Dummy = Count;
this->ToDoAdd(TD);
}
void TCreature::ToDoTurn(int ObjX, int ObjY, int ObjZ, ObjectType Type, uint8 RNum){
Object Obj = GetObject(this->ID, ObjX, ObjY, ObjZ, RNum, Type);
if(!Obj.exists()){
throw NOTACCESSIBLE;
}
// TODO(fusion): This could be an inlined function.
if(ObjX != 0xFFFF){ // SPECIAL_COORDINATE ?
if(this->posz > ObjZ){
throw UPSTAIRS;
}else if(this->posz < ObjZ){
throw DOWNSTAIRS;
}
if(!ObjectInRange(this->ID, Obj, 1)){
this->ToDoGo(ObjX, ObjY, ObjZ, false, INT_MAX);
}
}
this->ToDoWait(100);
TToDoEntry TD = {};
TD.Code = TDTurn;
TD.Turn.Obj = Obj.ObjectID;
this->ToDoAdd(TD);
}
void TCreature::ToDoAttack(void){
this->Combat.CanToDoAttack();
if(this->Combat.GetDistance() != 1){
this->ToDoWait(100);
}
TToDoEntry TD = {};
TD.Code = TDAttack;
this->ToDoAdd(TD);
}
void TCreature::ToDoTalk(int Mode, const char *Addressee, const char *Text, bool CheckSpamming){
TToDoEntry TD = {};
if(Text == NULL || Text[0] == 0){
error("TCreature::ToDoTalk: Text ist NULL bei %s.\n", this->Name);
// TODO(fusion): The original code would attempt to call `AddDynamicString`
// even after this check but it doesn't check whether `Text` is NULL and
// calls `strlen` immediately on it which could be a problem.
TD.Talk.Text = 0;
}else{
TD.Talk.Text = AddDynamicString(Text);
}
if(Addressee == NULL){
TD.Talk.Addressee = 0;
}else{
TD.Talk.Addressee = AddDynamicString(Addressee);
}
TD.Code = TDTalk;
TD.Talk.Mode = Mode;
TD.Talk.CheckSpamming = CheckSpamming;
this->ToDoAdd(TD);
}
void TCreature::ToDoChangeState(int NewState){
TToDoEntry TD = {};
TD.Code = TDChangeState;
TD.ChangeState.NewState = NewState;
this->ToDoAdd(TD);
}

View File

@ -307,11 +307,11 @@ int TCombat::GetArmorStrength(void){
int TCombat::GetDistance(void){
int Distance = 0;
if(this->Close != NONE || this->Fist){
Distance = 1;
Distance = 1; // DISTANCE_CLOSE ?
}else if(this->Throw != NONE){
Distance = 2;
Distance = 2; // DISTANCE_THROW ?
}else if(this->Missile != NONE || this->Wand != NONE){
Distance = 3;
Distance = 3; // DISTANCE_RANGE ?
}
return Distance;
}

View File

@ -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;
}

View File

@ -1159,7 +1159,7 @@ bool TSkillBase::NewSkill(uint16 SkillNo, TCreature *Creature){
}
bool TSkillBase::SetSkills(int Race){
if(Race < 0 || Race >= NARRAY(RaceData)){
if(!IsRaceValid(Race)){
error("TSkillBase::SetSkills: Ungültige Rassennummer %d.\n", Race);
return false;
}
@ -1250,3 +1250,13 @@ void TSkillBase::DelTimer(uint16 SkNr){
}
}
}
// Initialization
//==============================================================================
void InitCrskill(void){
// no-op
}
void ExitCrskill(void){
// no-op
}

View File

@ -372,8 +372,7 @@ TSummonImpact::TSummonImpact(TCreature *Actor, int Race, int Maximum){
error("TSummonImpact::TSummonImpact: Actor ist NULL.\n");
}
// TODO(fusion): I think there might be a `IsRaceValid` function that was inlined.
if(Race < 0 || Race >= NARRAY(RaceData)){
if(!IsRaceValid(Race)){
error("TSummonImpact::TSummonImpact: Ungültige Rassennummer %d.\n", Race);
}
@ -387,7 +386,7 @@ void TSummonImpact::handleField(int x, int y, int z){
int Race = this->Race;
int Maximum = this->Maximum;
if(Actor != NULL
&& Race >= 0 && Race < NARRAY(RaceData)
&& IsRaceValid(Race)
&& Actor->SummonedCreatures < Maximum){
int x, y, z;
if(SearchSummonField(&x, &y, &z, 2)){
@ -1870,7 +1869,7 @@ void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God){
throw ERROR;
}
if(Race < 1 || Race >= NARRAY(RaceData)){
if(!IsRaceValid(Race)){
error("SummonCreature: Ungültige Rassennummer %d übergeben.\n", Race);
throw ERROR;
}

View File

@ -8,21 +8,21 @@
#include <dirent.h>
static int OBCount;
static int SectorXMin;
static int SectorXMax;
static int SectorYMin;
static int SectorYMax;
static int SectorZMin;
static int SectorZMax;
static int RefreshedCylinders;
static int NewbieStartPositionX;
static int NewbieStartPositionY;
static int NewbieStartPositionZ;
static int VeteranStartPositionX;
static int VeteranStartPositionY;
static int VeteranStartPositionZ;
int SectorXMin;
int SectorXMax;
int SectorYMin;
int SectorYMax;
int SectorZMin;
int SectorZMax;
int RefreshedCylinders;
int NewbieStartPositionX;
int NewbieStartPositionY;
int NewbieStartPositionZ;
int VeteranStartPositionX;
int VeteranStartPositionY;
int VeteranStartPositionZ;
static int OBCount;
static matrix3d<TSector*> *Sector;
static TObjectBlock **ObjectBlock;
static TObject *FirstFreeObject;

View File

@ -97,6 +97,21 @@ struct TCronEntry {
int Next;
};
// NOTE(fusion): Map config values.
extern int SectorXMin;
extern int SectorXMax;
extern int SectorYMin;
extern int SectorYMax;
extern int SectorZMin;
extern int SectorZMax;
extern int RefreshedCylinders;
extern int NewbieStartPositionX;
extern int NewbieStartPositionY;
extern int NewbieStartPositionZ;
extern int VeteranStartPositionX;
extern int VeteranStartPositionY;
extern int VeteranStartPositionZ;
// NOTE(fusion): Cron management functions. Most for internal use.
Object CronCheck(void);
void CronExpire(Object Obj, int Delay);

View File

@ -18,6 +18,7 @@ extern void AbortWriter(void);
extern void AnnounceChangedCreature(uint32 CreatureID, int Type);
extern void BroadcastMessage(int Mode, const char *Text, ...) ATTR_PRINTF(2, 3);
extern void Change(Object Obj, ObjectType NewType, uint32 Value);
extern void ChangeNPCState(TCreature *Npc, int NewState, bool Stimulus);
extern void CharacterDeathOrder(TCreature *Creature, int OldLevel,
uint32 Offender, const char *Remark, bool Unjustified);
extern bool CheckRight(uint32 CreatureID, RIGHT Right);
@ -33,11 +34,11 @@ extern void CreatePool(Object Con, ObjectType Type, uint32 Value);
extern void Delete(Object Obj, int Count);
extern bool FieldPossible(int x, int y, int z, int FieldType);
extern Object GetBodyObject(uint32 CreatureID, int Position);
extern TCreature *GetCreature(uint32 CreatureID);
extern TCreature *GetCreature(Object Obj);
extern void GetExitPosition(uint16 HouseID, int *x, int *y, int *z);
extern TConnection *GetFirstConnection(void);
extern TConnection *GetNextConnection(void);
extern const char *GetName(Object Obj);
extern Object GetObject(uint32 CreatureID, int x, int y, int z, int RNum, ObjectType Type);
extern TPlayer *GetPlayer(uint32 CreatureID);
extern bool GetRaceNoConvince(int Race);
extern bool GetRaceNoIllusion(int Race);
@ -51,25 +52,24 @@ extern void GraphicalEffect(Object Obj, int Type);
extern int IdentifyPlayer(const char *Name, bool ExactMatch, bool IgnoreGamemasters, TPlayer **Player);
extern void InitLog(const char *ProtocolName);
extern void KickGuest(uint16 HouseID, TPlayer *Host, TPlayer *Guest);
extern bool LagDetected(void);
extern void LoadMonsterRaid(const char *FileName, int Start,
bool *Type, int *Date, int *Interval, int *Duration);
extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3);
extern void LogoutAllPlayers(void);
extern void Missile(Object Start, Object Dest, int Type);
extern void Move(uint32 CreatureID, Object Obj, Object Con, int Count, bool NoMerge, Object Ignore);
extern void MoveCreatures(int Delay);
extern void NetLoadCheck(void);
extern void NetLoadSummary(void);
extern void NotifyAllCreatures(Object Obj, int Type, Object OldCon);
extern int ObjectDistance(Object Obj1, Object Obj2);
extern bool ObjectInRange(uint32 CreatureID, Object Obj, int Range);
extern void ProcessCommunicationControl(void);
extern void ProcessConnections(void);
extern void ProcessCreatures(void);
extern void ProcessCronSystem(void);
extern void ProcessMonsterhomes(void);
extern void ProcessMonsterRaids(void);
extern void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails);
extern void ProcessSkills(void);
extern void ProcessWriterThreadReplies(void);
extern void ReceiveData(void);
extern void RefreshCylinders(void);
@ -77,6 +77,7 @@ extern void RefreshMap(void);
extern void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Data, int Size);
extern void SavePlayerDataOrder(void);
extern bool SearchFlightField(uint32 FugitiveID, uint32 PursuerID, int *x, int *y, int *z);
extern bool SearchLoginField(int *x, int *y, int *z, int Distance, bool Player);
extern bool SearchSummonField(int *x, int *y, int *z, int Distance);
extern void SendAll(void);
extern void SendAmbiente(TConnection *Connection);
@ -89,6 +90,7 @@ extern void SendPlayerData(TConnection *Connection);
extern void SendPlayerSkills(TConnection *Connection);
extern void SendPlayerState(TConnection *Connection, uint8 State);
extern void SendResult(TConnection *Connection, RESULT r);
extern void SendSnapback(TConnection *Connection);
extern void ShowGuestList(uint16 HouseID, TPlayer *Player, char *Buffer);
extern void ShowSubownerList(uint16 HouseID, TPlayer *Player, char *Buffer);
extern void ShowNameDoor(Object Door, TPlayer *Player, char *Buffer);