aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-05-20 16:41:03 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-05-20 16:41:03 -0300
commit42be37f4ad99fc8580415fed89939e305d56193e (patch)
tree41303aace3c214f925a43fec6a857f313196e38d /src
parent7fe30185df21617cf44100f89db462cd4501050e (diff)
downloadgame-42be37f4ad99fc8580415fed89939e305d56193e.tar.gz
game-42be37f4ad99fc8580415fed89939e305d56193e.zip
implement `main.cc`, `shm.cc`, and `time.cc` + overall tweeks
Diffstat (limited to 'src')
-rw-r--r--src/config.cc6
-rw-r--r--src/connection.hh56
-rw-r--r--src/containers.hh338
-rw-r--r--src/containers/matrix.hh132
-rw-r--r--src/containers/priority_queue.hh94
-rw-r--r--src/containers/vector.hh123
-rw-r--r--src/creature.cc2
-rw-r--r--src/creature.hh61
-rw-r--r--src/enums.hh198
-rw-r--r--src/main.cc569
-rw-r--r--src/main.hh58
-rw-r--r--src/map.hh26
-rw-r--r--src/monster.hh88
-rw-r--r--src/player.hh9
-rw-r--r--src/shm.cc385
-rw-r--r--src/skill.cc6
-rw-r--r--src/skill.hh14
-rw-r--r--src/time.cc96
-rw-r--r--src/util.cc42
19 files changed, 1923 insertions, 380 deletions
diff --git a/src/config.cc b/src/config.cc
new file mode 100644
index 0000000..36917cb
--- /dev/null
+++ b/src/config.cc
@@ -0,0 +1,6 @@
+#include "main.hh"
+
+int Beat = 0;
+int DebugLevel = 0;
+
+//
diff --git a/src/connection.hh b/src/connection.hh
new file mode 100644
index 0000000..724ac91
--- /dev/null
+++ b/src/connection.hh
@@ -0,0 +1,56 @@
+#ifndef TIBIA_CONNECTION_HH_
+#define TIBIA_CONNECTION_HH_ 1
+
+#include "main.hh"
+#include "enums.hh"
+
+struct TConnection;
+
+struct TKnownCreature {
+ 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;
+ 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];
+};
+
+#endif // TIBIA_CONNECTION_HH_
diff --git a/src/containers.hh b/src/containers.hh
new file mode 100644
index 0000000..f249c81
--- /dev/null
+++ b/src/containers.hh
@@ -0,0 +1,338 @@
+#ifndef TIBIA_CONTAINERS_HH_
+#define TIBIA_CONTAINERS_HH_ 1
+
+#include "main.hh"
+
+// NOTE(fusion): What the actual fuck. This is an ever growing dynamic array
+// with each access through `operator()` growing it to make sure the requested
+// index is valid, even for negative indices.
+template<typename T>
+struct vector{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ vector(int min, int max, int block){
+ int space = (max - min) + 1;
+ if(space < 1){
+ error("vector: Ungueltige Feldgroesse %d bis %d.\n", min, max);
+ space = 1;
+ }
+
+ if(block < 0){
+ error("vector: Ungueltige Blockgroesse %d.\n", block);
+ block = 0;
+ }
+
+ this->min = min;
+ this->max = max;
+ this->start = min;
+ this->space = space;
+ this->block = block;
+ this->initialized = false;
+ this->entry = new T[this->space];
+ }
+
+ vector(int min, int max, int block, T init) : vector(min, max, block) {
+ this->initialized = true;
+ this->init = init;
+ for(int i = 0; i < this->space; i += 1){
+ this->entry[i] = init;
+ }
+ }
+
+ // TODO(fusion): Probably missing some inlined destructor?
+
+ T *at(int index){
+ // TODO(fusion): This is probably not the best way to achieve this.
+
+ while(index < this->start){
+ int increment = this->block;
+ if(increment == 0){
+ increment = this->space;
+ }
+
+ T *entry = new T[this->space + increment];
+ for(int i = this->min; i <= this->max; i += 1){
+ int old_index = i - this->start;
+ int new_index = old_index + increment;
+
+ // TODO(fusion): Do we actually need to swap elements here? I'm
+ // assuming some non-trivial structures that would invoke their
+ // destructors when `this->entry` gets deleted just below.
+ std::swap(entry[new_index], this->entry[old_index]);
+ }
+
+ if(this->entry != NULL){
+ delete[] this->entry;
+ }
+ this->entry = entry;
+ this->start -= increment;
+ this->space += increment;
+ }
+
+ while(index >= (this->start + this->space)){
+ int increment = this->block;
+ if(increment == 0){
+ increment = this->space;
+ }
+
+ T *entry = new[this->space + increment];
+ for(int i = this->min; i <= this->max; i += 1){
+ int old_index = i - this->start;
+ int new_index = old_index;
+
+ // TODO(fusion): Same as above.
+ std::swap(entry[new_index], this->entry[old_index]);
+ }
+
+ if(this->entry != NULL){
+ delete[] this->entry;
+ }
+ this->entry = entry;
+ this->space += increment;
+ }
+
+ while(index < this->min){
+ this->min -= 1;
+ if(this->initialized){
+ this->entry[this->min - this->start] = this->init;
+ }
+ }
+
+ while(index > this->max){
+ this->max += 1;
+ if(this->initialized){
+ this->entry[this->max - this->start] = this->init;
+ }
+ }
+
+ return &this->entry[index - this->start];
+ }
+
+ // DATA
+ // =========================================================================
+ int min;
+ int max;
+ int start;
+ int space;
+ int block;
+ bool initialized;
+ T init;
+ T *entry;
+};
+
+template<typename K, typename T>
+struct priority_queue_entry{
+ K Key;
+ T Data;
+};
+
+template<typename K, typename T>
+struct priority_queue{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ priority_queue(int capacity, int increment){
+ Entry = new vector<priority_queue_entry<K, T>>(1, capacity, increment);
+ Entries = 0;
+ }
+
+ // TODO(fusion): Probably missing some inlined destructor?
+
+ void insert(K Key, T *Data){
+ this->Entries += 1;
+ int CurrentIndex = this->Entries;
+ *this->Entry->at(CurrentIndex) = {Key, *Data};
+ while(CurrentIndex > 1){
+ int ParentIndex = CurrentIndex / 2;
+ priority_queue_entry<K, T> *Current = this->Entry->at(CurrentIndex);
+ priority_queue_entry<K, T> *Parent = this->Entry->at(ParentIndex);
+ if(Parent->Key <= Current->Key)
+ break;
+ std::swap(*Current, *Parent);
+ CurrentIndex = ParentIndex;
+ }
+ }
+
+ void deleteMin(void){
+ if(this->Entries < 1){
+ error("priority_queue::deleteMin: Warteschlange ist leer.\n");
+ return;
+ }
+
+ if(this->Entries > 1){
+ int CurrentIndex = 1;
+ int LastIndex = this->Entries;
+ std::swap(*this->Entry->at(CurrentIndex),
+ *this->Entry->at(LastIndex));
+
+ // TODO(fusion): This may be an oversight but the decompiled version
+ // checks in the loop below would INCLUDE `LastIndex`, which I assume
+ // is a bug? The first and last elements were just swapped and what
+ // was the first element in the queue is now at the end and should be
+ // considered "removed".
+ while(1){
+ int SmallestIndex = CurrentIndex * 2;
+ if(SmallestIndex >= LastIndex){
+ break;
+ }
+
+ priority_queue_entry<K, T> *Smallest = this->Entry->at(SmallestIndex);
+ if((SmallestIndex + 1) < LastIndex){
+ priority_queue_entry<K, T> *Other = this->Entry->at(SmallestIndex + 1);
+ if(Other->Key < Smallest->Key){
+ Smallest = Other;
+ SmallestIndex += 1;
+ }
+ }
+
+ priority_queue_entry<K, T> *Current = this->Entry->at(CurrentIndex);
+ if(Current->Key <= Smallest->Key){
+ break;
+ }
+
+ std::swap(*Current, *Smallest);
+ CurrentIndex = SmallestIndex;
+ }
+ }
+
+ this->Entries -= 1;
+ }
+
+ // DATA
+ // =========================================================================
+ vector<priority_queue_entry<K, T>> *Entry;
+ int Entries;
+};
+
+// TODO(fusion): We only use this structure two global queues:
+// priority_queue<uint32, uint32> ToDoQueue(5000, 1000);
+// priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100);
+
+typedef<typename T>
+struct matrix{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ matrix(int xmin, int xmax, int ymin, int ymax){
+ int dx = (xmax - xmin) + 1;
+ int dy = (ymax - ymin) + 1;
+
+ if(dx < 1 || dy < 1){
+ error("matrix: Ungueltige Feldgroesse %d..%d, %d..%d.\n", xmin, xmax, ymin, ymax);
+
+ if(dx < 1){
+ dx = 1;
+ }
+
+ if(dy < 1){
+ dy = 1;
+ }
+ }
+
+ this->xmin = xmin;
+ this->ymin = ymin;
+ this->dx = dx;
+ this->dy = dy;
+ this->entry = new T[dx * dy];
+ }
+
+ matrix(int xmin, int xmax, int ymin, int ymax, T init) : matrix(xmin, xmax, ymin, ymax) {
+ int count = this->dx * this->dy;
+ for(int i = 0; i < count; i += 1){
+ this->entry[i] = init;
+ }
+ }
+
+ // TODO(fusion): Probably missing some inlined destructor?
+
+ T *at(int x, int y){
+ int xoffset = x - this->xmin;
+ int yoffset = y - this->ymin;
+ if(xoffset < 0 || xoffset >= this->dx || yoffset < 0 || yoffset >= this->dy){
+ error("matrix::operator(): Ungueltiger Index %d/%d.\n", x, y);
+ return &this->entry[0];
+ }else{
+ // TODO(fusion): Are we really storing this in row major order?
+ return &this->entry[yoffset * this->dx + xoffset];
+ }
+ }
+
+ // DATA
+ // =========================================================================
+ int xmin;
+ int ymin;
+ int dx;
+ int dy;
+ T *entry;
+};
+
+typedef<typename T>
+struct matrix3d{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax){
+ int dx = (xmax - xmin) + 1;
+ int dy = (ymax - ymin) + 1;
+ int dz = (zmax - zmin) + 1;
+
+ if(dx < 1 || dy < 1 || dz < 1){
+ error("matrix3d: Ungueltige Feldgroesse %d..%d, %d..%d, %d..%d.\n",
+ xmin, xmax, ymin, ymax, zmin, zmax);
+
+ if(dx < 1){
+ dx = 1;
+ }
+
+ if(dy < 1){
+ dy = 1;
+ }
+
+ if(dz < 1){
+ dz = 1;
+ }
+ }
+
+ this->xmin = xmin;
+ this->ymin = ymin;
+ this->dx = dx;
+ this->dy = dy;
+ this->entry = new T[dx * dy];
+ }
+
+ matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, T init)
+ : matrix(xmin, xmax, ymin, ymax, zmin, zmax) {
+ int count = this->dx * this->dy * this->dz;
+ for(int i = 0; i < count; i += 1){
+ this->entry[i] = init;
+ }
+ }
+
+ // TODO(fusion): Probably missing some inlined destructor?
+
+ T *at(int x, int y, int z){
+ int xoffset = x - this->xmin;
+ int yoffset = y - this->ymin;
+ int zoffset = z - this->zmin;
+ if(xoffset < 0 || xoffset >= this->dx
+ || yoffset < 0 || yoffset >= this->dy
+ || zoffset < 0 || zoffset >= this->dz){
+ error("matrix3d::operator(): Ungueltiger Index %d/%d/%d.\n", x, y, z);
+ return &this->entry[0];
+ }else{
+ // TODO(fusion): Same as `matrix::at` on the XY plane.
+ return &this->entry[zoffset * this->dx * this->dy
+ + yoffset * this->dx
+ + xoffset];
+ }
+ }
+
+ // DATA
+ // =========================================================================
+ int xmin;
+ int ymin;
+ int zmin;
+ int dx;
+ int dy;
+ int dz;
+ T *entry;
+};
+
+#endif //TIBIA_CONTAINERS_HH_ \ No newline at end of file
diff --git a/src/containers/matrix.hh b/src/containers/matrix.hh
deleted file mode 100644
index bf255ff..0000000
--- a/src/containers/matrix.hh
+++ /dev/null
@@ -1,132 +0,0 @@
-#ifndef TIBIA_MATRIX_HH_
-#define TIBIA_MATRIX_HH_ 1
-
-typedef<typename T>
-struct matrix{
- // REGULAR FUNCTIONS
- // =========================================================================
- matrix(int xmin, int xmax, int ymin, int ymax){
- int dx = (xmax - xmin) + 1;
- int dy = (ymax - ymin) + 1;
-
- if(dx < 1 || dy < 1){
- error("matrix: Ungueltige Feldgroesse %d..%d, %d..%d.\n", xmin, xmax, ymin, ymax);
-
- if(dx < 1){
- dx = 1;
- }
-
- if(dy < 1){
- dy = 1;
- }
- }
-
- this->xmin = xmin;
- this->ymin = ymin;
- this->dx = dx;
- this->dy = dy;
- this->entry = new T[dx * dy];
- }
-
- matrix(int xmin, int xmax, int ymin, int ymax, T init) : matrix(xmin, xmax, ymin, ymax) {
- int count = this->dx * this->dy;
- for(int i = 0; i < count; i += 1){
- this->entry[i] = init;
- }
- }
-
- // TODO(fusion): Probably missing some inlined destructor?
-
- T *at(int x, int y){
- int xoffset = x - this->xmin;
- int yoffset = y - this->ymin;
- if(xoffset < 0 || xoffset >= this->dx || yoffset < 0 || yoffset >= this->dy){
- error("matrix::operator(): Ungueltiger Index %d/%d.\n", x, y);
- return &this->entry[0];
- }else{
- // TODO(fusion): Are we really storing this in row major order?
- return &this->entry[yoffset * this->dx + xoffset];
- }
- }
-
- // DATA
- // =========================================================================
- int xmin;
- int ymin;
- int dx;
- int dy;
- T *entry;
-};
-
-typedef<typename T>
-struct matrix3d{
- // REGULAR FUNCTIONS
- // =========================================================================
- matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax){
- int dx = (xmax - xmin) + 1;
- int dy = (ymax - ymin) + 1;
- int dz = (zmax - zmin) + 1;
-
- if(dx < 1 || dy < 1 || dz < 1){
- error("matrix3d: Ungueltige Feldgroesse %d..%d, %d..%d, %d..%d.\n",
- xmin, xmax, ymin, ymax, zmin, zmax);
-
- if(dx < 1){
- dx = 1;
- }
-
- if(dy < 1){
- dy = 1;
- }
-
- if(dz < 1){
- dz = 1;
- }
- }
-
- this->xmin = xmin;
- this->ymin = ymin;
- this->dx = dx;
- this->dy = dy;
- this->entry = new T[dx * dy];
- }
-
- matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, T init)
- : matrix(xmin, xmax, ymin, ymax, zmin, zmax) {
- int count = this->dx * this->dy * this->dz;
- for(int i = 0; i < count; i += 1){
- this->entry[i] = init;
- }
- }
-
- // TODO(fusion): Probably missing some inlined destructor?
-
- T *at(int x, int y, int z){
- int xoffset = x - this->xmin;
- int yoffset = y - this->ymin;
- int zoffset = z - this->zmin;
- if(xoffset < 0 || xoffset >= this->dx
- || yoffset < 0 || yoffset >= this->dy
- || zoffset < 0 || zoffset >= this->dz){
- error("matrix3d::operator(): Ungueltiger Index %d/%d/%d.\n", x, y, z);
- return &this->entry[0];
- }else{
- // TODO(fusion): Same as `matrix::at` on the XY plane.
- return &this->entry[zoffset * this->dx * this->dy
- + yoffset * this->dx
- + xoffset];
- }
- }
-
- // DATA
- // =========================================================================
- int xmin;
- int ymin;
- int zmin;
- int dx;
- int dy;
- int dz;
- T *entry;
-};
-
-#endif //TIBIA_MATRIX_HH_
diff --git a/src/containers/priority_queue.hh b/src/containers/priority_queue.hh
deleted file mode 100644
index b48353e..0000000
--- a/src/containers/priority_queue.hh
+++ /dev/null
@@ -1,94 +0,0 @@
-#ifndef TIBIA_PRIORITY_QUEUE_HH_
-#define TIBIA_PRIORITY_QUEUE_HH_ 1
-
-#include "main.hh"
-#include "vector.hh"
-
-template<typename K, typename T>
-struct priority_queue_entry{
- K Key;
- T Data;
-};
-
-template<typename K, typename T>
-struct priority_queue{
- // REGULAR FUNCTIONS
- // =========================================================================
- priority_queue(int capacity, int increment){
- Entry = new vector<priority_queue_entry<K, T>>(1, capacity, increment);
- Entries = 0;
- }
-
- // TODO(fusion): Probably missing some inlined destructor?
-
- void insert(K Key, T *Data){
- this->Entries += 1;
- int CurrentIndex = this->Entries;
- *this->Entry->at(CurrentIndex) = {Key, *Data};
- while(CurrentIndex > 1){
- int ParentIndex = CurrentIndex / 2;
- priority_queue_entry<K, T> *Current = this->Entry->at(CurrentIndex);
- priority_queue_entry<K, T> *Parent = this->Entry->at(ParentIndex);
- if(Parent->Key <= Current->Key)
- break;
- std::swap(*Current, *Parent);
- CurrentIndex = ParentIndex;
- }
- }
-
- void deleteMin(void){
- if(this->Entries < 1){
- error("priority_queue::deleteMin: Warteschlange ist leer.\n");
- return;
- }
-
- if(this->Entries > 1){
- int CurrentIndex = 1;
- int LastIndex = this->Entries;
- std::swap(*this->Entry->at(CurrentIndex),
- *this->Entry->at(LastIndex));
-
- // TODO(fusion): This may be an oversight but the decompiled version
- // checks in the loop below would INCLUDE `LastIndex`, which I assume
- // is a bug? The first and last elements were just swapped and what
- // was the first element in the queue is now at the end and should be
- // considered "removed".
- while(1){
- int SmallestIndex = CurrentIndex * 2;
- if(SmallestIndex >= LastIndex){
- break;
- }
-
- priority_queue_entry<K, T> *Smallest = this->Entry->at(SmallestIndex);
- if((SmallestIndex + 1) < LastIndex){
- priority_queue_entry<K, T> *Other = this->Entry->at(SmallestIndex + 1);
- if(Other->Key < Smallest->Key){
- Smallest = Other;
- SmallestIndex += 1;
- }
- }
-
- priority_queue_entry<K, T> *Current = this->Entry->at(CurrentIndex);
- if(Current->Key <= Smallest->Key){
- break;
- }
-
- std::swap(*Current, *Smallest);
- CurrentIndex = SmallestIndex;
- }
- }
-
- this->Entries -= 1;
- }
-
- // DATA
- // =========================================================================
- vector<priority_queue_entry<K, T>> *Entry;
- int Entries;
-};
-
-// TODO(fusion): We only use this structure two global queues:
-// priority_queue<uint32, uint32> ToDoQueue(5000, 1000);
-// priority_queue<uint32, TAttackWave*> AttackWaveQueue(100, 100);
-
-#endif //TIBIA_PRIORITY_QUEUE_HH_
diff --git a/src/containers/vector.hh b/src/containers/vector.hh
deleted file mode 100644
index 044ffe1..0000000
--- a/src/containers/vector.hh
+++ /dev/null
@@ -1,123 +0,0 @@
-#ifndef TIBIA_VECTOR_HH_
-#define TIBIA_VECTOR_HH_ 1
-
-#include "main.hh"
-
-// NOTE(fusion): What the actual fuck. This is an ever growing dynamic array
-// with each access through `operator()` growing it to make sure the requested
-// index is valid, even for negative indices.
-template<typename T>
-struct vector{
- // REGULAR FUNCTIONS
- // =========================================================================
- vector(int min, int max, int block){
- int space = (max - min) + 1;
- if(space < 1){
- error("vector: Ungueltige Feldgroesse %d bis %d.\n", min, max);
- space = 1;
- }
-
- if(block < 0){
- error("vector: Ungueltige Blockgroesse %d.\n", block);
- block = 0;
- }
-
- this->min = min;
- this->max = max;
- this->start = min;
- this->space = space;
- this->block = block;
- this->initialized = false;
- this->entry = new T[this->space];
- }
-
- vector(int min, int max, int block, T init) : vector(min, max, block) {
- this->initialized = true;
- this->init = init;
- for(int i = 0; i < this->space; i += 1){
- this->entry[i] = init;
- }
- }
-
- // TODO(fusion): Probably missing some inlined destructor?
-
- T *at(int index){
- // TODO(fusion): This is probably not the best way to achieve this.
-
- while(index < this->start){
- int increment = this->block;
- if(increment == 0){
- increment = this->space;
- }
-
- T *entry = new T[this->space + increment];
- for(int i = this->min; i <= this->max; i += 1){
- int old_index = i - this->start;
- int new_index = old_index + increment;
-
- // TODO(fusion): Do we actually need to swap elements here? I'm
- // assuming some non-trivial structures that would invoke their
- // destructors when `this->entry` gets deleted just below.
- std::swap(entry[new_index], this->entry[old_index]);
- }
-
- if(this->entry != NULL){
- delete[] this->entry;
- }
- this->entry = entry;
- this->start -= increment;
- this->space += increment;
- }
-
- while(index >= (this->start + this->space)){
- int increment = this->block;
- if(increment == 0){
- increment = this->space;
- }
-
- T *entry = new[this->space + increment];
- for(int i = this->min; i <= this->max; i += 1){
- int old_index = i - this->start;
- int new_index = old_index;
-
- // TODO(fusion): Same as above.
- std::swap(entry[new_index], this->entry[old_index]);
- }
-
- if(this->entry != NULL){
- delete[] this->entry;
- }
- this->entry = entry;
- this->space += increment;
- }
-
- while(index < this->min){
- this->min -= 1;
- if(this->initialized){
- this->entry[this->min - this->start] = this->init;
- }
- }
-
- while(index > this->max){
- this->max += 1;
- if(this->initialized){
- this->entry[this->max - this->start] = this->init;
- }
- }
-
- return &this->entry[index - this->start];
- }
-
- // DATA
- // =========================================================================
- int min;
- int max;
- int start;
- int space;
- int block;
- bool initialized;
- T init;
- T *entry;
-};
-
-#endif //TIBIA_VECTOR_HH_
diff --git a/src/creature.cc b/src/creature.cc
index f3acdb8..effac42 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -46,7 +46,7 @@ void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay){
}
// NOTE(fusion): Maintain largest exhaust?
- int EarliestSpellTime = Delay + ServerMilliseconds;
+ uint32 EarliestSpellTime = Delay + ServerMilliseconds;
if(Creature->EarliestSpellTime < EarliestSpellTime){
Creature->EarliestSpellTime = EarliestSpellTime;
}
diff --git a/src/creature.hh b/src/creature.hh
index 8f0917a..2b19836 100644
--- a/src/creature.hh
+++ b/src/creature.hh
@@ -2,6 +2,8 @@
#define TIBIA_CREATURE_HH_ 1
#include "main.hh"
+#include "connection.hh"
+#include "containers.hh"
#include "skill.hh"
struct TCreature;
@@ -13,6 +15,12 @@ struct TCombatEntry{
};
struct TCombat{
+ // REGULAR FUNCTIONS
+ // =========================================================================
+ // TODO
+
+ // DATA
+ // =========================================================================
TCreature *Master;
uint32 EarliestAttackTime;
uint32 EarliestDefendTime;
@@ -36,6 +44,59 @@ struct TCombat{
int LearningPoints;
};
+struct TToDoEntry {
+ ToDoType Code;
+ union{
+ struct{
+ uint32 Time;
+ } Wait;
+
+ struct{
+ int x;
+ int y;
+ int z;
+ } Go;
+
+ struct{
+ int Direction;
+ } Rotate;
+
+ struct{
+ uint32 Obj;
+ int x;
+ int y;
+ int z;
+ int Count;
+ } Move;
+
+ struct{
+ uint32 Obj;
+ uint32 Partner;
+ } Trade;
+
+ struct{
+ uint32 Obj1;
+ uint32 Obj2;
+ int Dummy;
+ } Use;
+
+ struct{
+ uint32 Obj;
+ } Turn;
+
+ struct{
+ uint32 Text; // POINTER? Probably a reference from `AddDynamicString`?
+ int Mode;
+ uint32 Addressee;
+ bool CheckSpamming;
+ } Talk;
+
+ struct{
+ int NewState;
+ } ChangeState;
+ };
+};
+
struct TOutfit{
int OutfitID;
union{
diff --git a/src/enums.hh b/src/enums.hh
index 053a2ed..feb9775 100644
--- a/src/enums.hh
+++ b/src/enums.hh
@@ -12,6 +12,99 @@ enum CreatureType: int {
NPC = 2,
};
+enum CONNECTIONSTATE: int {
+ CONNECTION_FREE = 0,
+ CONNECTION_ASSIGNED = 1,
+ CONNECTION_CONNECTED = 2,
+ CONNECTION_LOGIN = 3,
+ CONNECTION_GAME = 4,
+ CONNECTION_DEAD = 5,
+ CONNECTION_LOGOUT = 6,
+ CONNECTION_DISCONNECTED = 7,
+};
+
+enum FLAG: int {
+ BANK = 0,
+ CLIP = 1,
+ BOTTOM = 2,
+ TOP = 3,
+ CONTAINER = 4,
+ CHEST = 5,
+ CUMULATIVE = 6,
+ USEEVENT = 7,
+ CHANGEUSE = 8,
+ FORCEUSE = 9,
+ MULTIUSE = 10,
+ DISTUSE = 11,
+ MOVEMENTEVENT = 12,
+ COLLISIONEVENT = 13,
+ SEPARATIONEVENT = 14,
+ KEY = 15,
+ KEYDOOR = 16,
+ NAMEDOOR = 17,
+ LEVELDOOR = 18,
+ QUESTDOOR = 19,
+ BED = 20,
+ FOOD = 21,
+ RUNE = 22,
+ INFORMATION = 23,
+ TEXT = 24,
+ WRITE = 25,
+ WRITEONCE = 26,
+ LIQUIDCONTAINER = 27,
+ LIQUIDSOURCE = 28,
+ LIQUIDPOOL = 29,
+ TELEPORTABSOLUTE = 30,
+ TELEPORTRELATIVE = 31,
+ UNPASS = 32,
+ UNMOVE = 33,
+ UNTHROW = 34,
+ UNLAY = 35,
+ AVOID = 36,
+ MAGICFIELD = 37,
+ RESTRICTLEVEL = 38,
+ RESTRICTPROFESSION = 39,
+ TAKE = 40,
+ HANG = 41,
+ HOOKSOUTH = 42,
+ HOOKEAST = 43,
+ ROTATE = 44,
+ DESTROY = 45,
+ CLOTHES = 46,
+ SKILLBOOST = 47,
+ PROTECTION = 48,
+ LIGHT = 49,
+ ROPESPOT = 50,
+ CORPSE = 51,
+ EXPIRE = 52,
+ EXPIRESTOP = 53,
+ WEAROUT = 54,
+ WEAPON = 55,
+ SHIELD = 56,
+ BOW = 57,
+ THROW = 58,
+ WAND = 59,
+ AMMO = 60,
+ ARMOR = 61,
+ HEIGHT = 62,
+ DISGUISE = 63,
+ SHOWDETAIL = 64,
+ SPECIALOBJECT = 65,
+};
+
+enum GAMESTATE: int {
+ GAME_STARTING = 0,
+ GAME_RUNNING = 1,
+ GAME_CLOSING = 2,
+ GAME_ENDING = 3,
+};
+
+enum KNOWNCREATURESTATE: int {
+ KNOWNCREATURE_FREE = 0,
+ KNOWNCREATURE_UPTODATE = 1,
+ KNOWNCREATURE_OUTDATED = 2
+};
+
// NOTE(fusion): Not in debug symbols.
enum PROFESSION: uint8 {
PROFESSION_NONE = 0,
@@ -202,7 +295,26 @@ enum Skill: int {
SKILL_SOUL = 22,
};
-enum TalkMode: int {
+enum SpellShapeType: int {
+ SHAPE_ACTOR = 0,
+ SHAPE_VICTIM = 1,
+ SHAPE_ORIGIN = 2,
+ SHAPE_DESTINATION = 3,
+ SHAPE_ANGLE = 4,
+};
+
+enum SpellImpactType: int {
+ IMPACT_DAMAGE = 0,
+ IMPACT_FIELD = 1,
+ IMPACT_HEALING = 2,
+ IMPACT_SPEED = 3,
+ IMPACT_DRUNKEN = 4,
+ IMPACT_STRENGTH = 5,
+ IMPACT_OUTFIT = 6,
+ IMPACT_SUMMON = 7,
+};
+
+enum TALK_MODE: int {
TALK_SAY = 1,
TALK_WHISPER = 2,
TALK_YELL = 3,
@@ -228,4 +340,88 @@ enum TalkMode: int {
TALK_FAILURE_MESSAGE = 23,
};
+enum ToDoType: int {
+ TDWait = 0,
+ TDGo = 1,
+ TDRotate = 2,
+ TDMove = 3,
+ TDTrade = 4,
+ TDUse = 5,
+ TDTurn = 6,
+ TDAttack = 7,
+ TDTalk = 8,
+ TDChangeState = 9,
+};
+
+enum TYPEATTRIBUTE: int {
+ WAYPOINTS = 0,
+ CAPACITY = 1,
+ CHANGETARGET = 2,
+ KEYDOORTARGET = 3,
+ NAMEDOORTARGET = 4,
+ LEVELDOORTARGET = 5,
+ QUESTDOORTARGET = 6,
+ NUTRITION = 7,
+ INFORMATIONTYPE = 8,
+ FONTSIZE = 9,
+ MAXLENGTH = 10,
+ MAXLENGTHONCE = 11,
+ SOURCELIQUIDTYPE = 12,
+ ABSTELEPORTEFFECT = 13,
+ RELTELEPORTDISPLACEMENT = 14,
+ RELTELEPORTEFFECT = 15,
+ AVOIDDAMAGETYPES = 16,
+ MINIMUMLEVEL = 17,
+ PROFESSIONS = 18,
+ WEIGHT = 19,
+ ROTATETARGET = 20,
+ DESTROYTARGET = 21,
+ BODYPOSITION = 22,
+ SKILLNUMBER = 23,
+ SKILLMODIFICATION = 24,
+ PROTECTIONDAMAGETYPES = 25,
+ DAMAGEREDUCTION = 26,
+ BRIGHTNESS = 27,
+ LIGHTCOLOR = 28,
+ CORPSETYPE = 29,
+ TOTALEXPIRETIME = 30,
+ EXPIRETARGET = 31,
+ TOTALUSES = 32,
+ WEAROUTTARGET = 33,
+ WEAPONTYPE = 34,
+ WEAPONATTACKVALUE = 35,
+ WEAPONDEFENDVALUE = 36,
+ SHIELDDEFENDVALUE = 37,
+ BOWRANGE = 38,
+ BOWAMMOTYPE = 39,
+ THROWRANGE = 40,
+ THROWATTACKVALUE = 41,
+ THROWDEFENDVALUE = 42,
+ THROWMISSILE = 43,
+ THROWSPECIALEFFECT = 44,
+ THROWEFFECTSTRENGTH = 45,
+ THROWFRAGILITY = 46,
+ WANDRANGE = 47,
+ WANDMANACONSUMPTION = 48,
+ WANDATTACKSTRENGTH = 49,
+ WANDATTACKVARIATION = 50,
+ WANDDAMAGETYPE = 51,
+ WANDMISSILE = 52,
+ AMMOTYPE = 53,
+ AMMOATTACKVALUE = 54,
+ AMMOMISSILE = 55,
+ AMMOSPECIALEFFECT = 56,
+ AMMOEFFECTSTRENGTH = 57,
+ ARMORVALUE = 58,
+ ELEVATION = 59,
+ DISGUISETARGET = 60,
+ MEANING = 61,
+};
+
+enum TWorldType: int {
+ NORMAL = 0,
+ NON_PVP = 1,
+ PVP_ENFORCED = 2,
+};
+
#endif //TIBIA_ENUMS_HH_
diff --git a/src/main.cc b/src/main.cc
index e616ce3..e7a5721 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,38 +1,563 @@
#include "main.hh"
+#include <signal.h>
+#include <sys/time.h>
+
+static bool BeADaemon = false;
+static bool Reboot = false;
+static bool SaveMapOn = false;
+
+static int SigAlarmCounter = 0;
+static int SigUsr1Counter = 0;
+
+static sighandler_t handler(int signr, sighandler_t sighandler){
+ struct sigaction act;
+ struct sigaction oldact;
+
+ act.sa_handler = sighandler;
+
+ // TODO(fusion): I feel we should probably use `sigfillset` specially for
+ // signals that share the same handler.
+ sigemptyset(&act.sa_mask);
+
+ // TODO(fusion): We had this weird logic in the decompiled version of this
+ // function but it looks like some GLIBC internal thing, since these values
+ // would mask signals 1020 and 1021 which don't really make sense.
+ //
+ //if(signr == SIGALRM){
+ // act.sa_mask.__val[0x1f] = 0x20000000;
+ //}else{
+ // act.sa_mask.__val[0x1f] = 0x10000000;
+ //}
+ //
+
+ if(sigaction(signr, &act, &oldact) == 0){
+ return oldact.sa_handler;
+ }else{
+ return SIG_ERR;
+ }
+}
+
+static void SigHupHandler(int signr){
+ // no-op (?)
+}
+
+static void SigAbortHandler(int signr){
+ print(1, "SigAbortHandler: schalte Writer-Thread ab.\n");
+ AbortWriter();
+}
+
+static void DefaultHandler(int signr){
+ print(1, "DefaultHandler: Beende Game-Server (SigNr. %d: %s).\n",
+ signr, sigdescr_np(signr));
+
+ handler(SIGINT, SIG_IGN);
+ handler(SIGQUIT, SIG_IGN);
+ handler(SIGTERM, SIG_IGN);
+ handler(SIGXCPU, SIG_IGN);
+ handler(SIGXFSZ, SIG_IGN);
+ handler(SIGPWR, SIG_IGN);
+
+ SaveMapOn = (signr == SIGQUIT) || (signr == SIGTERM) || (signr == SIGPWR);
+ if(signr == SIGTERM){
+ GetRealTime(&Hour, &Minute);
+ RebootTime = (Hour * 60 + Minute + 6) % 1440;
+ CloseGame();
+ }else{
+ EndGame();
+ }
+
+ Reboot = false;
+}
+
+// TODO(fusion): This function was exported in the binary but not referenced anywhere.
+static void ErrorHandler(int signr){
+ error("ErrorHandler: SigNr. %d: %s\n", signr, sigdescr_np(signr));
+ EndGame();
+ LogoutAllPlayers();
+ exit(1);
+}
+
+static void InitSignalHandler(void){
+ int count = 0;
+ count += (handler(SIGHUP, SigHupHandler) != SIG_ERR);
+ count += (handler(SIGINT, DefaultHandler) != SIG_ERR);
+ count += (handler(SIGQUIT, DefaultHandler) != SIG_ERR);
+ count += (handler(SIGABRT, SigAbortHandler) != SIG_ERR);
+ count += (handler(SIGUSR1, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGUSR2, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGPIPE, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGALRM, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGTERM, DefaultHandler) != SIG_ERR);
+ count += (handler(SIGSTKFLT, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGCHLD, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGTSTP, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGTTIN, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGTTOU, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGURG, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGXCPU, DefaultHandler) != SIG_ERR);
+ count += (handler(SIGXFSZ, DefaultHandler) != SIG_ERR);
+ count += (handler(SIGVTALRM, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGWINCH, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGPOLL, SIG_IGN) != SIG_ERR);
+ count += (handler(SIGPWR, DefaultHandler) != SIG_ERR);
+ print(1, "InitSignalHandler: %d Signalhandler eingerichtet (Soll=%d)\n", count, 0x1c);
+}
+
+static void ExitSignalHandler(void){
+ // no-op
+}
+
+static void SigAlarmHandler(int signr){
+ SigAlarmCounter += 1;
+
+ struct itimerval new_timer = {};
+ strict itimerval old_timer = {};
+ new_timer.it_value.tv_usec = Beat * 1000;
+ setitimer(ITIMER_REAL, &new_timer, &old_timer);
+}
+
+static void InitTime(void){
+ SigAlarmCounter = 0;
+ handler(SIGALRM, SigAlarmHandler);
+ SigAlarmHandler(SIGALARM);
+}
+
+static void ExitTime(void){
+ struct itimerval new_timer = {};
+ strict itimerval old_timer = {};
+ setitimer(ITIMER_REAL, &new_timer, &old_timer);
+ handler(SIGALRM, SIG_IGN);
+}
+
+static void UnlockGame(void){
+ // TODO(fusion): Probably use snprintf to format file name?
+ char FileName[4096];
+ strcpy(FileName, SAVEPATH);
+ strcat(FileName, "/game.pid");
+
+ std::ifstream InputFile(Filename, std::ios_base::in);
+ if(!InputFile.fail()){
+ int Pid;
+ InputFile >> Pid;
+
+ if(Pid == getpid()){
+ unlink(FileName);
+ }
+ }
+}
+
+static void LockGame(void){
+ // TODO(fusion): Probably use snprintf to format file name?
+ char FileName[4096];
+ strcpy(FileName, SAVEPATH);
+ strcat(FileName, "/game.pid");
+
+ {
+ std::ifstream InputFile(Filename, std::ios_base::in);
+ if(!InputFile.fail()){
+ int Pid;
+ InputFile >> Pid;
+ if(Pid != 0){
+ throw "Game-Server is already running, PID file exists.";
+ }
+ }
+ }
+
+ {
+ std::ofstream OutputFile(FileName, std::ios_base::out | std::ios_base::trunc);
+ OutputFile << (int)getpid();
+ }
+
+ atexit(UnlockGame);
+}
+
+void LoadWorldConfig(void){
#if 0
-// TODO(fusion): All globals should probably be packed into a `GlobalState`
-// structure to minimize collisions and bugs.
-struct GlobalState{
- // world state
- // creatures
- // items
- // etc...
-};
-
-GlobalState G = {};
+ // TODO(fusion): Whenever we implement query/database stuff.
+ TQueryManagerConnection Connection(0x4000);
+ if(Connection.WriteBuffer.Position < 0){
+ error("LoadWorldConfig: Kann nicht zum Query-Manager verbinden.\n");
+ throw "cannot connect to querymanager";
+ }
+
+ int HelpGameAddress[4];
+ int Ret = Connection.loadWorldConfig(&WorldType, &RebootTime, HelpGameAddress,
+ &Port, &MaxPlayers, &PremiumPlayerBuffer, &MaxNewbies, &PremiumNewbieBuffer);
+ if(Ret != 0){ // TODO(fusion): Maybe `Ret != QUERY_OK` or something?
+ error("LoadWorldConfig: Kann Konfigurationsdaten nicht holen.\n");
+ throw "cannot load world config";
+ }
+
+ // NOTE(fusion): Ugh...
+ snprintf(GameAddress, sizeof(GameAddress), "%d.%d.%d.%d",
+ HelpGameAddress[0], HelpGameAddress[1],
+ HelpGameAddress[2], HelpGameAddress[3]);
#endif
-// TODO(fusion): Probably current server time. Used for delays, timers, etc.
-int ServerMilliseconds = 0;
+ WorldType = NORMAL;
+ RebootTime = 6 * 60; // minutes
+ strcpy(GameAddress, "127.0.0.1"); // I KNOW
+ Port = 7171;
+ MaxPlayers = 1000;
+ PremiumPlayerBuffer = 100;
+ MaxNewbies = 200;
+ PremiumNewbieBuffer = 50;
+}
-void (*ErrorFunction)(const char*) = NULL;
+static void InitAll(void){
+ try{
+ //ReadConfig();
+ //SetQueryManagerLoginData(1, WorldName);
+ LoadWorldConfig();
+ InitSHM(!BeADaemon);
+ LockGame();
+ //InitLog("game");
+ srand(time(NULL));
+ InitSignalHandler();
+ //InitConnections();
+ //InitCommunication();
+ //InitStrings();
+ //InitWriter();
+ //InitReader();
+ //InitObjects();
+ //InitMap();
+ //InitInfo();
+ //InitMoveUse();
+ //InitMagic();
+ //InitCr();
+ //InitHouses();
+ InitTime();
+ //ApplyPatches();
+ }catch(const char *str){
+ error("Initialisierungsfehler: %s\n", str);
+ exit(EXIT_FAILURE);
+ }
+}
-void error(char *Text, ...){
- char s[1024];
+static void ExitAll(void){
+ // TODO(fusion): Probably missing some inlined empty `Exit*` functions here.
- va_list ap;
- va_start(ap, Text);
- vsnprintf(s, sizeof(s) - 1, Text, ap);
- va_end(ap);
+ EndGame();
+ ExitTime();
+ //ExitCr();
+ //ExitMagic();
+ //ExitMoveUse();
+ //ExitInfo();
+ //ExitHouses();
+ //ExitMap(SaveMapOn);
+ //ExitObjects();
+ //ExitReader();
+ //ExitWriter();
+ //ExitStrings();
+ //ExitCommunication();
+ //ExitConnections();
+ ExitSignalHandler();
+ ExitSHM();
+}
- if(ErrorFunction){
- ErrorFunction(s);
+static void ProcessCommand(void){
+ int Command = GetCommand();
+ if(Command != 0){
+ char *Buffer = GetCommandBuffer();
+ if(Command == 1){
+ if(Buffer != NULL){
+ BroadcastMessage(TALK_ADMIN_MESSAGE, "%s", Buffer);
+ }else{
+ error("ProcessCommand: Text für Broadcast ist NULL.\n");
+ }
+ }else{
+ error("ProcessCommand: Unbekanntes Kommando %d.\n", Command);
+ }
+
+ SetCommand(0, NULL);
+ }
+}
+
+static void AdvanceGame(int Delay){
+ static int CreatureTimeCounter = 0;
+ static int CronTimeCounter = 0;
+ static int SkillTimeCounter = 0;
+ static int OtherTimeCounter = 0;
+ static int OldAmbiente = -1;
+ static uint32 NextMinute = 30;
+ static bool Lag = false;
+
+ CreatureTimeCounter += Delay;
+ CronTimeCounter += Delay;
+ SkillTimeCounter += Delay;
+ OtherTimeCounter += Delay;
+
+ if(CreatureTimeCounter >= 1750){
+ CreatureTimeCounter -= 1000;
+ ProcessCreature();
+ }
+
+ if(CronTimeCounter >= 1500){
+ CronTimeCounter -= 1000;
+ ProcessCronSystem();
+ }
+
+ if(SkillTimeCounter >= 1250){
+ SkillTimeCounter -= 1000;
+ ProcessSkills();
+ }
+
+ if(OtherTimeCounter >= 1000){
+ OtherTimeCounter -= 1000;
+
+ RoundNr += 1;
+ SetRoundNr(RoundNr);
+
+ ProcessConnections();
+ ProcessMonsterhomes();
+ ProcessMonsterRaids();
+ ProcessCommunicationControl();
+ ProcessReaderThreadReplies(RefreshSector,SendMails);
+ ProcessWriterThreadReplies();
+ ProcessCommand();
+
+ // TODO(fusion): Shouldn't we be checking both brightness and color?
+ int Brightness, Color;
+ GetAmbiente(&Brightness, &Color);
+ if(OldAmbiente != Brightness){
+ OldAmbiente = Brightness;
+ TConnection *Connection = GetFirstConnection();
+ while(Connection != NULL){
+ // TODO(fusion): This is probably an inlined function that checks
+ // whether the connection is still going. The exact decompiled condition
+ // was `Connection->State - CONDITION_LOGIN < 4` but I think that's
+ // just a compiler optimization that wouldn't work properly on the
+ // decompiled version. That comparison in the disassembly is unsigned
+ // (`JBE`) but the enum is signed which would probably generate an
+ // invalid signed comparison (`JLE`).
+ //
+ // MOV EAX, dword ptr [Connection + Connection->State]
+ // SUB EAX, 0x3
+ // CMP EAX, 0x3
+ // JBE ... -> SendAmbiente(Connection)
+ //
+ if(Connection->State == CONNECTION_LOGIN
+ || Connection->State == CONNECTION_GAME
+ || Connection->State == CONNECTION_DEAD
+ || Connection->State == CONNECTION_LOGOUT){
+ SendAmbiente(Connection);
+ }
+ Connection = GetNextConnection();
+ }
+ }
+
+ if(RoundNr % 10 == 0){
+ NetLoadCheck();
+ }
+
+ if(RoundNr >= NextMinute){
+ int Hour, Minute;
+ GetRealTime(&Hour, &Minute);
+
+ RefreshCylinders();
+ if(Minute % 5 == 0){
+ CreatePlayerList(true);
+ }
+ if(Minute % 15 == 0){
+ SavePlayerDataOrder();
+ }
+ if(Minute == 0){
+ NetLoadSummary();
+ }
+ if(Minute == 55){
+ WriteKillStatistics();
+ }
+
+ int RealTime = Minute + Hour * 60;
+ if((RealTime + 5) % 1440 == RebootTime){
+ if(Reboot){
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is saving game in 5 minutes.\nPlease come back in 10 minutes.");
+ }else{
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is going down in 5 minutes.\nPlease log out.");
+ }
+ CloseGame();
+ }else if((RealTime + 3) % 1440 == RebootTime){
+ if(Reboot){
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is saving game in 3 minutes.\nPlease come back in 10 minutes.");
+ }else{
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is going down in 3 minutes.\nPlease log out.");
+ }
+ }else if((RealTime + 1) % 1440 == RebootTime){
+ if(Reboot){
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is saving game in one minute.\nPlease log out.");
+ }else{
+ BroadcastMessage(TALK_ADMIN_MESSAGE,
+ "Server is going down in one minute.\nPlease log out.");
+ }
+ }else if(RealTime == RebootTime){
+ CloseGame();
+ LogoutAllPlayers();
+ SendAll();
+ if(Reboot){
+ RefreshMap();
+ }
+ SaveMap();
+ SaveMapOn = false;
+ EndGame();
+ }
+
+ NextMinute = GetRoundForNextMinute();
+ }
+ CleanupDynamicStrings();
+ }
+
+ if(Delay > Beat){
+ Log("lag", "Verzögerung %d msec.\n", Delay);
+ }
+
+ // TODO(fusion): Why would we delay creature movement yet another beat?
+ if(Delay < 1000){
+ MoveCreatures(Delay);
+ Lag = false;
}else{
- printf("%s", s);
+ if(!Lag && RoundNr > 10){
+ error("AdvanceGame: Keine Kreaturbewegung wegen Lag (Verzögerung: %d msec).\n", Delay);
+ }
+ Lag = true;
}
+
+ SendAll();
+}
+
+static void SigUsr1Handler(int signr){
+ SigUsr1Counter += 1;
+}
+
+static void LaunchGame(void){
+ SigUsr1Counter = 0;
+ handler(SIGUSR1, SigUsr1Handler);
+ StartGame();
+
+ print(1, "LaunchGame: Game-Server ist bereit (Pid=%d).\n", getpid());
+
+ // IMPORTANT(fusion): The whole design of the server is to run across a few
+ // different processes and communicate via shared memory and signals. Each
+ // of these processes are single threaded, meaning that signal handlers are
+ // executed concurrently on the SAME thread. You'd still require to synchronize
+ // access to large structures to avoid race conditions BUT accessing a few
+ // independent integers and booleans like we're doing with `SigAlarmCounter`,
+ // `SigUsr1Counter`, and `SaveMapOn` is perfectly safe.
+
+ SaveMapOn = true;
+ SigAlarmCounter = 0;
+ while(GameRunning()){
+ // TODO(fusion): `sigblock` and `sigpause` are deprecated in favour of
+ // `sigprocmask` and `sigsuspend`.
+ sigblock(sigmask(SIGUSR1));
+ while(SigUsr1Counter == 0 && SigAlarmCounter == 0){
+ sigpause(0);
+ }
+
+ if(SigUsr1Counter > 0){
+ SigUsr1Counter = 0;
+ ReceiveData();
+ }
+
+ int NumBeats = SigAlarmCounter;
+ if(NumBeats > 0){
+ SigAlarmCounter = 0;
+ AdvanceGame(NumBeats * Beat);
+ }
+ }
+
+ LogoutAllPlayers();
+}
+
+static bool DaemonInit(bool NoFork){
+ if(!NoFork){
+ pid_t Pid = fork();
+ if(Pid < 0){
+ return true;
+ }
+
+ if(Pid != 0){
+ exit(EXIT_SUCCESS);
+ }
+
+ setsid();
+ }
+
+ umask(0177);
+ chdir(SAVEPATH);
+
+ int OpenMax = sysconf(_SC_OPEN_MAX);
+ if(OpenMax < 0){
+ OpenMax = 1024;
+ }
+
+ for(int fd = 0; fd < OpenMax; fd += 1){
+ close(fd);
+ }
+
+ return false;
}
int main(int argc, char **argv){
+ bool NoFork = false;
+ BeADaemon = false;
+ Reboot = true;
+
+ for(int i = 1; i < argc; i += 1){
+ if(strcmp(argv[i], "daemon") == 0){
+ BeADaemon = true;
+ }else if(strcmp(argv[i], "nofork") == 0){
+ NoFork = true;
+ }
+ }
+
+ // TODO(fusion): It doesn't make sense for `DaemonInit` to even return here.
+ // It either exits the parent or child process, or let it run.
+ if(BeADaemon && DaemonInit(NoFork)){
+ return 2;
+ }
+
+ puts("Tibia Game-Server\n(c) by CIP Productions, 2003.\n");
+
+ InitAll();
+ atexit(ExitAll);
+
+ // TODO(fusion): The original binary does use exceptions but identifying
+ // try..catch blocks are not as straightforward as throw statements. I'll
+ // leave this one at the top level but we should come back to this problem
+ // once we identify all throw statements and how to roughly handle them.
+ try{
+ LaunchGame();
+ }catch(RESULT result){
+ error("main: Nicht abgefangene Exception %d.\n", result);
+ }catch(const char *str){
+ error("main: Nicht abgefangene Exception \"%s\".\n", str);
+ }catch(const std::exception &e){
+ error("main: Nicht abgefangene Exception %s.\n", e.what());
+ }catch(...){
+ error("main: Nicht abgefangene Exception unbekannten Typs.\n");
+ }
+
+ if(!Reboot){
+ print(1, "Beende Game-Server...\n");
+ }else{
+ UnlockGame();
+
+ char FileName[4096];
+ snprintf(FileName, sizeof(FileName), "%s/reboot-daily", BINPATH);
+ if(FileExists(FileName)){
+ ExitAll();
+ print(1, "Starte Game-Server neu...\n");
+ execv(FileName, argv);
+ }else{
+ print(1, "Reboot-Skript existiert nicht.\n");
+ }
+ }
+
return 0;
}
diff --git a/src/main.hh b/src/main.hh
index 0b0993c..287b59c 100644
--- a/src/main.hh
+++ b/src/main.hh
@@ -75,6 +75,64 @@ STATIC_ASSERT(sizeof(int) == 4);
# define ASSERT(expr) ((void)(expr))
#endif
+// TODO(fusion): We should re-implement the multi-process structure used by the
+// original code but only for reference. Making it compile on Windows shouldn't
+// be too difficult either. Overall this design is outdated and should be reviewed.
+// Nevertheless, we should focus on getting it working as intended, on the target
+// platform (which is Linux 32-bits) before attempting to refine it.
+STATIC_ASSERT(OS_LINUX);
+#include <sys/types.h>
+//
+
+// config.cc
+extern int Beat;
+
+// shm.cc
+void StartGame(void);
+void CloseGame(void);
+void EndGame(void);
+bool LoginAllowed(void);
+bool GameRunning(void);
+bool GameStarting(void);
+bool GameEnding(void);
+pid_t GetGameThreadPID(void);
+int GetPrintlogPosition(void);
+char *GetPrintlogLine(int Line);
+void IncrementObjectCounter(void);
+void DecrementObjectCounter(void);
+uint32 GetObjectCounter(void);
+void IncrementPlayersOnline(void);
+void DecrementPlayersOnline(void);
+int GetPlayersOnline(void);
+void IncrementNewbiesOnline(void);
+void DecrementNewbiesOnline(void);
+int GetNewbiesOnline(void);
+void SetRoundNr(uint32 RoundNr);
+uint32 GetRoundNr(void);
+void SetCommand(int Command, char *Text);
+int GetCommand(void);
+char *GetCommandBuffer(void);
+void InitSHM(bool Verbose);
+void ExitSHM(void);
+void InitSHMExtern(bool Verbose);
+void ExitSHMExtern(void);
+
+// time.cc
+extern uint32 RoundNr;
+extern uint32 ServerMilliseconds;
+void GetRealTime(int *Hour, int *Minute);
+void GetTime(int *Hour, int *Minute);
+void GetDate(int *Year, int *Cycle, int *Day);
+void GetAmbiente(int *Brightness, int *Color);
+uint32 GetRoundAtTime(int Hour, int Minute);
+uint32 GetRoundForNextMinute(void);
+
+// util.cc
+typedef void TErrorFunction(const char *Text);
+typedef void TPrintFunction(int Level, const char *Text);
+void SetErrorFunction(TErrorFunction *Function);
+void SetPrintFunction(TPrintFunction *Function);
void error(char *Text, ...) ATTR_PRINTF(1, 2);
+void print(int Level, char *Text, ...) ATTR_PRINTF(1, 2);
#endif //TIBIA_MAIN_HH_
diff --git a/src/map.hh b/src/map.hh
new file mode 100644
index 0000000..7bef02b
--- /dev/null
+++ b/src/map.hh
@@ -0,0 +1,26 @@
+#ifndef TIBIA_MAP_HH_
+#define TIBIA_MAP_HH_ 1
+
+#include "main.hh"
+
+// TODO(fusion): I'm not sure whether to put these.
+
+struct Object {
+ uint32 ObjectID;
+};
+
+constexpr Object NONE = {};
+
+struct ObjectType {
+ int TypeID;
+};
+
+struct TObjectType {
+ char *Name;
+ char *Description;
+ uint8 Flags[9];
+ uint32 Attributes[62];
+ int AttributeOffsets[18];
+};
+
+#endif //TIBIA_MAP_HH_ \ No newline at end of file
diff --git a/src/monster.hh b/src/monster.hh
new file mode 100644
index 0000000..7e18f48
--- /dev/null
+++ b/src/monster.hh
@@ -0,0 +1,88 @@
+#ifndef TIBIA_MONSTER_HH_
+#define TIBIA_MONSTER_HH_ 1
+
+#include "main.hh"
+#include "creature.hh"
+#include "containers.hh"
+#include "enums.hh"
+
+struct TSkillData {
+ int Nr;
+ int Actual;
+ int Minimum;
+ int Maximum;
+ int NextLevel;
+ int FactorPercent;
+ int AddLevel;
+};
+
+struct TItemData {
+ ObjectType Type;
+ int Maximum;
+ int Probability;
+};
+
+struct TSpellData {
+ SpellShapeType Shape;
+ int ShapeParam1;
+ int ShapeParam2;
+ int ShapeParam3;
+ int ShapeParam4;
+ SpellImpactType Impact;
+ int ImpactParam1;
+ int ImpactParam2;
+ int ImpactParam3;
+ int ImpactParam4;
+ int Delay;
+};
+
+struct TRaceData {
+ char Name[30];
+ char Article[3];
+ TOutfit Outfit;
+ ObjectType MaleCorpse;
+ ObjectType FemaleCorpse;
+ BloodType Blood;
+ int ExperiencePoints;
+ int FleeThreshold;
+ int Attack;
+ int Defend;
+ int Armor;
+ int Poison;
+ int SummonCost;
+ int LoseTarget;
+ int Strategy[4];
+ bool KickBoxes;
+ bool KickCreatures;
+ bool SeeInvisible;
+ bool Unpushable;
+ bool DistanceFighting;
+ bool NoSummon;
+ bool NoIllusion;
+ bool NoConvince;
+ bool NoBurning;
+ bool NoPoison;
+ bool NoEnergy;
+ bool NoHit;
+ bool NoLifeDrain;
+ bool NoParalyze;
+ int Skills;
+ vector<TSkillData> Skill;
+ int Talks;
+ vector<uint32> Talk; // POINTER? Probably a reference from `AddDynamicString`?
+ int Items;
+ vector<TItemData> Item;
+ int Spells;
+ vector<TSpellData> Spell;
+};
+
+#if 0
+// TODO(fusion):
+struct TMonster: TNonplayer {
+ int Home;
+ uint32 Master;
+ uint32 Target;
+};
+#endif
+
+#endif //TIBIA_MONSTER_HH_
diff --git a/src/player.hh b/src/player.hh
index b6009a1..0a3f583 100644
--- a/src/player.hh
+++ b/src/player.hh
@@ -3,6 +3,7 @@
#include "main.hh"
#include "creature.hh"
+#include "containers.hh"
struct TPlayerData {
uint32 CharacterID;
@@ -43,7 +44,7 @@ struct TPlayerData {
int InventorySize;
uint8 *Depot[9];
int DepotSize[9];
- ulong AccountID;
+ uint32 AccountID;
int Sex;
char Name[30];
uint8 Rights[12];
@@ -76,7 +77,7 @@ struct TPlayer: TCreature {
// DATA
// =========================================================================
//TCreature super_TCreature; // IMPLICIT
- ulong AccountID;
+ uint32 AccountID;
char Guild[31];
char Rank[31];
char Title[31];
@@ -98,10 +99,10 @@ struct TPlayer: TCreature {
uint8 SpellList[256];
int QuestValues[500];
Object OpenContainer[16];
- vector<long_unsigned_int> AttackedPlayers;
+ vector<uint32> AttackedPlayers;
int NumberOfAttackedPlayers;
bool Aggressor;
- vector<long_unsigned_int> FormerAttackedPlayers;
+ vector<uint32> FormerAttackedPlayers;
int NumberOfFormerAttackedPlayers;
bool FormerAggressor;
uint32 FormerLogoutRound;
diff --git a/src/shm.cc b/src/shm.cc
new file mode 100644
index 0000000..5cd4a52
--- /dev/null
+++ b/src/shm.cc
@@ -0,0 +1,385 @@
+#include "main.hh"
+
+// NOTE(fusion): This looks like an interface to external tools. Looking at the
+// `bin` directory this program was in, there are other programs that probably
+// use this interface to control certain aspects of the server. My previous
+// assumption that each connection was dispatched into its own process may not
+// be correct after all.
+
+struct TSharedMemory {
+ int Command;
+ char CommandBuffer[256];
+ uint32 RoundNr;
+ uint32 ObjectCounter;
+ uint32 Errors;
+ int PlayersOnline;
+ int NewbiesOnline;
+ int PrintBufferPosition;
+ char PrintBuffer[200][128];
+ GAMESTATE GameState;
+ pid_t GameThreadPID;
+};
+
+static TSharedMemory *SHM = NULL;
+static bool IsGameServer = false;
+static bool VerboseOutput = false;
+
+void StartGame(void){
+ if(SHM != NULL){
+ if(SHM->GameState == GAME_STARTING){
+ SHM->GameState = GAME_RUNNING;
+ }
+ }else{
+ error("StartGame: SharedMemory existiert nicht.\n");
+ }
+}
+
+void CloseGame(void){
+ if(SHM != NULL){
+ if(SHM->GameState == GAME_RUNNING){
+ SHM->GameState = GAME_CLOSING;
+ }
+ }else{
+ error("CloseGame: SharedMemory existiert nicht.\n");
+ }
+}
+
+void EndGame(void){
+ if(SHM != NULL){
+ SHM->GameState = GAME_ENDING;
+ }else{
+ error("EndGame: SharedMemory existiert nicht.\n");
+ }
+}
+
+bool LoginAllowed(void){
+ bool Result = false;
+ if(SHM != NULL){
+ Result = SHM->GameState == GAME_RUNNING;
+ }else{
+ error("IsLoginAllowed: SharedMemory existiert nicht.\n");
+ }
+ return Result;
+}
+
+bool GameRunning(void){
+ bool Result = false;
+ if(SHM != NULL){
+ Result = SHM->GameState == GAME_STARTING
+ || SHM->GameState == GAME_RUNNING
+ || SHM->GameState == GAME_CLOSING;
+ }else{
+ error("GameRunning: SharedMemory existiert nicht.\n");
+ }
+ return Result;
+}
+
+bool GameStarting(void){
+ bool Result = false;
+ if(SHM != NULL){
+ Result = SHM->GameState == GAME_STARTING;
+ }else{
+ error("GameStarting: SharedMemory existiert nicht.\n");
+ }
+ return Result;
+}
+
+bool GameEnding(void){
+ bool Result = false;
+ if(SHM != NULL){
+ Result = SHM->GameState == GAME_CLOSING
+ || SHM->GameState == GAME_ENDING;
+ }else{
+ error("GameEnding: SharedMemory existiert nicht.\n");
+ }
+ return Result;
+}
+
+pid_t GetGameThreadPID(void){
+ pid_t Pid = 0;
+ if(SHM != NULL){
+ Pid = SHM->GameThreadPID;
+ }
+ return Pid;
+}
+
+static void ErrorHandler(char *Text){
+ if(VerboseOutput){
+ printf("%s", Text);
+ }
+
+ if(SHM != NULL){
+ SHM->Errors += 1;
+ if(SHM->Errors <= 0x8000){
+ Log("error", Text);
+ if(SHM->Errors == 0x8000){
+ Log("error", "Zu viele Fehler. Keine weitere Protokollierung.\n");
+ }
+ }
+ }
+}
+
+static void PrintHandler(int Level, char *Text){
+ static Semaphore LogfileMutex(1);
+
+ if(Level > DebugLevel){
+ return;
+ }
+
+ if(VerboseOutput){
+ printf("%s", Text);
+ }
+
+ if(SHM != NULL){
+ // TODO(fusion): Does it even make sense to have this Semaphore here?
+ // It controls writes to the print buffer but reads outside this scope
+ // may be partial. But then, it doesn't seem like it is read anywhere
+ // else. Perhaps some external monitor tool, in which case do we even
+ // print from multiple threads?
+ LogfileMutex.down();
+ int Line = SHM->PrintBufferPosition;
+ char *Buffer = SHM->PrintBuffer[Line];
+ // TODO(fusion): Ughh...
+ strncpy(Buffer, Text, sizeof(SHM->PrintBuffer[0]) - 1);
+ Buffer[sizeof(SHM->PrintBuffer[0]) - 2] = 0;
+ if(Buffer[0] != 0){
+ usize TextLen = strlen(Buffer);
+ if(Buffer[TextLen - 1] != '\n'){
+ strcat(Buffer, "\n");
+ }
+ }
+ SHM->PrintBufferPosition = (Line + 1) % NARRAY(SHM->PrintBuffer);
+ LogfileMutex.up();
+ }
+}
+
+int GetPrintlogPosition(void){
+ int Result = 0;
+ if(SHM != NULL){
+ Result = SHM->PrintBufferPosition;
+ }
+ return Result;
+}
+
+char *GetPrintlogLine(int Line){
+ char *Result = NULL;
+ if(SHM != NULL && Line >= 0 && Line < NARRAY(SHM->PrintBuffer)){
+ Result = SHM->PrintBuffer[Line];
+ }
+ return Result;
+}
+
+void IncrementObjectCounter(void){
+ if(SHM != NULL){
+ SHM->ObjectCounter += 1;
+ }
+}
+
+void DecrementObjectCounter(void){
+ if(SHM != NULL){
+ SHM->ObjectCounter -= 1;
+ }
+}
+
+uint32 GetObjectCounter(void){
+ uint32 ObjectCounter = 0;
+ if(SHM != NULL){
+ ObjectCounter = SHM->ObjectCounter;
+ }
+ return ObjectCounter;
+}
+
+void IncrementPlayersOnline(void){
+ if(SHM != NULL){
+ SHM->PlayersOnline += 1;
+ }
+}
+
+void DecrementPlayersOnline(void){
+ if(SHM != NULL){
+ SHM->PlayersOnline -= 1;
+ }
+}
+
+int GetPlayersOnline(void){
+ int PlayersOnline = 0;
+ if(SHM != NULL){
+ PlayersOnline = SHM->PlayersOnline;
+ }
+ return PlayersOnline;
+}
+
+void IncrementNewbiesOnline(void){
+ if(SHM != NULL){
+ SHM->NewbiesOnline += 1;
+ }
+}
+
+void DecrementNewbiesOnline(void){
+ if(SHM != NULL){
+ SHM->NewbiesOnline -= 1;
+ }
+}
+
+int GetNewbiesOnline(void){
+ int NewbiesOnline = 0;
+ if(SHM != NULL){
+ NewbiesOnline = SHM->NewbiesOnline;
+ }
+ return NewbiesOnline;
+}
+
+void SetRoundNr(uint32 RoundNr){
+ if(SHM != NULL){
+ SHM->RoundNr = RoundNr;
+ }
+}
+
+uint32 GetRoundNr(void){
+ uint32 RoundNr = 0;
+ if(SHM != NULL){
+ RoundNr = SHM->RoundNr;
+ }
+ return RoundNr;
+}
+
+void SetCommand(int Command, char *Text){
+ if(SHM != NULL){
+ SHM->Command = Command;
+ if(Text == NULL){
+ SHM->CommandBuffer[0] = 0;
+ }else{
+ strncpy(SHM->CommandBuffer, sizeof(SHM->CommandBuffer), Text);
+ SHM->CommandBuffer[sizeof(SHM->CommandBuffer) - 1] = 0;
+ }
+ }
+}
+
+int GetCommand(void){
+ int Command = 0;
+ if(SHM != NULL){
+ Command = SHM->Command;
+ }
+ return Command;
+}
+
+char *GetCommandBuffer(void){
+ char *Buffer = NULL;
+ if(SHM != NULL && SHM->Command != 0){
+ Buffer = SHM->CommandBuffer;
+ }
+ return Buffer;
+}
+
+static bool DeleteSHM(void){
+ int SHMID = shmget(SHMKey, 0, 0);
+ if(SHMID == -1){
+ if(VerboseOutput){
+ puts("DeleteSHM: SharedMemory existiert nicht.");
+ }
+ return true;
+ }
+
+ if(shmctl(SHMID, IPC_RMID, NULL) == -1){
+ if(VerboseOutput){
+ // TODO(fusion): Include `errno` in the error message?
+ puts("DeleteSHM: Kann SharedMemory nicht löschen.");
+ }
+ return false;
+ }
+
+ return true;
+}
+
+static void CreateSHM(void){
+ bool Deleted = false;
+ while(true){
+ int SHMID = shmget(SHMKey, sizeof(TSharedMemory), IPC_CREAT | IPC_EXCL | 0777);
+ if(SHMID != -1){
+ return;
+ }
+
+ if(errno != EEXIST || Deleted){
+ if(VerboseOutput){
+ printf("CreateSHM: Kann SharedMemory nicht anlegen (Fehler %d).\n", errno);
+ }
+ throw "Cannot create SharedMemory";
+ }
+
+ if(!DeleteSHM()){
+ throw "Cannot delete SharedMemory";
+ }
+
+ Deleted = true;
+ }
+}
+
+static void AttachSHM(void){
+ int SHMID = shmget(SHMKey, sizeof(TSharedMemory), 0);
+ if(SHMID == -1){
+ if(VerboseOutput){
+ // TODO(fusion): Include `errno` in the error message?
+ puts("AttachSHM: Kann SharedMemory nicht fassen.");
+ }
+ SHM = NULL;
+ throw "Cannot get SharedMemory";
+ }
+
+ SHM = (TSharedMemory*)shmat(SHMID, NULL, 0);
+ if(SHM == (void*)-1){
+ if(VerboseOutput){
+ puts("AttachSHM: Kann SharedMemory nicht anbinden.");
+ }
+
+ SHM = NULL;
+ throw "Cannot attach SharedMemory";
+ }
+}
+
+static void DetachSHM(void){
+ if(SHM != NULL){
+ if(shmdt(SHM) == -1 && VerboseOutput){
+ puts("DetachSHM: Kann SharedMemory nicht löschen.");
+ }
+ SHM = NULL;
+ }
+}
+
+void InitSHM(bool Verbose){
+ IsGameServer = true;
+ VerboseOutput = Verbose;
+
+ CreateSHM();
+ AttachSHM();
+ SetErrorFunction(ErrorHandler);
+ SetPrintFunction(PrintHandler);
+
+ // NOTE(fusion): `CreateSHM` ensures a new shared memory segment is created
+ // and it should be always zero initialized as per the manual. Nevertheless
+ // the decompiled version was also clearing SHM, probably just in case.
+ memset(SHM, 0, sizeof(TSharedMemory));
+
+ strncpy(SHM->PrintBuffer[0], sizeof(SHM->PrintBuffer[0]),
+ "SHM initialized. System printing is working!\n");
+ SHM->PrintBuffer[0][sizeof(SHM->PrintBuffer[0]) - 1] = 0;
+
+ SHM->PrintBufferPosition = 1;
+ SHM->GameState = GAME_STARTING;
+ SHM->GameThreadPID = getpid();
+}
+
+void ExitSHM(void){
+ SetErrorFunction(NULL);
+ SetPrintFunction(NULL);
+ DetachSHM();
+ DeleteSHM();
+}
+
+void InitSHMExtern(bool Verbose){
+ VerboseOutput = Verbose;
+ AttachSHM();
+}
+
+void ExitSHMExtern(void){
+ DetachSHM();
+}
diff --git a/src/skill.cc b/src/skill.cc
index 629d935..6ca04ef 100644
--- a/src/skill.cc
+++ b/src/skill.cc
@@ -796,7 +796,7 @@ void TSkillSoulpoints::Event(int Range){
// TODO(fusion): Shouldn't this be the same as `Master->Skills[SKILL_SOUL]`?
// Not sure what's going on here.
if(!Master->IsDead){
- Master->Skills[SKILL_SOUL]->Set(Other->Act + 1);
+ Master->Skills[SKILL_SOUL]->Change(1);
}
}
@@ -1157,7 +1157,7 @@ bool TSkillBase::NewSkill(uint16 SkillNo, TCreature *Creature){
}
bool TSkillBase::SetSkills(int Race){
- if(Race < 0 || Race >= 512){
+ if(Race < 0 || Race >= NARRAY(RaceData)){
error("TSkillBase::SetSkills: Ungültige Rassennummer %d.\n", Race);
return false;
}
@@ -1237,7 +1237,7 @@ void TSkillBase::DelTimer(uint16 SkNr){
Skill->DelTimer();
int End = this->FirstFreeTimer;
- for(int Index = 0; Index < End; i += 1){
+ for(int Index = 0; Index < End; Index += 1){
if(Skill == this->TimerList[Index]){
// NOTE(fusion): A little swap and pop action.
End -= 1;
diff --git a/src/skill.hh b/src/skill.hh
index 9b54649..c3beff6 100644
--- a/src/skill.hh
+++ b/src/skill.hh
@@ -64,6 +64,7 @@ struct TSkill{
};
struct TSkillLevel: TSkill {
+ TSkillLevel(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Increase(int Amount) override;
void Decrease(int Amount) override;
int GetExpForLevel(int Level) override;
@@ -71,6 +72,7 @@ struct TSkillLevel: TSkill {
};
struct TSkillProbe: TSkill {
+ TSkillProbe(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Increase(int Amount) override;
void Decrease(int Amount) override;
int GetExpForLevel(int Level) override;
@@ -83,47 +85,57 @@ struct TSkillProbe: TSkill {
};
struct TSkillAdd: TSkill {
+ TSkillAdd(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Advance(int Range) override;
};
struct TSkillHitpoints: TSkillAdd {
+ TSkillHitpoints(int SkNr, TCreature *Master) : TSkillAdd(SkNr, Master) {}
void Set(int Value) override;
};
struct TSkillMana: TSkillAdd {
+ TSkillMana(int SkNr, TCreature *Master) : TSkillAdd(SkNr, Master) {}
void Set(int Value) override;
};
struct TSkillGoStrength: TSkillAdd {
+ TSkillGoStrength(int SkNr, TCreature *Master) : TSkillAdd(SkNr, Master) {}
bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override;
void Event(int Range) override;
};
struct TSkillCarryStrength: TSkillAdd {
+ TSkillCarryStrength(int SkNr, TCreature *Master) : TSkillAdd(SkNr, Master) {}
void Set(int Value) override;
};
struct TSkillSoulpoints: TSkillAdd {
+ TSkillSoulpoints(int SkNr, TCreature *Master) : TSkillAdd(SkNr, Master) {}
void Set(int Value) override;
int TimerValue(void) override;
void Event(int Range) override;
};
struct TSkillFed: TSkill {
+ TSkillFed(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Event(int Range) override;
};
struct TSkillLight: TSkill {
+ TSkillLight(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override;
void Event(int Range) override;
};
struct TSkillIllusion: TSkill {
+ TSkillIllusion(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override;
void Event(int Range) override;
};
struct TSkillPoison: TSkill {
+ TSkillPoison(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
bool Process(void) override;
bool SetTimer(int Cycle, int Count, int MaxCount, int AdditionalValue) override;
void Event(int Range) override;
@@ -131,10 +143,12 @@ struct TSkillPoison: TSkill {
};
struct TSkillBurning: TSkill {
+ TSkillBurning(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Event(int Range) override;
};
struct TSkillEnergy: TSkill {
+ TSkillEnergy(int SkNr, TCreature *Master) : TSkill(SkNr, Master) {}
void Event(int Range) override;
};
diff --git a/src/time.cc b/src/time.cc
new file mode 100644
index 0000000..89325ba
--- /dev/null
+++ b/src/time.cc
@@ -0,0 +1,96 @@
+#include "main.hh"
+
+// IMPORTANT(fusion): `RoundNr` is just the number of seconds since startup which
+// is why `GetRoundAtTime` and `GetRoundForNextMinute` straight up uses it as
+// seconds. It is incremented every 1 second inside `AdvanceGame`.
+uint32 RoundNr = 0;
+
+uint32 ServerMilliseconds = 0;
+
+// NOTE(fusion): This isn't strictly required because each server process is
+// single threaded and don't share static memory, which is what the result of
+// the regular `localtime` function uses.
+static struct tm GetLocalTimeTM(time_t timer){
+ struct tm result;
+#if COMPILER_MSVC
+ localtime_s(&result, &timer);
+#else
+ localtime_r(&timer, &result);
+#endif
+ return result;
+}
+
+void GetRealTime(int *Hour, int *Minute){
+ struct tm LocalTime = GetLocalTimeTM(time(NULL));
+ *Hour = LocalTime.tm_hour;
+ *Minute = LocalTime.tm_min;
+}
+
+void GetTime(int *Hour, int *Minute){
+ // NOTE(fusion): This maps each real time hour to a game time day.
+ struct tm LocalTime = GetLocalTimeTM(time(NULL));
+ int Time = LocalTime.tm_sec + LocalTime.tm_min * 60;
+ *Hour = (Time / 150);
+ *Minute = (Time % 150) * 2 / 5;
+}
+
+void GetDate(int *Year, int *Cycle, int *Day){
+ // NOTE(fusion): This maps each real time week to a game time year.
+ time_t RealTime = time(NULL);
+ struct tm LocalTime = GetLocalTimeTM(RealTime);
+ *Year = ((T / 86400) + 4) / 7
+ *Cycle = LocalTime.tm_wday;
+ *Day = LocalTime.tm_hour;
+}
+
+void GetAmbiente(int *Brightness, int *Color){
+ int Hour, Minute;
+ GetTime(&Hour, &Minute);
+
+ int Time = Minute + Hour * 60;
+ if(Time < 60){
+ *Brightness = 0x33;
+ *Color = 0xD7;
+ }else if(Time < 120){
+ *Brightness = 0x66;
+ *Color = 0xD7;
+ }else if(Time < 180){
+ *Brightness = 0x99;
+ *Color = 0xAD;
+ }else if(Time < 240){
+ *Brightness = 0xCC;
+ *Color = 0xAD;
+ }else if(Time <= 1200){
+ *Brightness = 0xFF;
+ *Color = 0xD7;
+ }else if(Time <= 1260){
+ *Brightness = 0xCC;
+ *Color = 0xD0;
+ }else if(Time <= 1320){
+ *Brightness = 0x99;
+ *Color = 0xD0;
+ }else if(Time <= 1380){
+ *Brightness = 0x66;
+ *Color = 0xD7;
+ }else{
+ *Brightness = 0x33;
+ *Color = 0xD7;
+ }
+}
+
+uint32 GetRoundAtTime(int Hour, int Minute){
+ struct tm LocalTime = GetLocalTimeTM(RealTime);
+ int SecondsToTime = (Hour - LocalTime.tm_hour) * 3600
+ + (Minute - LocalTime.tm_min) * 60
+ + (0 - LocalTime.tm_sec);
+ if(SecondsToTime < 0){
+ SecondsToTime += 86400;
+ }
+ return SecondsToTime + RoundNr;
+}
+
+uint32 GetRoundForNextMinute(void){
+ struct tm LocalTime = GetLocalTimeTM(RealTime);
+ int SecondsToNextMinute = 60 - LocalTime.tm_sec;
+ return SecondsToNextMinute + RoundNr + 30;
+}
diff --git a/src/util.cc b/src/util.cc
new file mode 100644
index 0000000..f2ebc48
--- /dev/null
+++ b/src/util.cc
@@ -0,0 +1,42 @@
+#include "util.hh"
+
+static void (*ErrorFunction)(const char *Text) = NULL;
+static void (*PrintFunction)(int Level, const char *Text) = NULL;
+
+void SetErrorFunction(TErrorFunction *Function){
+ ErrorFunction = Function;
+}
+
+void SetPrintFunction(TPrintFunction *Function){
+ PrintFunction = Function;
+}
+
+void error(char *Text, ...){
+ char s[1024];
+
+ va_list ap;
+ va_start(ap, Text);
+ vsnprintf(s, sizeof(s), Text, ap);
+ va_end(ap);
+
+ if(ErrorFunction){
+ ErrorFunction(s);
+ }else{
+ printf("%s", s);
+ }
+}
+
+void print(int Level, char *Text, ...){
+ char s[1024];
+
+ va_list ap;
+ va_start(ap, Text);
+ vsnprintf(s, sizeof(s), Text, ap);
+ va_end(ap);
+
+ if(PrintFunction){
+ PrintFunction(Level, s);
+ }else{
+ printf("%s", s);
+ }
+}