aboutsummaryrefslogtreecommitdiff
path: root/src/moveuse.cc
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-27 18:57:29 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-27 18:57:29 -0300
commite59667ec6be0a21ce702ce077154357a1c7f7c04 (patch)
tree8222cce37f8dbf1694cffa138c0bb0ad6b893bf3 /src/moveuse.cc
parentea319bc2fbef91e3ea062be554b215756648d1d3 (diff)
downloadgame-e59667ec6be0a21ce702ce077154357a1c7f7c04.tar.gz
game-e59667ec6be0a21ce702ce077154357a1c7f7c04.zip
`moveuse.cc`
Diffstat (limited to 'src/moveuse.cc')
-rw-r--r--src/moveuse.cc3039
1 files changed, 3024 insertions, 15 deletions
diff --git a/src/moveuse.cc b/src/moveuse.cc
index f11fffa..9cdcd95 100644
--- a/src/moveuse.cc
+++ b/src/moveuse.cc
@@ -1,27 +1,424 @@
+#include "moveuse.hh"
+#include "config.hh"
+#include "info.hh"
#include "operate.hh"
-// NOTE(fusion): Oh no.
+#include "stubs.hh"
+
+static vector<TMoveUseCondition> MoveUseConditions(1, 1000, 1000);
+static int NumberOfMoveUseConditions;
+
+static vector<TMoveUseAction> MoveUseActions(1, 1000, 1000);
+static int NumberOfMoveUseActions;
+
+static TMoveUseDatabase MoveUseDatabases[5];
+
+static vector<TDelayedMail> DelayedMail(0, 10, 10);
+static int DelayedMails;
+
+// Coordinate Packing
+// =============================================================================
+int PackAbsoluteCoordinate(int x, int y, int z){
+ // DOMAIN: [24576, 40959] x [24576, 40959] x [0, 15]
+ int Packed = (((x - 24576) & 0x00003FFF) << 18)
+ | (((y - 24576) & 0x00003FFF) << 4)
+ | (z & 0x0000000F);
+ return Packed;
+}
+
+void UnpackAbsoluteCoordinate(int Packed, int *x, int *y, int *z){
+ *x = ((Packed >> 18) & 0x00003FFF) + 24576;
+ *y = ((Packed >> 4) & 0x00003FFF) + 24576;
+ *z = ((Packed >> 0) & 0x0000000F);
+}
+
+int PackRelativeCoordinate(int x, int y, int z){
+ // DOMAIN: [-8192, 8191] x [-8192, 8191] x [-8, 7]
+ int Packed = (((x + 8192) & 0x00003FFF) << 18)
+ | (((y + 8192) & 0x00003FFF) << 4)
+ | ((z + 8) & 0x0000000F);
+ return Packed;
+}
+
+void UnpackRelativeCoordinate(int Packed, int *x, int *y, int *z){
+ *x = ((Packed >> 18) & 0x00003FFF) - 8192;
+ *y = ((Packed >> 4) & 0x00003FFF) - 8192;
+ *z = ((Packed >> 0) & 0x0000000F) - 8;
+}
+
+// Event Execution
+// =============================================================================
+Object GetEventObject(int Nr, Object User, Object Obj1, Object Obj2, Object Temp){
+ Object Obj = NONE;
+ switch(Nr){
+ case 0: Obj = NONE; break;
+ case 1: Obj = Obj1; break;
+ case 2: Obj = Obj2; break;
+ case 3: Obj = User; break;
+ case 4: Obj = Temp; break;
+ default:{
+ error("GetEventObject: Unbekannte Nummer %d.\n", Nr);
+ break;
+ }
+ }
+ return Obj;
+}
+
+bool Compare(int Value1, int Operator, int Value2){
+ bool Result = false;
+ switch(Operator){
+ case '<': Result = (Value1 < Value2); break;
+ case '>': Result = (Value1 > Value2); break;
+ case '=': Result = (Value1 == Value2); break;
+ case 'N': Result = (Value1 != Value2); break;
+ case 'L': Result = (Value1 <= Value2); break;
+ case 'G': Result = (Value1 >= Value2); break;
+ default:{
+ error("Compare: Ungültiger Operator %d.\n", Operator);
+ break;
+ }
+ }
+ return Result;
+}
+
+bool CheckCondition(MoveUseEventType EventType, TMoveUseCondition *Condition,
+ Object User, Object Obj1, Object Obj2, Object *Temp){
+ bool Result = false;
+ switch(Condition->Condition){
+ case MOVEUSE_CONDITION_ISPOSITION:{
+ int ObjX, ObjY, ObjZ, CoordX, CoordY, CoordZ;
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackAbsoluteCoordinate(Condition->Parameters[1], &CoordX, &CoordY, &CoordZ);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Result = (ObjX == CoordX && ObjY == CoordY && ObjZ == CoordZ);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISTYPE:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ Result = (Obj.getObjectType().TypeID == Condition->Parameters[1]);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISCREATURE:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ Result = Obj.getObjectType().isCreatureContainer();
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISPLAYER:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ Result = (Creature && Creature->Type == PLAYER);
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASFLAG:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ Result = Obj.getObjectType().getFlag((FLAG)Condition->Parameters[1]);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASTYPEATTRIBUTE:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Attribute = (int)Obj.getObjectType().getAttribute((TYPEATTRIBUTE)Condition->Parameters[1]);
+ int Operator = Condition->Parameters[2];
+ int Value = Condition->Parameters[3];
+ Result = Compare(Attribute, Operator, Value);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASINSTANCEATTRIBUTE:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Attribute = (int)Obj.getAttribute((INSTANCEATTRIBUTE)Condition->Parameters[1]);
+ int Operator = Condition->Parameters[2];
+ int Value = Condition->Parameters[3];
+ Result = Compare(Attribute, Operator, Value);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASTEXT:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ const char *Attribute = GetDynamicString(Obj.getAttribute(TEXTSTRING));
+ const char *Text = GetDynamicString(Condition->Parameters[1]);
+ Result = (strcmp(Attribute, Text) == 0);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISPEACEFUL:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ Result = (Creature && Creature->IsPeaceful());
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_MAYLOGOUT:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ Result = (Creature != NULL && Creature->Type == PLAYER
+ && Creature->LogoutPossible() == 0);
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASPROFESSION:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Profession = Condition->Parameters[1];
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ Result = (Creature != NULL && Creature->Type == PLAYER
+ && ((TPlayer*)Creature)->GetEffectiveProfession() == Profession);
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASLEVEL:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Operator = Condition->Parameters[1];
+ int Value = Condition->Parameters[2];
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature != NULL){
+ int Level = Creature->Skills[SKILL_LEVEL]->Get();
+ Result = Compare(Level, Operator, Value);
+ }
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASRIGHT:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ RIGHT Right = (RIGHT)Condition->Parameters[1];
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ Result = (Creature != NULL && Creature->Type == PLAYER
+ && CheckRight(Creature->ID, Right));
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_HASQUESTVALUE:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int QuestNr = Condition->Parameters[1];
+ int Operator = Condition->Parameters[2];
+ int Value = Condition->Parameters[3];
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature != NULL && Creature->Type == PLAYER){
+ int QuestValue = ((TPlayer*)Creature)->GetQuestValue(QuestNr);
+ Result = Compare(QuestValue, Operator, Value);
+ }
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_TESTSKILL:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int SkillNr = Condition->Parameters[1];
+ int Difficulty = Condition->Parameters[2];
+ int Probability = Condition->Parameters[3];
+ if(Obj.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature != NULL){
+ if(SkillNr >= 0 && SkillNr < NARRAY(Creature->Skills)){
+ Result = Creature->Skills[SkillNr]->Probe(Difficulty, Probability, true);
+ }else{
+ error("CheckCondition (TestSkill): Ungültige Skillnummer %d.\n", SkillNr);
+ }
+ }
+ }
+ break;
+ }
+
+ // TODO(fusion): This also counts objects at the object's map location?
+ case MOVEUSE_CONDITION_COUNTOBJECTS:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Operator = Condition->Parameters[1];
+ int Value = Condition->Parameters[2];
+ Object MapCon = GetMapContainer(Obj);
+ int ObjCount = CountObjectsInContainer(MapCon);
+ Result = Compare(ObjCount, Operator, Value);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_COUNTOBJECTSONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Condition->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ int Operator = Condition->Parameters[1];
+ int Value = Condition->Parameters[2];
+ Object MapCon = GetMapContainer(CoordX, CoordY, CoordZ);
+ int ObjCount = CountObjectsInContainer(MapCon);
+ Result = Compare(ObjCount, Operator, Value);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISOBJECTTHERE:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Condition->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Condition->Parameters[1];
+ *Temp = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ Result = (*Temp != NONE);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISCREATURETHERE:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Condition->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = TYPEID_CREATURE_CONTAINER;
+ *Temp = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ Result = (*Temp != NONE);
+ break;
+ }
+
+ // TODO(fusion): I feel this one should iterate the field because there
+ // could be more than one creature.
+ case MOVEUSE_CONDITION_ISPLAYERTHERE:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Condition->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = TYPEID_CREATURE_CONTAINER;
+ *Temp = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ if(*Temp != NONE){
+ TCreature *Creature = GetCreature(*Temp);
+ Result = Creature && Creature->Type == PLAYER;
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISOBJECTININVENTORY:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ ObjectType Type = Condition->Parameters[1];
+ int Count = Condition->Parameters[2];
+ if(Obj.getObjectType().isCreatureContainer()){
+ *Temp = GetInventoryObject(Obj.getCreatureID(), Type, Count);
+ Result = (*Temp != NONE);
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISPROTECTIONZONE:{
+ int ObjX, ObjY, ObjZ;
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Result = IsProtectionZone(ObjX, ObjY, ObjZ);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISHOUSE:{
+ int ObjX, ObjY, ObjZ;
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Result = IsHouse(ObjX, ObjY, ObjZ);
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISHOUSEOWNER:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ Object Cr = GetEventObject(Condition->Parameters[1], User, Obj1, Obj2, *Temp);
+ if(Cr.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Cr);
+ if(Creature != NULL && Creature->Type == PLAYER){
+ int ObjX, ObjY, ObjZ;
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ uint16 HouseID = GetHouseID(ObjX, ObjY, ObjZ);
+ Result = IsOwner(HouseID, (TPlayer*)Creature);
+ }
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_ISDRESSED:{
+ Object Obj = GetEventObject(Condition->Parameters[0], User, Obj1, Obj2, *Temp);
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.getFlag(CLOTHES)){
+ int CurPosition = GetObjectBodyPosition(Obj);
+ int ObjPosition = (int)ObjType.getAttribute(BODYPOSITION);
+ Result = (CurPosition == ObjPosition);
+ }else{
+ error("CheckCondition (IsDressed): Objekt ist kein Kleidungsstück.\n");
+ }
+ break;
+ }
+
+ case MOVEUSE_CONDITION_RANDOM:{
+ Result = (random(1, 100) <= Condition->Parameters[0]);
+ break;
+ }
+
+ default:{
+ error("CheckCondition: Unbekannte Bedingung %d.\n", Condition->Condition);
+ return false;
+ }
+ }
+
+ if(Condition->Modifier == MOVEUSE_MODIFIER_NORMAL){
+ // no-op
+ }else if(Condition->Modifier == MOVEUSE_MODIFIER_INVERT){
+ Result = !Result;
+ }else if(Condition->Modifier == MOVEUSE_MODIFIER_TRUE){
+ Result = true;
+ }else{
+ // TODO(fusion): Error?
+ Result = false;
+ }
+
+ return Result;
+}
+
+Object CreateObject(Object Con, ObjectType Type, uint32 Value){
+ for(int Attempt = 0; true; Attempt += 1){
+ try{
+ Object Obj = NONE;
+ ObjectType ConType = Con.getObjectType();
+ if(ConType.isBodyContainer()){
+ uint32 CreatureID = GetObjectCreatureID(Con);
+ Obj = CreateAtCreature(CreatureID, Type, Value);
+ }else{
+ Obj = Create(Con, Type, Value);
+ }
+ return Obj;
+ }catch(RESULT r){
+ if(Attempt == 0 && (r == NOTTAKABLE || r == TOOHEAVY || r == CONTAINERFULL)){
+ // NOTE(fusion): Try to create object on the map, ONCE.
+ Con = GetMapContainer(Con);
+ }else{
+ int ConX, ConY, ConZ;
+ GetObjectCoordinates(Con, &ConX, &ConY, &ConZ);
+ error("moveuse::CreateObject: Exception %d, Objekt %d, Position [%d,%d,%d].\n",
+ r, Type.TypeID, ConX, ConY, ConZ);
+ return NONE;
+ }
+ }
+ }
+}
+
void ChangeObject(Object Obj, ObjectType NewType, uint32 Value){
if(!Obj.exists()){
error("ChangeObject: Übergebenes Objekt existiert nicht (1, NewType=%d).\n", NewType.TypeID);
return;
}
- Object Con = Obj.getContainer();
- while(true){
+ // NOTE(fusion): `Change` requires CUMULATIVE objects to have AMOUNT
+ // equal to ONE or it fails with `SPLITOBJECT`. This means might need
+ // to split the object and the split destination may change depending
+ // on how things go.
+ Object SplitDest = Obj.getContainer();
+ for(int Attempt = 0; true; Attempt += 1){
if(!Obj.exists()){
error("ChangeObject: Übergebenes Objekt existiert nicht (2, NewType=%d).\n", NewType.TypeID);
return;
}
try{
- // NOTE(fusion): `Change` requires CUMULATIVE objects to have AMOUNT
- // equal to one or it fails with `SPLITOBJECT`.
ObjectType ObjType = Obj.getObjectType();
if(ObjType.getFlag(CUMULATIVE)){
uint32 Amount = Obj.getAttribute(AMOUNT);
if(Amount > 1){
- Move(0, Obj, Con, Amount - 1, true, NONE);
+ Move(0, Obj, SplitDest, Amount - 1, true, NONE);
}
}
@@ -33,20 +430,25 @@ void ChangeObject(Object Obj, ObjectType NewType, uint32 Value){
return;
}
+ // TODO(fusion): I feel a couple of attempts should be enough?
+ if(Attempt >= 2){
+ throw;
+ }
+
if(r == CONTAINERFULL){
- Con = GetMapContainer(Obj);
+ // NOTE(fusion): Set `SplitDest` to map.
+ SplitDest = GetMapContainer(Obj);
}else if(r == TOOHEAVY){
+ // NOTE(fusion): Move object to map, keep `SplitDest`.
Object MapCon = GetMapContainer(Obj);
Move(0, Obj, MapCon, -1, true, NONE);
- }else if(r == NOROOM){
- if(!Obj.getObjectType().getFlag(CUMULATIVE)
- || Obj.getAttribute(AMOUNT) <= 1){
- throw;
- }
-
- // TODO(fusion): Why do we use `Con` with `GetMapContainer` here?
+ }else if(r == NOROOM
+ && Obj.getObjectType().getFlag(CUMULATIVE)
+ && Obj.getAttribute(AMOUNT) > 1){
+ // NOTE(fusion): Split to map directly. Similar to setting `SplitDest`
+ // to map, although failing here will throw us out of the function.
uint32 Amount = Obj.getAttribute(AMOUNT);
- Object MapCon = GetMapContainer(Con);
+ Object MapCon = GetMapContainer(SplitDest);
Move(0, Obj, MapCon, Amount - 1, true, NONE);
}else{
throw;
@@ -54,3 +456,2610 @@ void ChangeObject(Object Obj, ObjectType NewType, uint32 Value){
}
}
}
+
+void MoveOneObject(Object Obj, Object Con){
+ if(!Obj.exists()){
+ error("MoveOneObject: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ if(!Con.exists()){
+ error("MoveOneObject: Übergebener Container existiert nicht.\n");
+ return;
+ }
+
+ ObjectType ConType = Con.getObjectType();
+ if(!ConType.getFlag(CONTAINER)){
+ error("MoveOneObject: \"Con\" ist kein Container.\n");
+ return;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.isCreatureContainer() && !ConType.isMapContainer()){
+ error("MoveOneObject: \"Con\" ist kein MapContainer.\n");
+ return;
+ }
+
+ try{
+ if(ObjType.getFlag(LIQUIDPOOL) || ObjType.getFlag(MAGICFIELD)){
+ Delete(Obj, -1);
+ }else{
+ Move(0, Obj, Con, -1, true, NONE);
+ }
+ }catch(RESULT r){
+ if(r != DESTROYED){
+ int ConX, ConY, ConZ;
+ GetObjectCoordinates(Con, &ConX, &ConY, &ConZ);
+ error("MoveOneObject: Exception %d; Koordinate [%d,%d,%d].\n",
+ r, ConX, ConY, ConZ);
+ }
+
+ if(r == CONTAINERFULL){
+ Delete(Obj, -1);
+ }
+ }
+}
+
+void MoveAllObjects(Object Obj, Object Dest, Object Exclude, bool MoveUnmovable){
+ if(Obj == NONE){
+ return;
+ }
+
+ if(!Dest.getObjectType().isMapContainer()){
+ error("MoveAllObjects: \"Dest\" ist kein Mapcontainer.\n");
+ return;
+ }
+
+ // TODO(fusion): We probably use recursion to move objects in reverse order?
+ MoveAllObjects(Obj.getNextObject(), Dest, Exclude, MoveUnmovable);
+
+ if(Obj != Exclude){
+ ObjectType ObjType = Obj.getObjectType();
+ try{
+ if(ObjType.getFlag(LIQUIDPOOL) || ObjType.getFlag(MAGICFIELD)){
+ Delete(Obj, -1);
+ }else if(MoveUnmovable || !ObjType.getFlag(UNMOVE)){
+ Move(0, Obj, Dest, -1, true, NONE);
+ }
+ }catch(RESULT r){
+ if(r != DESTROYED){
+ int DestX, DestY, DestZ;
+ GetObjectCoordinates(Dest, &DestX, &DestY, &DestZ);
+ error("MoveAllObjects: Exception %d; Objekt %d, Zielkoordinate [%d,%d,%d].\n",
+ r, ObjType.TypeID, DestX, DestY, DestZ);
+ }
+ }
+ }
+}
+
+void DeleteAllObjects(Object Obj, Object Exclude, bool DeleteUnmovable){
+ if(Obj == NONE){
+ return;
+ }
+
+ // TODO(fusion): Same as `MoveAllObjects`.
+ DeleteAllObjects(Obj.getNextObject(), Exclude, DeleteUnmovable);
+
+ if(Obj != Exclude){
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.isCreatureContainer()){
+ int ObjX, ObjY, ObjZ;
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ error("DeleteAllObjects: An Position [%d,%d,%d] soll eine Kreatur gelöscht werden.\n",
+ ObjX, ObjY, ObjZ);
+ return;
+ }
+
+ if(!DeleteUnmovable && ObjType.getFlag(UNMOVE)
+ && !ObjType.getFlag(LIQUIDPOOL)
+ && !ObjType.getFlag(MAGICFIELD)){
+ return;
+ }
+
+ try{
+ Delete(Obj, -1);
+ }catch(RESULT r){
+ error("DeleteAllObjects: Exception %d.\n", r);
+ }
+ }
+}
+
+void ClearField(Object Obj, Object Exclude){
+ // IMPORTANT(fusion): This function seems to be used when items transform
+ // into `UNPASS` versions of themselves, like doors, which is why we skip
+ // the actual object when calling `MoveAllObjects` at the end.
+ // The order we scan available fields may also be important which is why
+ // I've kept the same ordering used by the original function.
+
+ if(!Obj.exists()){
+ error("ClearField: Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjX, ObjY, ObjZ;
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+
+ bool DestFound = false;
+ int DestX, DestY, DestZ;
+ int OffsetX[4] = { 1, 0, -1, 0};
+ int OffsetY[4] = { 0, 1, 0, -1};
+
+ // NOTE(fusion): First look for a field that's not blocked.
+ for(int i = 0; i < 4; i += 1){
+ DestX = ObjX + OffsetX[i];
+ DestY = ObjY + OffsetY[i];
+ DestZ = ObjZ;
+ if(CoordinateFlag(DestX, DestY, DestZ, BANK)
+ && !CoordinateFlag(DestX, DestY, DestZ, UNPASS)){
+ DestFound = true;
+ break;
+ }
+ }
+
+ // NOTE(fusion): Then look for a field where jumping is possible.
+ if(!DestFound){
+ for(int i = 0; i < 4; i += 1){
+ DestX = ObjX + OffsetX[i];
+ DestY = ObjY + OffsetY[i];
+ DestZ = ObjZ;
+ if(JumpPossible(DestX, DestY, DestZ, false)){
+ DestFound = true;
+ break;
+ }
+ }
+ }
+
+ if(DestFound){
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveAllObjects(Obj.getNextObject(), Dest, Exclude, true);
+ }
+}
+
+void LoadDepotBox(uint32 CreatureID, int Nr, Object Con){
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("moveuse::LoadDepotBox: Kreatur nicht gefunden.\n");
+ return;
+ }
+
+ if(!Con.exists()){
+ error("moveuse::LoadDepotBox: Übergebener Container existiert nicht.\n");
+ return;
+ }
+
+ if(!Con.getObjectType().getFlag(CONTAINER)){
+ error("moveuse::LoadDepotBox: Übergebenes Objekt ist kein Container.\n");
+ return;
+ }
+
+ // NOTE(fusion): Holy fuck. Objects are actually loaded into the depot box.
+ LoadDepot(Player->PlayerData, Nr, Con);
+
+ int DepotObjects = CountObjects(Con) - 1;
+ int DepotCapacity = GetDepotSize(Nr, CheckRight(Player->ID, PREMIUM_ACCOUNT));
+ int DepotSpace = DepotCapacity - DepotObjects;
+ Player->Depot = Con;
+ Player->DepotNr = Nr;
+ Player->DepotSpace = DepotSpace;
+
+ print(3, "Depot von %s hat %d freie Plätze.\n", Player->Name, DepotSpace);
+ SendMessage(Player->Connection, TALK_STATUS_MESSAGE,
+ "Your depot contains %d item%s.",
+ DepotObjects, (DepotObjects != 1 ? "s" : ""));
+
+ if(DepotSpace <= 0){
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "Your depot is full. Remove surplus items before storing new ones.");
+ }
+}
+
+void SaveDepotBox(uint32 CreatureID, int Nr, Object Con){
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("moveuse::SaveDepotBox: Kreatur nicht gefunden.\n");
+ return;
+ }
+
+ if(!Con.exists()){
+ error("moveuse::SaveDepotBox: Übergebener Container existiert nicht.\n");
+ return;
+ }
+
+ if(!Con.getObjectType().getFlag(CONTAINER)){
+ error("moveuse::SaveDepotBox: Übergebenes Objekt ist kein Container.\n");
+ return;
+ }
+
+ int DepotObjects = CountObjects(Con) - 1;
+ Log("game", "Speichere Depot %d von %s ... Depotgröße: %d.\n",
+ Nr, Player->Name, DepotObjects);
+ print(3, "Depot von %s: berechnete freie Plätze %d, tatsächliche Objekte %d.\n",
+ Player->Name, Player->DepotSpace, DepotObjects);
+
+ SaveDepot(Player->PlayerData, Nr, Con);
+ Player->Depot = NONE;
+
+ Object Obj = GetFirstContainerObject(Con);
+ while(Obj != NONE){
+ Object Next = Obj.getNextObject();
+ Delete(Obj, -1);
+ Obj = Next;
+ }
+}
+
+void SendMail(Object Obj){
+ if(!Obj.exists()){
+ error("SendMail: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType != GetSpecialObject(PARCEL_NEW)
+ && ObjType != GetSpecialObject(LETTER_NEW)){
+ return;
+ }
+
+ print(3,"Versende Post...\n");
+
+ uint32 TextAttr = 0;
+ if(ObjType == GetSpecialObject(LETTER_NEW)){
+ TextAttr = Obj.getAttribute(TEXTSTRING);
+ }else{
+ ObjectType LabelType = GetSpecialObject(PARCEL_LABEL);
+ Object Label = GetFirstContainerObject(Obj);
+ while(Label != NONE){
+ if(Label.getObjectType() == LabelType){
+ break;
+ }
+ Label = Label.getNextObject();
+ }
+
+ if(Label == NONE){
+ return;
+ }
+
+ TextAttr = Label.getAttribute(TEXTSTRING);
+ }
+
+ const char *Text = GetDynamicString(TextAttr);
+ if(Text == NULL || Text[0] == 0){
+ return;
+ }
+
+
+ // TODO(fusion): Have some helper functions to read lines?
+ char Addressee[256];
+ char Town[256];
+ {
+ int ReadPos = 0;
+ int WritePos = 0;
+ while(Text[ReadPos] != 0 && Text[ReadPos] != '\n'){
+ if(WritePos < (int)(sizeof(Addressee) - 1)){
+ Addressee[WritePos] = Text[ReadPos];
+ WritePos += 1;
+ }
+ ReadPos += 1;
+ }
+ Addressee[WritePos] = 0;
+
+ WritePos = 0;
+ while(Text[ReadPos] != 0 && Text[ReadPos] != '\n'){
+ if(WritePos < (int)(sizeof(Town) - 1)){
+ Town[WritePos] = Text[ReadPos];
+ WritePos += 1;
+ }
+ ReadPos += 1;
+ }
+ Town[WritePos] = 0;
+
+ Trim(Addressee);
+ Trim(Town);
+ }
+
+ if(Addressee[0] == 0 || Town[0] == 0){
+ return;
+ }
+
+ // TODO(fusion): This constant for the size of creature names showing up again.
+ // Maybe `MAX_NAME_LENGTH`?
+ if(strlen(Addressee) >= 30){
+ return;
+ }
+
+ Log("game", "Post an %s in %s.\n", Addressee, Town);
+ int DepotNr = GetDepotNumber(Town);
+ if(DepotNr == -1){
+ return;
+ }
+
+ TPlayer *Player;
+ bool PlayerOnline;
+ uint32 CharacterID;
+ if(IdentifyPlayer(Addressee, true, true, &Player) == 0){
+ PlayerOnline = true;
+ CharacterID = Player->ID;
+ }else{
+ PlayerOnline = false;
+ CharacterID = GetCharacterID(Addressee);
+ if(CharacterID == 0){
+ return;
+ }
+ }
+
+ if(PlayerOnline && Player->Depot != NONE && Player->DepotNr == DepotNr){
+ print(3, "Adressat ist eingeloggt und hat Depot offen.\n");
+ try{
+ Move(0, Obj, Player->Depot, -1, true, NONE);
+ if(ObjType == GetSpecialObject(LETTER_NEW)){
+ Change(Obj, GetSpecialObject(LETTER_STAMPED), 0);
+ }else{
+ Change(Obj, GetSpecialObject(PARCEL_STAMPED), 0);
+ }
+ Log("game", "Post an %s in %s versandt.\n", Addressee, Town);
+ Player->DepotSpace -= CountObjects(Obj);
+ print(3, "Depot von %s hat jetzt %d freie Plätze.\n", Player->Name, Player->DepotSpace);
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE, "New mail has arrived.");
+ print(3, "Post erfolgreich versandt.\n");
+ }catch(RESULT r){
+ if(r != CONTAINERFULL){
+ error("SendMail (Depot offen): Exception %d.\n", r);
+ }
+
+ print(3, "Exception %d beim Postversand.\n", r);
+ }
+ }else{
+ print(3, "Adressat ist nicht eingeloggt oder hat Depot geschlossen.\n");
+
+ // TODO(fusion): The scope of this try block was unclear but I suppose
+ // this is correct due to how `Change` is also inside the try block in
+ // the try block above, when the player is online.
+ try{
+ if(ObjType == GetSpecialObject(LETTER_NEW)){
+ Change(Obj, GetSpecialObject(LETTER_STAMPED), 0);
+ }else{
+ Change(Obj, GetSpecialObject(PARCEL_STAMPED), 0);
+ }
+
+ TDynamicWriteBuffer WriteBuffer(1024);
+ SaveObjects(Obj, &WriteBuffer, true);
+ Delete(Obj, -1);
+
+ TDelayedMail *Mail = DelayedMail.at(DelayedMails);
+ Mail->CharacterID = CharacterID;
+ Mail->DepotNumber = DepotNr;
+ Mail->PacketSize = WriteBuffer.Position;
+ // TODO(fusion): Check if `WriteBuffer.Position` is not zero?
+ Mail->Packet = new uint8[WriteBuffer.Position];
+ memcpy(Mail->Packet, WriteBuffer.Data, WriteBuffer.Position);
+ DelayedMails += 1;
+
+ Log("game", "Post an %s in %s zurückgestellt.\n", Addressee, Town);
+ print(3, "Post zurückgestellt.\n");
+ if(PlayerOnline){
+ SendMails(Player->PlayerData);
+ }else{
+ LoadCharacterOrder(CharacterID);
+ }
+ }catch(RESULT r){
+ error("SendMail: Kann Paket nicht löschen (%d).\n", r);
+ }catch(const char *str){
+ error("SendMail: Kann Paket nicht schreiben (%s).\n", str);
+ }
+ }
+}
+
+void SendMails(TPlayerData *PlayerData){
+ if(PlayerData == NULL){
+ error("SendMails: PlayerData ist NULL.\n");
+ return;
+ }
+
+ if(PlayerData->CharacterID == 0){
+ error("SendMails: Slot ist nicht belegt.\n");
+ return;
+ }
+
+ if(PlayerData->Locked != getpid()){
+ error("SendMails: Slot ist nicht vom GameThread gesperrt.\n");
+ return;
+ }
+
+ for(int i = 0; i < DelayedMails; i += 1){
+ TDelayedMail *Mail = DelayedMail.at(i);
+ if(Mail->CharacterID != PlayerData->CharacterID){
+ continue;
+ }
+
+ int DepotNr = Mail->DepotNumber;
+ if(DepotNr < 0 || DepotNr >= 9){ // MAX_DEPOT ?
+ error("SendMails: Ungültige Depotnummer %d.\n", DepotNr);
+ DepotNr = 0;
+ }
+
+ int NewDepotSize = 0;
+ uint8 *NewDepot = NULL;
+ if(PlayerData->Depot[DepotNr] != NULL){
+ NewDepotSize = PlayerData->DepotSize[DepotNr] + Mail->PacketSize;
+ NewDepot = new uint8[NewDepotSize];
+ memcpy(&NewDepot[0], Mail->Packet, Mail->PacketSize);
+ memcpy(&NewDepot[Mail->PacketSize],
+ PlayerData->Depot[DepotNr],
+ PlayerData->DepotSize[DepotNr]);
+ delete[] PlayerData->Depot[DepotNr];
+ }else{
+ NewDepotSize = Mail->PacketSize + 5;
+ NewDepot = new uint8[NewDepotSize];
+ memcpy(&NewDepot[0], Mail->Packet, Mail->PacketSize);
+ TWriteBuffer WriteBuffer(&NewDepot[Mail->PacketSize], 5);
+ WriteBuffer.writeWord((uint16)GetSpecialObject(DEPOT_CHEST).TypeID);
+ WriteBuffer.writeByte(0xFF);
+ WriteBuffer.writeWord(0xFFFF);
+ }
+
+ PlayerData->Depot[DepotNr] = NewDepot;
+ PlayerData->DepotSize[DepotNr] = NewDepotSize;
+ PlayerData->Dirty = true;
+
+ Log("game", "Post an %s zugestellt.\n", PlayerData->Name);
+
+ if(Mail->Packet != NULL){
+ delete[] Mail->Packet;
+ }
+
+ // NOTE(fusion): A little swap and pop action.
+ DelayedMails -= 1;
+ *DelayedMail.at(i) = *DelayedMail.at(DelayedMails);
+ *DelayedMail.at(DelayedMails) = TDelayedMail{};
+
+ // NOTE(fusion): Check current index again after removal.
+ i -= 1;
+ }
+}
+
+void TextEffect(const char *Text, int x, int y, int z, int Radius){
+ if(Text == NULL){
+ error("TextEffect: Text existiert nicht.\n");
+ return;
+ }
+
+ TFindCreatures Search(Radius, Radius, x, y, FIND_PLAYERS);
+ while(true){
+ uint32 CharacterID = Search.getNext();
+ if(CharacterID == 0){
+ break;
+ }
+
+ TPlayer *Player = GetPlayer(CharacterID);
+ if(Player == NULL || Player->Connection == NULL){
+ continue;
+ }
+
+ int PlayerZ = Player->posz;
+ if(PlayerZ == z || Radius > 30 || (Radius > 7 && PlayerZ <= 7 && z <= 7)){
+ SendTalk(Player->Connection, 0, "", TALK_ANIMAL_LOW, x, y, z, Text);
+ }
+ }
+}
+
+void ExecuteAction(MoveUseEventType EventType, TMoveUseAction *Action,
+ Object User, Object Obj1, Object Obj2, Object *Temp){
+ try{
+ switch(Action->Action){
+ case MOVEUSE_ACTION_CREATEONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Action->Parameters[1];
+ int Value = Action->Parameters[2];
+ Object MapCon = GetMapContainer(CoordX, CoordY, CoordZ);
+ *Temp = CreateObject(MapCon, Type, Value);
+ break;
+ }
+
+ case MOVEUSE_ACTION_CREATE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ ObjectType Type = Action->Parameters[1];
+ int Value = Action->Parameters[2];
+ *Temp = CreateObject(Obj.getContainer(), Type, Value);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MONSTERONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ int Race = Action->Parameters[1];
+ CreateMonster(Race, CoordX, CoordY, CoordZ, 0, 0, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MONSTER:{
+ int ObjX, ObjY, ObjZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Race = Action->Parameters[1];
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ CreateMonster(Race, ObjX, ObjY, ObjZ, 0, 0, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_EFFECTONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ int Effect = Action->Parameters[1];
+ GraphicalEffect(CoordX, CoordY, CoordZ, Effect);
+ break;
+ }
+
+ case MOVEUSE_ACTION_EFFECT:{
+ int ObjX, ObjY, ObjZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ int Effect = Action->Parameters[1];
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ GraphicalEffect(ObjX, ObjY, ObjZ, Effect);
+ break;
+ }
+
+ case MOVEUSE_ACTION_TEXTONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ const char *Text = GetDynamicString(Action->Parameters[1]);
+ int Radius = Action->Parameters[2];
+ TextEffect(Text, CoordX, CoordY, CoordZ, Radius);
+ break;
+ }
+
+ case MOVEUSE_ACTION_TEXT:{
+ int ObjX, ObjY, ObjZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ const char *Text = GetDynamicString(Action->Parameters[1]);
+ int Radius = Action->Parameters[2];
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ TextEffect(Text, ObjX, ObjY, ObjZ, Radius);
+ break;
+ }
+
+ case MOVEUSE_ACTION_CHANGEONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType OldType = Action->Parameters[1];
+ ObjectType NewType = Action->Parameters[2];
+ int NewValue = Action->Parameters[3];
+ *Temp = GetFirstSpecObject(CoordX, CoordY, CoordZ, OldType);
+ if(*Temp != NONE){
+ ChangeObject(*Temp, NewType, NewValue);
+ }else{
+ error("ExecuteAction (CHANGEONMAP): Kein Objekt %d auf [%d,%d,%d].\n",
+ OldType.TypeID, CoordX, CoordY, CoordZ);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_CHANGE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ ObjectType NewType = Action->Parameters[1];
+ int NewValue = Action->Parameters[2];
+ ChangeObject(Obj, NewType, NewValue);
+ break;
+ }
+
+ case MOVEUSE_ACTION_CHANGEREL:{
+ int ObjX, ObjY, ObjZ, RelX, RelY, RelZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackRelativeCoordinate(Action->Parameters[1], &RelX, &RelY, &RelZ);
+ ObjectType OldType = Action->Parameters[2];
+ ObjectType NewType = Action->Parameters[3];
+ int NewValue = Action->Parameters[4];
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ *Temp = GetFirstSpecObject((ObjX + RelX), (ObjY + RelY), (ObjZ + RelZ), OldType);
+ if(*Temp != NONE){
+ ChangeObject(*Temp, NewType, NewValue);
+ }else{
+ error("ExecuteAction (CHANGEREL): Kein Objekt %d auf [%d,%d,%d].\n",
+ OldType.TypeID, (ObjX + RelX), (ObjY + RelY), (ObjZ + RelZ));
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_SETATTRIBUTE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ INSTANCEATTRIBUTE Attribute = (INSTANCEATTRIBUTE)Action->Parameters[1];
+ int Value = Action->Parameters[2];
+ Change(Obj, Attribute, Value);
+ break;
+ }
+
+ case MOVEUSE_ACTION_CHANGEATTRIBUTE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ INSTANCEATTRIBUTE Attribute = (INSTANCEATTRIBUTE)Action->Parameters[1];
+ int Amount = Action->Parameters[2];
+ // TODO(fusion): This is weird.
+ int OldValue = (int)Obj.getAttribute(Attribute);
+ int NewValue = OldValue + Amount;
+ if(NewValue <= 0){
+ if(OldValue == 0){
+ NewValue = 0;
+ }else{
+ NewValue = 1;
+ }
+ }
+ Change(Obj, Attribute, NewValue);
+ break;
+ }
+
+ case MOVEUSE_ACTION_SETQUESTVALUE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ int QuestNr = Action->Parameters[1];
+ int QuestValue = Action->Parameters[2];
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.isCreatureContainer()){
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature != NULL && Creature->Type == PLAYER){
+ ((TPlayer*)Creature)->SetQuestValue(QuestNr, QuestValue);
+ }else{
+ error("ExecuteAction (SETQUESTVALUE): Kreatur existiert nicht oder ist kein Spieler.\n");
+ }
+ }else{
+ error("ExecuteAction (SETQUESTVALUE): Objekt ist keine Kreatur, sondern Typ %d.\n", ObjType.TypeID);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_DAMAGE:{
+ Object AttackerObj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ Object VictimObj = GetEventObject(Action->Parameters[1], User, Obj1, Obj2, *Temp);
+ int DamageType = Action->Parameters[2];
+ int Damage = Action->Parameters[3];
+
+ TCreature *Attacker = NULL;
+ if(AttackerObj != NONE){
+ ObjectType AttackerType = AttackerObj.getObjectType();
+ if(AttackerType.isCreatureContainer()){
+ Attacker = GetCreature(AttackerObj);
+ }else if(AttackerType.getFlag(MAGICFIELD)
+ && AttackerObj.getAttribute(RESPONSIBLE) != 0){
+ int TotalExpireTime = (int)AttackerType.getAttribute(TOTALEXPIRETIME);
+ int CurExpireTime = (int)CronInfo(AttackerObj, false);
+ if((TotalExpireTime - CurExpireTime) < 5){
+ Attacker = GetCreature(AttackerObj.getAttribute(RESPONSIBLE));
+ }
+ }
+ }
+
+ ObjectType VictimType = VictimObj.getObjectType();
+ if(VictimType.isCreatureContainer()){
+ TCreature *Victim = GetCreature(VictimObj);
+ if(Victim != NULL){
+ Victim->Damage(Attacker, Damage, DamageType);
+ }else{
+ error("ExecuteAction (DAMAGE): Kreatur existiert nicht.\n");
+ }
+ }else{
+ error("ExecuteAction (DAMAGE): Objekt ist keine Kreatur, sondern Typ %d.\n", VictimType.TypeID);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_SETSTART:{
+ int StartX, StartY, StartZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackAbsoluteCoordinate(Action->Parameters[1], &StartX, &StartY, &StartZ);
+
+ if(!Obj.getObjectType().isCreatureContainer()){
+ throw ERROR;
+ }
+
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature == NULL || Creature->Type != PLAYER){
+ throw ERROR;
+ }
+
+ Creature->startx = StartX;
+ Creature->starty = StartY;
+ Creature->startz = StartZ;
+ ((TPlayer*)Creature)->SaveData();
+ break;
+ }
+
+ case MOVEUSE_ACTION_WRITENAME:{
+ Object WriterObj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ const char *Format = GetDynamicString(Action->Parameters[1]);
+ Object TargetObj = GetEventObject(Action->Parameters[2], User, Obj1, Obj2, *Temp);
+
+ if(WriterObj == NONE || !WriterObj.getObjectType().isCreatureContainer()
+ || TargetObj == NONE || !TargetObj.getObjectType().getFlag(TEXT)){
+ throw ERROR;
+ }
+
+ TCreature *Writer = GetCreature(WriterObj);
+ if(Writer == NULL){
+ throw ERROR;
+ }
+
+ // TODO(fusion): Some helper function to format text?
+ char Text[256];
+ {
+ int ReadPos = 0;
+ int WritePos = 0;
+ int TextSize = (int)sizeof(Text);
+ while(Format[ReadPos] != 0){
+ if(Format[ReadPos] == '%' && Format[ReadPos + 1] == 'N'){
+ // NOTE(fusion): Make sure we don't overflow the buffer.
+ int NameLen = (int)strlen(Writer->Name);
+ if(NameLen > 0){
+ if((WritePos + NameLen) > TextSize){
+ NameLen = TextSize - WritePos;
+ }
+
+ if(NameLen > 0){
+ memcpy(&Text[WritePos], Writer->Name, NameLen);
+ WritePos += NameLen;
+ }
+ }
+
+ ReadPos += 2;
+ }else{
+ // NOTE(fusion): Make sure we don't overflow the buffer.
+ if(WritePos < TextSize){
+ Text[WritePos] = Format[ReadPos];
+ WritePos += 1;
+ }
+
+ ReadPos += 1;
+ }
+ }
+
+ if(WritePos >= TextSize){
+ throw ERROR;
+ }
+ }
+
+ DeleteDynamicString(TargetObj.getAttribute(TEXTSTRING));
+ DeleteDynamicString(TargetObj.getAttribute(EDITOR));
+ Change(TargetObj, TEXTSTRING, AddDynamicString(Text));
+ Change(TargetObj, EDITOR, 0);
+ break;
+ }
+
+ case MOVEUSE_ACTION_WRITETEXT:{
+ const char *Text = GetDynamicString(Action->Parameters[0]);
+ Object Obj = GetEventObject(Action->Parameters[1], User, Obj1, Obj2, *Temp);
+
+ if(Obj == NONE || !Obj.getObjectType().getFlag(TEXT)){
+ throw ERROR;
+ }
+
+ DeleteDynamicString(Obj.getAttribute(TEXTSTRING));
+ DeleteDynamicString(Obj.getAttribute(EDITOR));
+ Change(Obj, TEXTSTRING, AddDynamicString(Text));
+ Change(Obj, EDITOR, 0);
+ break;
+ }
+
+ case MOVEUSE_ACTION_LOGOUT:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+
+ if(Obj == NONE || !Obj.getObjectType().isCreatureContainer()){
+ throw ERROR;
+ }
+
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature == NULL){
+ throw ERROR;
+ }
+
+ Creature->StartLogout(true, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVEALLONMAP:{
+ int OrigX, OrigY, OrigZ, DestX, DestY, DestZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &OrigX, &OrigY, &OrigZ);
+ UnpackAbsoluteCoordinate(Action->Parameters[1], &DestX, &DestY, &DestZ);
+ Object First = GetFirstObject(OrigX, OrigY, OrigZ);
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveAllObjects(First, Dest, NONE, false);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVEALL:{
+ int DestX, DestY, DestZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackAbsoluteCoordinate(Action->Parameters[1], &DestX, &DestY, &DestZ);
+ Object First = GetFirstContainerObject(Obj.getContainer());
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ // TODO(fusion): Why do we set the `Exclude` parameter to `First`?
+ // Maybe we just don't want to check if it is `NONE` can call
+ // `getNextObject()` like MOVETOPONMAP?
+ MoveAllObjects(First, Dest, First, false);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVEALLREL:{
+ int ObjX, ObjY, ObjZ, RelX, RelY, RelZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackRelativeCoordinate(Action->Parameters[1], &RelX, &RelY, &RelZ);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Object First = GetFirstContainerObject(Obj.getContainer());
+ Object Dest = GetMapContainer((ObjX + RelX), (ObjY + RelY), (ObjZ + RelZ));
+ // TODO(fusion): Same as above?
+ MoveAllObjects(First, Dest, First, false);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVETOPONMAP:{
+ int OrigX, OrigY, OrigZ, DestX, DestY, DestZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &OrigX, &OrigY, &OrigZ);
+ ObjectType Type = Action->Parameters[1];
+ UnpackAbsoluteCoordinate(Action->Parameters[2], &DestX, &DestY, &DestZ);
+ Object Obj = GetFirstSpecObject(OrigX, OrigY, OrigZ, Type);
+ if(Obj != NONE){
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveAllObjects(Obj.getNextObject(), Dest, NONE, true);
+ }else{
+ error("ExecuteAction (MOVETOPONMAP): Kein Objekt %d auf [%d,%d,%d].\n",
+ Type.TypeID, OrigX, OrigY, OrigZ);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVETOP:{
+ int DestX, DestY, DestZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackAbsoluteCoordinate(Action->Parameters[1], &DestX, &DestY, &DestZ);
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveAllObjects(Obj.getNextObject(), Dest, NONE, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVETOPREL:{
+ int ObjX, ObjY, ObjZ, RelX, RelY, RelZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackRelativeCoordinate(Action->Parameters[1], &RelX, &RelY, &RelZ);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Object Dest = GetMapContainer((ObjX + RelX), (ObjY + RelY), (ObjZ + RelZ));
+ MoveAllObjects(Obj.getNextObject(), Dest, NONE, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVE:{
+ int DestX, DestY, DestZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackAbsoluteCoordinate(Action->Parameters[1], &DestX, &DestY, &DestZ);
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveOneObject(Obj, Dest);
+ break;
+ }
+
+ case MOVEUSE_ACTION_MOVEREL:{
+ int ObjX, ObjY, ObjZ, RelX, RelY, RelZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackRelativeCoordinate(Action->Parameters[1], &RelX, &RelY, &RelZ);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Object Dest = GetMapContainer((ObjX + RelX), (ObjY + RelY), (ObjZ + RelZ));
+ MoveOneObject(Obj, Dest);
+ break;
+ }
+
+ case MOVEUSE_ACTION_RETRIEVE:{
+ int ObjX, ObjY, ObjZ, OrigRelX, OrigRelY, OrigRelZ, DestRelX, DestRelY, DestRelZ;
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ UnpackRelativeCoordinate(Action->Parameters[1], &OrigRelX, &OrigRelY, &OrigRelZ);
+ UnpackRelativeCoordinate(Action->Parameters[2], &DestRelX, &DestRelY, &DestRelZ);
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+ Object Top = GetTopObject((ObjX + OrigRelX), (ObjY + OrigRelY), (ObjZ + OrigRelZ), false);
+ if(Top != NONE && !Top.getObjectType().getFlag(UNMOVE)){
+ Object Dest = GetMapContainer((ObjX + DestRelX), (ObjY + DestRelY), (ObjZ + DestRelZ));
+ MoveOneObject(Obj, Dest);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_DELETEALLONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ Object First = GetFirstObject(CoordX, CoordY, CoordZ);
+ // TODO(fusion): Same as `MOVEALL`.
+ DeleteAllObjects(First, First, false);
+ break;
+ }
+
+ case MOVEUSE_ACTION_DELETETOPONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Action->Parameters[1];
+ Object First = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ // TODO(fusion): Sometimes we throw, sometimes we print an error,
+ // sometimes we ignore it, what a mess.
+ if(First == NONE){
+ throw ERROR;
+ }
+ DeleteAllObjects(First, First, true);
+ break;
+ }
+
+ case MOVEUSE_ACTION_DELETEONMAP:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Action->Parameters[1];
+ Object Obj = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ if(Obj != NONE){
+ bool IsUseEvent = (EventType == MOVEUSE_EVENT_USE
+ || EventType == MOVEUSE_EVENT_MULTIUSE);
+ Delete(Obj, (IsUseEvent ? 1 : -1));
+ }else{
+ error("ExecuteAction (DELETEONMAP): Kein Objekt %d auf [%d,%d,%d].\n",
+ Type.TypeID, CoordX, CoordY, CoordZ);
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_DELETE:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ if(Obj == NONE){
+ throw ERROR;
+ }
+ bool IsUseEvent = (EventType == MOVEUSE_EVENT_USE
+ || EventType == MOVEUSE_EVENT_MULTIUSE);
+ Delete(Obj, (IsUseEvent ? 1 : -1));
+ break;
+ }
+
+ case MOVEUSE_ACTION_DELETEININVENTORY:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ ObjectType Type = Action->Parameters[1];
+ int Value = Action->Parameters[2];
+ if(Obj.getObjectType().isCreatureContainer()){
+ DeleteAtCreature(Obj.getCreatureID(), Type, 1, Value);
+ }else{
+ throw ERROR;
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_DESCRIPTION:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ Object Cr = GetEventObject(Action->Parameters[1], User, Obj1, Obj2, *Temp);
+ if(Cr.getObjectType().isCreatureContainer()){
+ TCreature *Creature = GetCreature(Cr);
+ if(Creature != NULL && Creature->Type == PLAYER){
+ SendMessage(Creature->Connection, TALK_INFO_MESSAGE,
+ "%s.", GetInfo(Obj));
+ }
+ }
+ break;
+ }
+
+ case MOVEUSE_ACTION_LOADDEPOT:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Action->Parameters[1];
+ Object Cr = GetEventObject(Action->Parameters[2], User, Obj1, Obj2, *Temp);
+ int DepotNr = Action->Parameters[3];
+
+ if(!Cr.getObjectType().isCreatureContainer()){
+ throw ERROR;
+ }
+
+ Object Depot = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ if(Depot == NONE){
+ throw ERROR;
+ }
+
+ LoadDepotBox(Cr.getCreatureID(), DepotNr, Depot);
+ break;
+ }
+
+ case MOVEUSE_ACTION_SAVEDEPOT:{
+ int CoordX, CoordY, CoordZ;
+ UnpackAbsoluteCoordinate(Action->Parameters[0], &CoordX, &CoordY, &CoordZ);
+ ObjectType Type = Action->Parameters[1];
+ Object Cr = GetEventObject(Action->Parameters[2], User, Obj1, Obj2, *Temp);
+ int DepotNr = Action->Parameters[3];
+
+ if(!Cr.getObjectType().isCreatureContainer()){
+ throw ERROR;
+ }
+
+ Object Depot = GetFirstSpecObject(CoordX, CoordY, CoordZ, Type);
+ if(Depot == NONE){
+ throw ERROR;
+ }
+
+ SaveDepotBox(Cr.getCreatureID(), DepotNr, Depot);
+ break;
+ }
+
+ case MOVEUSE_ACTION_SENDMAIL:{
+ Object Obj = GetEventObject(Action->Parameters[0], User, Obj1, Obj2, *Temp);
+ SendMail(Obj);
+ break;
+ }
+
+ case MOVEUSE_ACTION_NOP:{
+ // no-op
+ break;
+ }
+
+ default:{
+ error("ExecuteAction: Unbekannte Aktion %d.\n", Action->Action);
+ break;
+ }
+ }
+ }catch(RESULT r){
+ error("ExecuteAction: Exception %d (Aktion %d).\n", r, Action->Action);
+ }
+}
+
+bool HandleEvent(MoveUseEventType EventType, Object User, Object Obj1, Object Obj2){
+ static int RecursionDepth = 0;
+
+ if(RecursionDepth > 10){
+ error("HandleEvent: Endlosschleife im Move/Use-System vermutet.\n");
+ return false;
+ }
+
+ bool Result = false;
+ RecursionDepth += 1;
+ TMoveUseDatabase *DB = &MoveUseDatabases[EventType];
+ for(int RuleNr = 1; RuleNr <= DB->NumberOfRules; RuleNr += 1){
+ bool Execute = true;
+ Object Temp = NONE;
+ TMoveUseRule *Rule = DB->Rules.at(RuleNr);
+ for(int ConditionNr = Rule->FirstCondition;
+ ConditionNr <= Rule->LastCondition;
+ ConditionNr += 1){
+ TMoveUseCondition *Condition = MoveUseConditions.at(ConditionNr);
+ if(!CheckCondition(EventType, Condition, User, Obj1, Obj2, &Temp)){
+ Execute = false;
+ break;
+ }
+ }
+
+ if(Execute){
+ for(int ActionNr = Rule->FirstAction;
+ ActionNr <= Rule->LastAction;
+ ActionNr += 1){
+ TMoveUseAction *Action = MoveUseActions.at(ActionNr);
+ ExecuteAction(EventType, Action, User, Obj2, Obj2, &Temp);
+ }
+ Result = true;
+ }
+ }
+ RecursionDepth -= 1;
+ return Result;
+}
+
+// Event Dispatching
+// =============================================================================
+void UseContainer(uint32 CreatureID, Object Con, int NextContainerNr){
+ if(!Con.exists()){
+ error("UseContainer: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ConType = Con.getObjectType();
+ if(!ConType.getFlag(CONTAINER)){
+ error("UseContainer: Übergebenes Objekt ist kein Container.\n");
+ throw ERROR;
+ }
+
+ if(NextContainerNr < 0 || NextContainerNr >= NARRAY(TPlayer::OpenContainer)){
+ error("UseContainer: Ungültige Fensternummer %d.\n", NextContainerNr);
+ throw ERROR;
+ }
+
+ if(ConType.isCreatureContainer()){
+ throw NOTUSABLE;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseContainer: Spieler %d existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ bool ContainerClosed = false;
+ for(int ContainerNr = 0;
+ ContainerNr < NARRAY(TPlayer::OpenContainer);
+ ContainerNr += 1){
+ if(Player->GetOpenContainer(ContainerNr) == Con){
+ Player->SetOpenContainer(ContainerNr, NONE);
+ SendCloseContainer(Player->Connection, ContainerNr);
+ ContainerClosed = true;
+ break;
+ }
+ }
+
+ if(!ContainerClosed){
+ Player->SetOpenContainer(NextContainerNr, Con);
+ SendContainer(Player->Connection, NextContainerNr);
+ }
+}
+
+void UseChest(uint32 CreatureID, Object Chest){
+ if(!Chest.exists()){
+ error("UseChest: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ChestType = Chest.getObjectType();
+ if(!ChestType.getFlag(CHEST)){
+ error("UseChest: Übergebenes Objekt ist keine Schatztruhe.\n");
+ throw ERROR;
+ }
+
+ int ChestX, ChestY, ChestZ;
+ GetObjectCoordinates(Chest, &ChestX, &ChestY, &ChestZ);
+
+ int QuestNr = (int)Chest.getAttribute(CHESTQUESTNUMBER);
+ if(QuestNr < 0 || QuestNr >= NARRAY(TPlayer::QuestValues)){
+ error("UseChest: Ungültige Nummer %d auf Schatztruhe an Position [%d,%d,%d].\n",
+ QuestNr, ChestX, ChestY, ChestZ);
+ throw ERROR;
+ }
+
+ if(CountObjectsInContainer(Chest) != 1){
+ error("UseChest: Schatztruhe auf Position [%d,%d,%d] enthält nicht genau ein Objekt.\n",
+ ChestX, ChestY, ChestZ);
+ throw ERROR;
+ }
+
+ Object Treasure = GetFirstContainerObject(Chest);
+ ObjectType TreasureType = Treasure.getObjectType();
+ if(TreasureType.getFlag(UNMOVE) || !TreasureType.getFlag(TAKE)){
+ error("UseChest: Schatz auf Position [%d,%d,%d] ist nicht nehmbar.\n",
+ ChestX, ChestY, ChestZ);
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseChest: Spieler %u existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ if(Player->GetQuestValue(QuestNr) != 0){
+ print(3, "Schatztruhe ist schon bekannt.\n");
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "The %s is empty.", ChestType.getName(-1));
+ return;
+ }
+
+ if(!CheckRight(CreatureID, UNLIMITED_CAPACITY)){
+ int TreasureWeight = GetCompleteWeight(Treasure);
+ int InventoryWeight = GetInventoryWeight(CreatureID);
+ int MaxWeight = Player->Skills[SKILL_CARRY_STRENGTH]->Get() * 100;
+ if((InventoryWeight + TreasureWeight) > MaxWeight
+ || CheckRight(CreatureID, ZERO_CAPACITY)){
+ bool Multiple = TreasureType.getFlag(CUMULATIVE)
+ && Treasure.getAttribute(AMOUNT) > 1;
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "You have found %s. Weighing %d.%02d oz %s too heavy.",
+ GetName(Treasure), (TreasureWeight / 100), (TreasureWeight % 100),
+ (Multiple ? "they are" : "it is"));
+ return;
+ }
+ }
+
+ // NOTE(fusion): Copy treasure object and attempt to move into the player's
+ // inventory. The original version was somewhat confusing but this should
+ // achieve the same end result.
+ Treasure = Copy(Chest, Treasure);
+ bool CheckContainers = TreasureType.getFlag(MOVEMENTEVENT);
+ bool TreasureMoved = false;
+ for(int i = 0; i < 2 && !TreasureMoved; i += 1){
+ for(int Position = INVENTORY_FIRST;
+ Position <= INVENTORY_LAST && !TreasureMoved;
+ Position += 1){
+ try{
+ Object BodyObj = GetBodyObject(CreatureID, Position);
+ if(CheckContainers){
+ if(BodyObj != NONE && BodyObj.getObjectType().getFlag(CONTAINER)){
+ Move(CreatureID, Treasure, BodyObj, -1, true, NONE);
+ TreasureMoved = true;
+ }
+ }else{
+ if(BodyObj == NONE){
+ Object BodyCon = GetBodyContainer(CreatureID, Position);
+ Move(CreatureID, Treasure, BodyCon, -1, true, NONE);
+ TreasureMoved = true;
+ }
+ }
+ }catch(RESULT r){
+ // no-op
+ }
+ }
+ CheckContainers = !CheckContainers;
+ }
+
+ if(!TreasureMoved){
+ bool Multiple = TreasureType.getFlag(CUMULATIVE)
+ && Treasure.getAttribute(AMOUNT) > 1;
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "You have found %s, but you have no room to take %s.\n",
+ GetName(Treasure), (Multiple ? "them" : "it"));
+ Delete(Treasure, -1);
+ return;
+ }
+
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "You have found %s.\n", GetName(Treasure));
+ Player->SetQuestValue(QuestNr, 1);
+}
+
+void UseLiquidContainer(uint32 CreatureID, Object Obj, Object Dest){
+ if(!Obj.exists()){
+ error("UseLiquidContainer: Übergebenes Objekt Obj existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!Dest.exists()){
+ error("UseLiquidContainer: Übergebenes Objekt Dest existiert nicht (Obj %d).\n",
+ ObjType.TypeID);
+ throw ERROR;
+ }
+
+ if(!ObjType.getFlag(LIQUIDCONTAINER)){
+ error("UseLiquidContainer: Übergebenes Objekt ist kein Flüssigkeitscontainer.\n");
+ throw ERROR;
+ }
+
+ ObjectType DestType = Dest.getObjectType();
+ int LiquidType = Obj.getAttribute(CONTAINERLIQUIDTYPE);
+
+ // NOTE(fusion): Fill liquid container.
+ if(DestType.getFlag(LIQUIDSOURCE) && LiquidType == LIQUID_NONE){
+ int DestLiquidType = (int)DestType.getAttribute(SOURCELIQUIDTYPE);
+ Change(Obj, CONTAINERLIQUIDTYPE, DestLiquidType);
+ return;
+ }
+
+ // NOTE(fusion): Transfer liquid to another container.
+ if(DestType.getFlag(LIQUIDCONTAINER) && LiquidType != LIQUID_NONE
+ && Dest.getAttribute(CONTAINERLIQUIDTYPE) == LIQUID_NONE){
+ Change(Dest, CONTAINERLIQUIDTYPE, LiquidType);
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ return;
+ }
+
+ // NOTE(fusion): This is similar to the target picking logic for runes except
+ // we want to prioritize the user drinking the liquid, instead of otherwise
+ // throwing it on the ground.
+ if(!DestType.isCreatureContainer()){
+ Object Help = GetFirstContainerObject(Dest.getContainer());
+ while(Help != NONE){
+ ObjectType HelpType = Help.getObjectType();
+ if(HelpType.isCreatureContainer() && Help.getCreatureID() == CreatureID){
+ Dest = Help;
+ DestType = HelpType;
+ break;
+ }
+ Help = Help.getNextObject();
+ }
+ }
+
+ // NOTE(fusion): Spill liquid.
+ if(!DestType.isCreatureContainer() || Dest.getCreatureID() != CreatureID){
+ if(LiquidType == LIQUID_NONE){
+ throw NOTUSABLE;
+ }
+
+ int DestX, DestY, DestZ;
+ GetObjectCoordinates(Dest, &DestX, &DestY, &DestZ);
+ if(!CoordinateFlag(DestX, DestY, DestZ, BANK)
+ || CoordinateFlag(DestX, DestY, DestZ, UNLAY)){
+ throw NOROOM;
+ }
+
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ CreatePool(GetMapContainer(DestX, DestY, DestZ),
+ GetSpecialObject(BLOOD_POOL), LiquidType);
+ return;
+ }
+
+ // NOTE(fusion): Drink liquid.
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseLiquidContainer: Spieler %d existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ switch(LiquidType){
+ case LIQUID_NONE:{
+ // no-op
+ break;
+ }
+
+ case LIQUID_WINE:
+ case LIQUID_BEER:{
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ int DrunkLevel = Player->Skills[SKILL_DRUNKEN]->TimerValue();
+ if(DrunkLevel < 5){
+ Player->SetTimer(SKILL_DRUNKEN, (DrunkLevel + 1), 120, 120, -1);
+ }
+ Talk(CreatureID, TALK_SAY, NULL, "Aah...", false);
+ break;
+ }
+
+ case LIQUID_SLIME:{
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ Player->Damage(NULL, 200, DAMAGE_POISON_PERIODIC);
+ Talk(CreatureID, TALK_SAY, NULL, "Urgh!", false);
+ break;
+ }
+
+ case LIQUID_URINE:{
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ Talk(CreatureID, TALK_SAY, NULL, "Urgh!", false);
+ break;
+ }
+
+ case LIQUID_MANA:
+ case LIQUID_LIFE:{
+ DrinkPotion(CreatureID, Obj);
+ Talk(CreatureID, TALK_SAY, NULL, "Aaaah...", false);
+ break;
+ }
+
+ case LIQUID_LEMONADE:{
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ Talk(CreatureID, TALK_SAY, NULL, "Mmmh.", false);
+ break;
+ }
+
+ default:{
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+ Talk(CreatureID, TALK_SAY, NULL, "Gulp.", false);
+ break;
+ }
+ }
+}
+
+void UseFood(uint32 CreatureID, Object Obj){
+ if(!Obj.exists()){
+ error("UseFood: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(FOOD)){
+ error("UseFood: Übergebenes Objekt ist kein Nahrungsmittel.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseFood: Kreatur %u existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ int ObjFoodTime = (int)ObjType.getAttribute(NUTRITION) * 12;
+ int CurFoodTime = Player->Skills[SKILL_FED]->TimerValue();
+ int MaxFoodTime = Player->Skills[SKILL_FED]->Max;
+ if((CurFoodTime + ObjFoodTime) > MaxFoodTime){
+ throw FEDUP;
+ }
+
+ Player->SetTimer(SKILL_FED, (CurFoodTime + ObjFoodTime), 0, 0, -1);
+ Delete(Obj, -1);
+}
+
+void UseTextObject(uint32 CreatureID, Object Obj){
+ if(!Obj.exists()){
+ error("UseTextObject: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(TEXT)){
+ error("UseTextObject: Übergebenes Objekt ist kein Schriftstück.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseTextObject: Kreatur %u existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ SendEditText(Player->Connection, Obj);
+}
+
+void UseAnnouncer(uint32 CreatureID, Object Obj){
+ if(!Obj.exists()){
+ error("UseAnnouncer: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(INFORMATION)){
+ error("UseAnnouncer: Übergebenes Objekt liefert keine Informationen.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseAnnouncer: Kreatur %d existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ int Information = (int)ObjType.getAttribute(INFORMATIONTYPE);
+ switch(Information){
+ case 1:{ // INFORMATION_DATETIME
+ int Year, Cycle, Day, Hour, Minute;
+ GetDate(&Year, &Cycle, &Day);
+ GetTime(&Hour, &Minute);
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "It is the %dth day of the %dth cycle in the year %d. The time is %d:%.2d.",
+ Day, Cycle, Year, Hour, Minute);
+ break;
+ }
+
+ case 2:{ // INFORMATION_TIME
+ int Hour, Minute;
+ GetTime(&Hour, &Minute);
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE,
+ "The time is %d:%.2d.", Hour, Minute);
+ break;
+ }
+
+ case 3:{ // INFORMATION_BLESSINGS
+ int NumBlessings = 0;
+ char Help[256] = {};
+ strcpy(Help, "Received blessings:");
+
+ if(Player->GetQuestValue(101) != 0){
+ strcat(Help, "\nWisdom of Solitude");
+ NumBlessings += 1;
+ }
+
+ if(Player->GetQuestValue(102) != 0){
+ strcat(Help, "\nSpark of the Phoenix");
+ NumBlessings += 1;
+ }
+
+ if(Player->GetQuestValue(103) != 0){
+ strcat(Help, "\nFire of the Suns");
+ NumBlessings += 1;
+ }
+
+ if(Player->GetQuestValue(104) != 0){
+ strcat(Help, "\nSpiritual Shielding");
+ NumBlessings += 1;
+ }
+
+ if(Player->GetQuestValue(105) != 0){
+ strcat(Help, "\nEmbrace of Tibia");
+ NumBlessings += 1;
+ }
+
+ if(NumBlessings == 0){
+ strcpy(Help, "No blessings received.");
+ }
+
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE, "%s", Help);
+ break;
+ }
+
+ case 4:{ // INFORMATION_SPELLBOOK
+ SendEditText(Player->Connection, Obj);
+ break;
+ }
+
+ default:{
+ error("UseAnnouncer: Ungültiger Informationstyp %d.\n", Information);
+ break;
+ }
+ }
+}
+
+void UseKeyDoor(uint32 CreatureID, Object Key, Object Door){
+ if(!Key.exists()){
+ error("UseKeyDoor: Übergebenes Objekt Key existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Door.exists()){
+ error("UseKeyDoor: Übergebenes Objekt Door existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType KeyType = Key.getObjectType();
+ if(!KeyType.getFlag(KEY)){
+ error("UseKeyDoor: Übergebenes Objekt ist kein Schlüssel.\n");
+ throw ERROR;
+ }
+
+ ObjectType DoorType = Door.getObjectType();
+ if(!DoorType.getFlag(KEYDOOR)){
+ throw NOTUSABLE;
+ }
+
+ int KeyNumber = (int)Key.getAttribute(KEYNUMBER);
+ int KeyHoleNumber = (int)Door.getAttribute(KEYHOLENUMBER);
+ if(KeyNumber == 0 || KeyHoleNumber == 0 || KeyNumber != KeyHoleNumber){
+ throw NOKEYMATCH;
+ }
+
+ ObjectType KeyDoorTarget = (int)DoorType.getAttribute(KEYDOORTARGET);
+ if(KeyDoorTarget.isMapContainer()){
+ error("UseKeyDoor: Zieltür für Tür %d nicht spezifiziert.\n",
+ DoorType.TypeID);
+ throw ERROR;
+ }
+
+ if(KeyDoorTarget.getFlag(UNPASS)){
+ ClearField(Door, NONE);
+ }
+
+ Change(Door, KeyDoorTarget, 0);
+}
+
+void UseNameDoor(uint32 CreatureID, Object Door){
+ if(!Door.exists()){
+ error("UseNameDoor: Übergebenes Objekt Door existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType DoorType = Door.getObjectType();
+ if(!DoorType.getFlag(NAMEDOOR)){
+ error("UseNameDoor: Übergebenes Objekt Door ist keine beschriftete Tür.\n");
+ throw ERROR;
+ }
+
+ if(!DoorType.getFlag(TEXT)){
+ error("UseNameDoor: Übergebenes Objekt Door trägt keinen Text.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseNameDoor: Spieler existiert nicht.\n");
+ throw ERROR;
+ }
+
+ int DoorX, DoorY, DoorZ;
+ GetObjectCoordinates(Door, &DoorX, &DoorY, &DoorZ);
+
+ uint16 HouseID = GetHouseID(DoorX, DoorY, DoorZ);
+ if(HouseID == 0){
+ error("UseNameDoor: Koordinate [%d,%d,%d] gehört zu keinem Haus.\n",
+ DoorX, DoorY, DoorZ);
+ throw ERROR;
+ }
+
+ if(!IsOwner(HouseID, Player)
+ && !CheckRight(CreatureID, OPEN_NAMEDOORS)
+ && !MayOpenDoor(Door, Player)){
+ throw NOTACCESSIBLE;
+ }
+
+ ObjectType NameDoorTarget = (int)DoorType.getAttribute(NAMEDOORTARGET);
+ if(NameDoorTarget.isMapContainer()){
+ error("UseNameDoor: Zieltür für Tür %d nicht spezifiziert.\n",
+ DoorType.TypeID);
+ throw ERROR;
+ }
+
+ if(NameDoorTarget.getFlag(UNPASS)){
+ ClearField(Door, NONE);
+ }
+
+ Change(Door, NameDoorTarget, 0);
+}
+
+void UseLevelDoor(uint32 CreatureID, Object Door){
+ if(!Door.exists() || !Door.getObjectType().getFlag(LEVELDOOR)
+ || !Door.getContainer().getObjectType().isMapContainer()){
+ error("UseLevelDoor: Übergebenes Objekt Door ist keine Level-Tür.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseLevelDoor: Spieler existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType DoorType = Door.getObjectType();
+ if(!DoorType.getFlag(UNPASS)){
+ throw NOTUSABLE;
+ }
+
+ int DoorLevel = (int)Door.getAttribute(DOORLEVEL);
+ int PlayerLevel = Player->Skills[SKILL_LEVEL]->Get();
+ if(PlayerLevel < DoorLevel){
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE, "%s.", GetInfo(Door));
+ return;
+ }
+
+ ObjectType LevelDoorTarget = (int)DoorType.getAttribute(LEVELDOORTARGET);
+ if(LevelDoorTarget.isMapContainer() || LevelDoorTarget.getFlag(UNPASS)){
+ error("UseLevelDoor: Zieltür für Tür %d nicht spezifiziert oder nicht passierbar.\n",
+ DoorType.TypeID);
+ throw ERROR;
+ }
+
+ Change(Door, LevelDoorTarget, 0);
+ Move(0, Player->CrObject, Door.getContainer(), -1, false, NONE);
+}
+
+void UseQuestDoor(uint32 CreatureID, Object Door){
+ if(!Door.exists() || !Door.getObjectType().getFlag(QUESTDOOR)
+ || !Door.getContainer().getObjectType().isMapContainer()){
+ error("UseQuestDoor: Übergebenes Objekt Door ist keine Quest-Tür.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseQuestDoor: Spieler existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType DoorType = Door.getObjectType();
+ if(!DoorType.getFlag(UNPASS)){
+ throw NOTUSABLE;
+ }
+
+ int QuestNr = Door.getAttribute(DOORQUESTNUMBER);
+ int QuestValue = Door.getAttribute(DOORQUESTVALUE);
+ if(Player->GetQuestValue(QuestNr) != QuestValue){
+ SendMessage(Player->Connection, TALK_INFO_MESSAGE, "%s.", GetInfo(Door));
+ return;
+ }
+
+ ObjectType QuestDoorTarget = (int)DoorType.getAttribute(QUESTDOORTARGET);
+ if(QuestDoorTarget.isMapContainer() || QuestDoorTarget.getFlag(UNPASS)){
+ error("UseQuestDoor: Zieltür für Tür %d nicht spezifiziert oder nicht passierbar.\n",
+ DoorType.TypeID);
+ throw ERROR;
+ }
+
+ Change(Door, QuestDoorTarget, 0);
+ Move(0, Player->CrObject, Door.getContainer(), -1, false, NONE);
+}
+
+void UseWeapon(uint32 CreatureID, Object Weapon, Object Target){
+ if(!Weapon.exists() || !Weapon.getObjectType().isCloseWeapon()){
+ error("UseWeapon: Übergebene Waffe existiert nicht oder ist keine Waffe.\n");
+ throw ERROR;
+ }
+
+ if(!Target.exists() || !Target.getObjectType().getFlag(DESTROY)){
+ error("UseWeapon: Übergebenes Ziel existiert nicht oder ist nicht zerstörbar.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseWeapon: Spieler %d existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ if(!Target.getContainer().getObjectType().isMapContainer()){
+ throw NOTACCESSIBLE;
+ }
+
+ GraphicalEffect(Target, EFFECT_POFF);
+ if(random(1, 3) == 1){
+ // TODO(fusion): This is very similar to what is done in `ProcessCronSystem`
+ // and it is probably some inlined helper function to transform items that
+ // might be containers.
+ ObjectType TargetType = Target.getObjectType();
+ ObjectType DestroyTarget = (int)TargetType.getAttribute(DESTROYTARGET);
+ if(TargetType.getFlag(CONTAINER)){
+ int Remainder = 0;
+ if(!DestroyTarget.isMapContainer() && DestroyTarget.getFlag(CONTAINER)){
+ Remainder = (int)DestroyTarget.getAttribute(CAPACITY);
+ }
+
+ Empty(Target, Remainder);
+ }
+ Change(Target, DestroyTarget, 0);
+ }
+}
+
+void UseChangeObject(uint32 CreatureID, Object Obj){
+ if(!Obj.exists() || !Obj.getObjectType().getFlag(CHANGEUSE)){
+ error("UseChangeObject: Objekt existiert nicht oder ist kein CHANGEUSE-Objekt.\n");
+ throw ERROR;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("UseChangeObject: Spieler %d existiert nicht.\n", CreatureID);
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ ObjectType ChangeTarget = (int)ObjType.getAttribute(CHANGETARGET);
+ if(!ObjType.getFlag(UNPASS)
+ && !ChangeTarget.isMapContainer()
+ && ChangeTarget.getFlag(UNPASS)){
+ ClearField(Obj, NONE);
+ }else if(!ObjType.getFlag(UNLAY)
+ && !ChangeTarget.isMapContainer()
+ && ChangeTarget.getFlag(UNLAY)){
+ // TODO(fusion): This is similar to `ClearField` except we look for an
+ // field without `UNLAY`. It is probably an inlined function.
+ int ObjX, ObjY, ObjZ;
+ GetObjectCoordinates(Obj, &ObjX, &ObjY, &ObjZ);
+
+ int OffsetX[4] = { 1, 0, -1, 0};
+ int OffsetY[4] = { 0, 1, 0, -1};
+ for(int i = 0; i < 4; i += 1){
+ int DestX = ObjX + OffsetX[i];
+ int DestY = ObjY + OffsetY[i];
+ int DestZ = ObjZ;
+ if(CoordinateFlag(DestX, DestY, DestZ, BANK)
+ && !CoordinateFlag(DestX, DestY, DestZ, UNPASS)){
+ Object Dest = GetMapContainer(DestX, DestY, DestZ);
+ MoveAllObjects(Obj.getNextObject(), Dest, NONE, true);
+ break;
+ }
+ }
+ }
+
+ Change(Obj, ChangeTarget, 0);
+}
+
+void UseObject(uint32 CreatureID, Object Obj){
+ if(!Obj.exists()){
+ error("UseObjects: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Obj.getObjectType().getFlag(USEEVENT)){
+ error("UseObjects: Übergebenes Objekt ist nicht benutzbar.\n");
+ throw ERROR;
+ }
+
+ Object User = NONE;
+ if(CreatureID != 0){
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature != NULL){
+ User = Creature->CrObject;
+ }
+ }
+
+ if(!HandleEvent(MOVEUSE_EVENT_USE, User, Obj, NONE)){
+ throw NOTUSABLE;
+ }
+}
+
+void UseObjects(uint32 CreatureID, Object Obj1, Object Obj2){
+ if(!Obj1.exists()){
+ error("UseObjects: Übergebenes Objekt Obj1 existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Obj2.exists()){
+ error("UseObjects: Übergebenes Objekt Obj2 existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Obj1.getObjectType().getFlag(USEEVENT)){
+ error("UseObjects: Übergebenes Objekt Obj1 ist nicht benutzbar.\n");
+ throw ERROR;
+ }
+
+ Object User = NONE;
+ if(CreatureID != 0){
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature != NULL){
+ User = Creature->CrObject;
+ }
+ }
+
+ if(!HandleEvent(MOVEUSE_EVENT_MULTIUSE, User, Obj1, Obj2)){
+ throw NOTUSABLE;
+ }
+}
+
+void MovementEvent(Object Obj, Object Start, Object Dest){
+ if(!Obj.exists()){
+ error("MovementEvent: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType StartType = Start.getObjectType();
+ if(!Start.exists() || (!StartType.getFlag(CONTAINER) && !StartType.getFlag(CHEST))){
+ error("MovementEvent: \"Start\" ist kein Container.\n");
+ throw ERROR;
+ }
+
+ ObjectType DestType = Dest.getObjectType();
+ if(!Dest.exists() || (!DestType.getFlag(CONTAINER) && !DestType.getFlag(CHEST))){
+ error("MovementEvent: \"Dest\" ist kein Container.\n");
+ throw ERROR;
+ }
+
+ if(Obj.getObjectType().getFlag(MOVEMENTEVENT)){
+ HandleEvent(MOVEUSE_EVENT_MOVEMENT, NONE, Obj, NONE);
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }
+}
+
+void SeparationEvent(Object Obj, Object Start){
+ if(!Obj.exists()){
+ error("SeparationEvent: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Start.exists()){
+ error("SeparationEvent: Übergebener Container existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType StartType = Start.getObjectType();
+ if(!StartType.isMapContainer()){
+ return;
+ }
+
+ // NOTE(fusion): Separation event for `Obj` against objects at `Start`.
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.getFlag(SEPARATIONEVENT)){
+ Object Help = GetFirstContainerObject(Start);
+ while(Help != NONE){
+ Object Next = Help.getNextObject();
+ if(Help != Obj){
+ HandleEvent(MOVEUSE_EVENT_SEPARATION, NONE, Obj, Help);
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }
+ Help = Next;
+ }
+ }
+
+ // NOTE(fusion): Separation event for objects at `Start` against `Obj`.
+ Object Help = GetFirstContainerObject(Start);
+ while(Help != NONE){
+ Object Next = Help.getNextObject();
+ if(Help != Obj){
+ ObjectType HelpType = Help.getObjectType();
+ if(ObjType.isCreatureContainer()
+ && (HelpType.getFlag(LEVELDOOR) || HelpType.getFlag(QUESTDOOR))){
+ ObjectType DoorTarget = HelpType.getFlag(LEVELDOOR)
+ ? (int)HelpType.getAttribute(LEVELDOORTARGET)
+ : (int)HelpType.getAttribute(QUESTDOORTARGET);
+ if(DoorTarget.isMapContainer() || DoorTarget.getFlag(UNPASS)){
+ error("SeparationEvent: Zieltür für Tür %d nicht spezifiziert oder passierbar.\n",
+ HelpType.TypeID);
+ throw ERROR;
+ }
+
+ ClearField(Help, Obj);
+ Change(Help, DoorTarget, 0);
+ break;
+ }
+
+ if(HelpType.getFlag(SEPARATIONEVENT)){
+ HandleEvent(MOVEUSE_EVENT_SEPARATION, NONE, Help, Obj);
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }
+ }
+ Help = Next;
+ }
+}
+
+void CollisionEvent(Object Obj, Object Dest){
+ if(!Obj.exists()){
+ error("CollisionEvent: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Dest.exists()){
+ error("CollisionEvent: Übergebener Container existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType DestType = Dest.getObjectType();
+ if(!DestType.isMapContainer()){
+ return;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(ObjType.getFlag(TELEPORTABSOLUTE) || ObjType.getFlag(TELEPORTRELATIVE)){
+ int TeleportEffect;
+ int TeleportDestX, TeleportDestY, TeleportDestZ;
+ if(ObjType.getFlag(TELEPORTABSOLUTE)){
+ UnpackAbsoluteCoordinate((int)Obj.getAttribute(ABSTELEPORTDESTINATION),
+ &TeleportDestX, &TeleportDestY, &TeleportDestZ);
+ TeleportEffect = (int)ObjType.getAttribute(ABSTELEPORTEFFECT);
+ }else{
+ int DisplacementX, DisplacementY, DisplacementZ;
+ GetObjectCoordinates(Obj, &TeleportDestX, &TeleportDestY, &TeleportDestZ);
+ UnpackRelativeCoordinate((int)ObjType.getAttribute(RELTELEPORTDISPLACEMENT),
+ &DisplacementX, &DisplacementY, &DisplacementZ);
+ TeleportDestX += DisplacementX;
+ TeleportDestY += DisplacementY;
+ TeleportDestZ += DisplacementZ;
+ TeleportEffect = (int)ObjType.getAttribute(RELTELEPORTEFFECT);
+ }
+
+ Object TeleportDest = GetMapContainer(TeleportDestX, TeleportDestY, TeleportDestZ);
+ MoveAllObjects(Obj.getNextObject(), TeleportDest, NONE, true);
+ GraphicalEffect(TeleportDestX, TeleportDestY, TeleportDestZ, TeleportEffect);
+
+ // TODO(fusion): Is this even possible? Maybe `MoveAllObjects` could
+ // trigger a separation event that destroys `Obj`?
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }else{
+ // NOTE(fusion): Collision event for `Obj` against objects at `Dest`.
+ if(ObjType.getFlag(COLLISIONEVENT)){
+ Object Help = GetFirstContainerObject(Dest);
+ while(Help != NONE){
+ Object Next = Help.getNextObject();
+ if(Help != Obj){
+ HandleEvent(MOVEUSE_EVENT_COLLISION, NONE, Obj, Help);
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }
+ Help = Next;
+ }
+ }
+
+ // NOTE(fusion): Collision event for objects at `Dest` against `Obj`.
+ Object Help = GetFirstContainerObject(Dest);
+ while(Help != NONE){
+ Object Next = Help.getNextObject();
+ if(Help != Obj){
+ ObjectType HelpType = Help.getObjectType();
+ if(HelpType.getFlag(TELEPORTABSOLUTE) || HelpType.getFlag(TELEPORTRELATIVE)){
+ int TeleportEffect;
+ int TeleportDestX, TeleportDestY, TeleportDestZ;
+ if(HelpType.getFlag(TELEPORTABSOLUTE)){
+ UnpackAbsoluteCoordinate((int)Help.getAttribute(ABSTELEPORTDESTINATION),
+ &TeleportDestX, &TeleportDestY, &TeleportDestZ);
+ TeleportEffect = (int)HelpType.getAttribute(ABSTELEPORTEFFECT);
+ }else{
+ int DisplacementX, DisplacementY, DisplacementZ;
+ GetObjectCoordinates(Help, &TeleportDestX, &TeleportDestY, &TeleportDestZ);
+ UnpackRelativeCoordinate((int)HelpType.getAttribute(RELTELEPORTDISPLACEMENT),
+ &DisplacementX, &DisplacementY, &DisplacementZ);
+ TeleportDestX += DisplacementX;
+ TeleportDestY += DisplacementY;
+ TeleportDestZ += DisplacementZ;
+ TeleportEffect = (int)HelpType.getAttribute(RELTELEPORTEFFECT);
+ }
+
+ Object TeleportDest = GetMapContainer(TeleportDestX, TeleportDestY, TeleportDestZ);
+ MoveOneObject(Obj, TeleportDest);
+ GraphicalEffect(TeleportDestX, TeleportDestY, TeleportDestZ, TeleportEffect);
+ }else if(HelpType.getFlag(COLLISIONEVENT)){
+ HandleEvent(MOVEUSE_EVENT_COLLISION, NONE, Help, Obj);
+ }
+
+ if(!Obj.exists()){
+ throw DESTROYED;
+ }
+ }
+ Help = Next;
+ }
+ }
+}
+
+// Event Loading / Initialization
+// =============================================================================
+void LoadParameters(TReadScriptFile *Script, int *Parameters, int NumberOfParameters, ...){
+ if(NumberOfParameters > MOVEUSE_MAX_PARAMETERS){
+ error("LoadParameters: Zu viele Parameter (%d).\n", NumberOfParameters);
+ Script->error("too many parameters");
+ }
+
+ if(NumberOfParameters == 0){
+ return;
+ }
+
+ if(Script->Token != SPECIAL || Script->getSpecial() != '('){
+ Script->error("'(' expected");
+ }
+
+ va_list ap;
+ va_start(ap, NumberOfParameters);
+ for(int i = 0; i < NumberOfParameters; i += 1){
+ if(i > 0){
+ Script->readSymbol(',');
+ }
+
+ int ParamType = va_arg(ap, int);
+ switch(ParamType){
+ case MOVEUSE_PARAMETER_OBJECT:{
+ const char *Object = Script->readIdentifier();
+ if(strcmp(Object, "null") == 0){
+ Parameters[i] = 0; // MOVEUSE_OBJECT_NULL ?
+ }else if(strcmp(Object, "obj1") == 0){
+ Parameters[i] = 1; // MOVEUSE_OBJECT_1 ?
+ }else if(strcmp(Object, "obj2") == 0){
+ Parameters[i] = 2; // MOVEUSE_OBJECT_2 ?
+ }else if(strcmp(Object, "user") == 0){
+ Parameters[i] = 3; // MOVEUSE_OBJECT_USER ?
+ }else if(strcmp(Object, "temp") == 0){
+ Parameters[i] = 4; // MOVEUSE_OBJECT_TEMP ?
+ }else{
+ Script->error("Object expected");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_TYPE:{
+ int TypeID = Script->readNumber();
+ Parameters[i] = TypeID;
+ if(!ObjectTypeExists(TypeID)){
+ Script->error("Unknown object type");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_FLAG:{
+ int Flag = GetFlagByName(Script->readIdentifier());
+ Parameters[i] = Flag;
+ if(Flag == -1){
+ Script->error("Unknown flag");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_TYPEATTRIBUTE:{
+ int TypeAttribute = GetTypeAttributeByName(Script->readIdentifier());
+ Parameters[i] = TypeAttribute;
+ if(TypeAttribute == -1){
+ Script->error("Unknown type attribute");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_INSTANCEATTRIBUTE:{
+ int InstanceAttribute = GetInstanceAttributeByName(Script->readIdentifier());
+ Parameters[i] = InstanceAttribute;
+ if(InstanceAttribute == -1){
+ Script->error("Unknown instance attribute");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_COORDINATE:{
+ int AbsX, AbsY, AbsZ;
+ Script->readCoordinate(&AbsX, &AbsY, &AbsZ);
+ Parameters[i] = PackAbsoluteCoordinate(AbsX, AbsY, AbsZ);
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_VECTOR:{
+ int RelX, RelY, RelZ;
+ Script->readCoordinate(&RelX, &RelY, &RelZ);
+ Parameters[i] = PackRelativeCoordinate(RelX, RelY, RelZ);
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_RIGHT:{
+ const char *Right = Script->getIdentifier();
+ if(strcmp(Right, "premium_account") == 0){
+ Parameters[i] = PREMIUM_ACCOUNT;
+ }else if(strcmp(Right, "special_moveuse") == 0){
+ Parameters[i] = SPECIAL_MOVEUSE;
+ }else{
+ Script->error("Unknown right");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_SKILL:{
+ int SkillNr = GetSkillByName(Script->readIdentifier());
+ Parameters[i] = SkillNr;
+ if(SkillNr == -1){
+ Script->error("Unknown skill");
+ }
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_NUMBER:{
+ Parameters[i] = Script->readNumber();
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_TEXT:{
+ const char *Text = Script->readString();
+ Parameters[i] = AddDynamicString(Text);
+ break;
+ }
+
+ case MOVEUSE_PARAMETER_COMPARISON:{
+ int Operator = (int)Script->readSpecial();
+ Parameters[i] = Operator;
+ if(strchr("<=>GLN", Operator) == NULL){
+ Script->error("Unknown comparison operator");
+ }
+ break;
+ }
+ }
+ }
+ va_end(ap);
+
+ Script->readSymbol(')');
+ Script->nextToken();
+}
+
+void LoadCondition(TReadScriptFile *Script, TMoveUseCondition *Condition){
+ char Identifier[MAX_IDENT_LENGTH];
+ strcpy(Identifier, Script->getIdentifier());
+
+ Script->nextToken();
+ if(strcmp(Identifier, "isposition") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISPOSITION;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "istype") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISTYPE;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPE);
+ }else if(strcmp(Identifier, "iscreature") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISCREATURE;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "isplayer") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISPLAYER;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "hasflag") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASFLAG;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_FLAG);
+ }else if(strcmp(Identifier, "hastypeattribute") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASTYPEATTRIBUTE;
+ LoadParameters(Script, Condition->Parameters, 4,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPEATTRIBUTE,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "hasinstanceattribute") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASINSTANCEATTRIBUTE;
+ LoadParameters(Script, Condition->Parameters, 4,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_INSTANCEATTRIBUTE,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "hastext") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASTEXT;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TEXT);
+ }else if(strcmp(Identifier, "ispeaceful") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISPEACEFUL;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "maylogout") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_MAYLOGOUT;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "hasprofession") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASPROFESSION;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "haslevel") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASLEVEL;
+ LoadParameters(Script, Condition->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "hasright") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASRIGHT;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_RIGHT);
+ }else if(strcmp(Identifier, "hasquestvalue") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_HASQUESTVALUE;
+ LoadParameters(Script, Condition->Parameters, 4,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "testskill") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_TESTSKILL;
+ LoadParameters(Script, Condition->Parameters, 4,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_SKILL,
+ MOVEUSE_PARAMETER_NUMBER,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "countobjects") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_COUNTOBJECTS;
+ LoadParameters(Script, Condition->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "countobjectsonmap") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_COUNTOBJECTSONMAP;
+ LoadParameters(Script, Condition->Parameters, 3,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_COMPARISON,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "isobjectthere") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISOBJECTTHERE;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE);
+ }else if(strcmp(Identifier, "iscreaturethere") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISCREATURETHERE;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "isplayerthere") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISPLAYERTHERE;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "isobjectininventory") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISOBJECTININVENTORY;
+ LoadParameters(Script, Condition->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "isprotectionzone") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISPROTECTIONZONE;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "ishouse") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISHOUSE;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "ishouseowner") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISHOUSEOWNER;
+ LoadParameters(Script, Condition->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "isdressed") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_ISDRESSED;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "random") == 0){
+ Condition->Condition = MOVEUSE_CONDITION_RANDOM;
+ LoadParameters(Script, Condition->Parameters, 1,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else{
+ Script->error("invalid condition");
+ }
+}
+
+void LoadAction(TReadScriptFile *Script, TMoveUseAction *Action){
+ char Identifier[MAX_IDENT_LENGTH];
+ strcpy(Identifier, Script->getIdentifier());
+
+ Script->nextToken();
+ if(strcmp(Identifier, "createonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_CREATEONMAP;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "create") == 0){
+ Action->Action = MOVEUSE_ACTION_CREATE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "monsteronmap") == 0){
+ Action->Action = MOVEUSE_ACTION_MONSTERONMAP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "monster") == 0){
+ Action->Action = MOVEUSE_ACTION_MONSTER;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "effectonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_EFFECTONMAP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "effect") == 0){
+ Action->Action = MOVEUSE_ACTION_EFFECT;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "textonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_TEXTONMAP;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TEXT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "text") == 0){
+ Action->Action = MOVEUSE_ACTION_TEXT;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TEXT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "changeonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_CHANGEONMAP;
+ LoadParameters(Script, Action->Parameters, 4,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "change") == 0){
+ Action->Action = MOVEUSE_ACTION_CHANGE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "changerel") == 0){
+ Action->Action = MOVEUSE_ACTION_CHANGEREL;
+ LoadParameters(Script, Action->Parameters, 5,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_VECTOR,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "setattribute") == 0){
+ Action->Action = MOVEUSE_ACTION_SETATTRIBUTE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_INSTANCEATTRIBUTE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "changeattribute") == 0){
+ Action->Action = MOVEUSE_ACTION_CHANGEATTRIBUTE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_INSTANCEATTRIBUTE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "setquestvalue") == 0){
+ Action->Action = MOVEUSE_ACTION_SETQUESTVALUE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "damage") == 0){
+ Action->Action = MOVEUSE_ACTION_DAMAGE;
+ LoadParameters(Script, Action->Parameters, 4,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "setstart") == 0){
+ Action->Action = MOVEUSE_ACTION_SETSTART;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "writename") == 0){
+ Action->Action = MOVEUSE_ACTION_WRITENAME;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TEXT,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "writetext") == 0){
+ Action->Action = MOVEUSE_ACTION_WRITETEXT;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_TEXT,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "logout") == 0){
+ Action->Action = MOVEUSE_ACTION_LOGOUT;
+ LoadParameters(Script, Action->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "moveallonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVEALLONMAP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "moveall") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVEALL;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "moveallrel") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVEALLREL;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_VECTOR);
+ }else if(strcmp(Identifier, "movetoponmap") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVETOPONMAP;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "movetop") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVETOP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "movetoprel") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVETOPREL;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_VECTOR);
+ }else if(strcmp(Identifier, "move") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVE;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "moverel") == 0){
+ Action->Action = MOVEUSE_ACTION_MOVEREL;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_VECTOR);
+ }else if(strcmp(Identifier, "retrieve") == 0){
+ Action->Action = MOVEUSE_ACTION_RETRIEVE;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_VECTOR,
+ MOVEUSE_PARAMETER_VECTOR);
+ }else if(strcmp(Identifier, "deleteallonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_DELETEALLONMAP;
+ LoadParameters(Script, Action->Parameters, 1,
+ MOVEUSE_PARAMETER_COORDINATE);
+ }else if(strcmp(Identifier, "deletetoponmap") == 0){
+ Action->Action = MOVEUSE_ACTION_DELETETOPONMAP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE);
+ }else if(strcmp(Identifier, "deleteonmap") == 0){
+ Action->Action = MOVEUSE_ACTION_DELETEONMAP;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE);
+ }else if(strcmp(Identifier, "delete") == 0){
+ Action->Action = MOVEUSE_ACTION_DELETE;
+ LoadParameters(Script, Action->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "deleteininventory") == 0){
+ Action->Action = MOVEUSE_ACTION_DELETEININVENTORY;
+ LoadParameters(Script, Action->Parameters, 3,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "description") == 0){
+ Action->Action = MOVEUSE_ACTION_DESCRIPTION;
+ LoadParameters(Script, Action->Parameters, 2,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "loaddepot") == 0){
+ Action->Action = MOVEUSE_ACTION_LOADDEPOT;
+ LoadParameters(Script, Action->Parameters, 4,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "savedepot") == 0){
+ Action->Action = MOVEUSE_ACTION_SAVEDEPOT;
+ LoadParameters(Script, Action->Parameters, 4,
+ MOVEUSE_PARAMETER_COORDINATE,
+ MOVEUSE_PARAMETER_TYPE,
+ MOVEUSE_PARAMETER_OBJECT,
+ MOVEUSE_PARAMETER_NUMBER);
+ }else if(strcmp(Identifier, "sendmail") == 0){
+ Action->Action = MOVEUSE_ACTION_SENDMAIL;
+ LoadParameters(Script, Action->Parameters, 1,
+ MOVEUSE_PARAMETER_OBJECT);
+ }else if(strcmp(Identifier, "nop") == 0){
+ Action->Action = MOVEUSE_ACTION_NOP;
+ LoadParameters(Script, Action->Parameters, 0);
+ }else{
+ Script->error("invalid action");
+ }
+}
+
+void LoadDataBase(void){
+ print(1, "Lade Move/Use-Datenbank ...\n");
+
+ char FileName[4096];
+ snprintf(FileName, sizeof(FileName), "%s/moveuse.dat", DATAPATH);
+
+ for(int i = 0; i < NARRAY(MoveUseDatabases); i += 1){
+ MoveUseDatabases[i].NumberOfRules = 0;
+ }
+
+ TReadScriptFile Script;
+ Script.open(FileName);
+ Script.nextToken();
+ while(Script.Token != ENDOFFILE){
+ const char *Identifier = Script.getIdentifier();
+
+ if(strcmp(Identifier, "begin") == 0){
+ Script.readString();
+ continue;
+ }else if(strcmp(Identifier, "end") == 0){
+ continue;
+ }
+
+ int EventType;
+ if(strcmp(Identifier, "use") == 0){
+ EventType = MOVEUSE_EVENT_MULTIUSE;
+ }else if(strcmp(Identifier, "multiuse") == 0){
+ EventType = MOVEUSE_EVENT_MULTIUSE;
+ }else if(strcmp(Identifier, "movement") == 0){
+ EventType = MOVEUSE_EVENT_MULTIUSE;
+ }else if(strcmp(Identifier, "collision") == 0){
+ EventType = MOVEUSE_EVENT_MULTIUSE;
+ }else if(strcmp(Identifier, "separation") == 0){
+ EventType = MOVEUSE_EVENT_MULTIUSE;
+ }else{
+ Script.error("Unknown event type");
+ }
+
+ Script.readSymbol(',');
+
+ MoveUseDatabases[EventType].NumberOfRules += 1;
+ TMoveUseRule *Rule = MoveUseDatabases[EventType].Rules.at(
+ MoveUseDatabases[EventType].NumberOfRules);
+
+ // NOTE(fusion): Conditions.
+ Rule->FirstCondition = NumberOfMoveUseConditions + 1;
+ while(true){
+ Script.nextToken();
+ NumberOfMoveUseConditions += 1;
+ TMoveUseCondition *Condition = MoveUseConditions.at(NumberOfMoveUseConditions);
+ Condition->Modifier = MOVEUSE_MODIFIER_NORMAL;
+ if(Script.Token == SPECIAL){
+ if(Script.getSpecial() == '!'){
+ Script.nextToken();
+ Condition->Modifier = MOVEUSE_MODIFIER_INVERT;
+ }else if(Script.getSpecial() == '~'){
+ Script.nextToken();
+ Condition->Modifier = MOVEUSE_MODIFIER_TRUE;
+ }
+ }
+ LoadCondition(&Script, Condition);
+
+ if(Script.Token != SPECIAL || Script.getSpecial() != ','){
+ break;
+ }
+ }
+ Rule->LastCondition = NumberOfMoveUseConditions;
+
+ if(Script.Token != SPECIAL || Script.getSpecial() != 'I'){
+ Script.error("'->' expected");
+ }
+
+ // NOTE(fusion): Actions.
+ Rule->FirstAction = NumberOfMoveUseActions + 1;
+ while(true){
+ Script.nextToken();
+ NumberOfMoveUseActions += 1;
+ TMoveUseAction *Action = MoveUseActions.at(NumberOfMoveUseActions);
+ LoadAction(&Script, Action);
+
+ if(Script.Token != SPECIAL || Script.getSpecial() != ','){
+ break;
+ }
+ }
+ Rule->LastAction = NumberOfMoveUseActions;
+
+ // NOTE(fusion): Optional tag?
+ if(Script.Token == STRING){
+ Script.nextToken();
+ }
+ }
+
+ Script.close();
+
+ print(1, "Move/Use-Datenbank mit %d/%d/%d/%d/%d Regeln gelesen.\n",
+ MoveUseDatabases[MOVEUSE_EVENT_USE].NumberOfRules,
+ MoveUseDatabases[MOVEUSE_EVENT_MULTIUSE].NumberOfRules,
+ MoveUseDatabases[MOVEUSE_EVENT_MOVEMENT].NumberOfRules,
+ MoveUseDatabases[MOVEUSE_EVENT_COLLISION].NumberOfRules,
+ MoveUseDatabases[MOVEUSE_EVENT_SEPARATION].NumberOfRules);
+}
+
+void InitMoveUse(void){
+ NumberOfMoveUseConditions = 0;
+ NumberOfMoveUseActions = 0;
+ DelayedMails = 0;
+ LoadDataBase();
+}
+
+void ExitMoveUse(void){
+ for(int i = 0; i < DelayedMails; i += 1){
+ error("ExitMoveUse: Paket an %u wurde nicht zugestellt.\n",
+ DelayedMail.at(i)->CharacterID);
+ }
+}