aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-05 02:00:10 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-05 02:00:10 -0300
commit3c8bcba6b540f5cb84572994d9ec2426dd348a93 (patch)
tree54af53f02d549951446d5a87845b42d4ee777559 /src
parent2c91caa1dd4ea53dfeb63751d04170d9a9a61474 (diff)
downloadgame-3c8bcba6b540f5cb84572994d9ec2426dd348a93.tar.gz
game-3c8bcba6b540f5cb84572994d9ec2426dd348a93.zip
finish `magic.cc`
Diffstat (limited to 'src')
-rw-r--r--src/crcombat.cc15
-rw-r--r--src/creature.cc4
-rw-r--r--src/enums.hh17
-rw-r--r--src/magic.cc1235
-rw-r--r--src/magic.hh126
-rw-r--r--src/main.cc4
-rw-r--r--src/stubs.hh6
7 files changed, 1306 insertions, 101 deletions
diff --git a/src/crcombat.cc b/src/crcombat.cc
index 2843cd3..e1bf72f 100644
--- a/src/crcombat.cc
+++ b/src/crcombat.cc
@@ -715,14 +715,13 @@ void TCombat::WandAttack(TCreature *Target){
int AttackStrength = WandType.getAttribute(WANDATTACKSTRENGTH);
int AttackVariation = (int)WandType.getAttribute(WANDATTACKVARIATION);
- // NOTE(fusion): Oof...
try{
CheckMana(Master, ManaConsumption, 0, 0);
- }catch(RESULT err){
- if(err == NOTENOUGHMANA){
+ }catch(RESULT r){
+ if(r == NOTENOUGHMANA){
throw OUTOFAMMO;
}else{
- throw err;
+ throw r;
}
}
@@ -836,7 +835,7 @@ void TCombat::DistanceAttack(TCreature *Target){
int Damage = ComputeDamage(Master, 0, EffectStrength, EffectStrength);
TDamageImpact Impact(Master, DAMAGE_PHYSICAL, Damage, false);
CircleShapeSpell(Master, DropX, DropY, DropZ, -1,
- ANIMATION_NONE, 2, &Impact, EFFECT_BURST_ARROW);
+ ANIMATION_NONE, 2, &Impact, EFFECT_FIRE);
}
try{
@@ -845,10 +844,10 @@ void TCombat::DistanceAttack(TCreature *Target){
}else{
Move(0, this->Ammo, DropCon, 1, false, NONE);
}
- }catch(RESULT err){
- if(err != DESTROYED){
+ }catch(RESULT r){
+ if(r != DESTROYED){
error("TCombat::RangeAttack: Konnte Ammo nicht verschieben/löschen"
- " (Exception %d, [%d,%d,%d].\n", err, DropX, DropY, DropZ);
+ " (Exception %d, [%d,%d,%d].\n", r, DropX, DropY, DropZ);
}
}
diff --git a/src/creature.cc b/src/creature.cc
index d1cb7bf..9eadc6e 100644
--- a/src/creature.cc
+++ b/src/creature.cc
@@ -165,9 +165,9 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){
ObjectType WearOutType = ObjType.getAttribute(WEAROUTTARGET);
Change(Obj, WearOutType, 0);
}
- }catch(RESULT err){
+ }catch(RESULT r){
error("TCreature::Damage: Exception %d beim Abnutzen von Objekt %d.\n",
- err, ObjType.TypeID);
+ r, ObjType.TypeID);
}
}
}else if(ObjType.getFlag(PROTECTION) && !ObjType.getFlag(CLOTHES)){
diff --git a/src/enums.hh b/src/enums.hh
index 7b2fc68..21194dd 100644
--- a/src/enums.hh
+++ b/src/enums.hh
@@ -18,6 +18,7 @@ enum AnimationType: int {
ANIMATION_THROWING_STAR = 8,
ANIMATION_THROWING_KNIFE = 9,
ANIMATION_SMALL_STONE = 10,
+ ANIMATION_DEATH = 11,
ANIMATION_SNOWBALL = 13,
ANIMATION_POWER_BOLT = 14,
ANIMATION_POISON = 15,
@@ -81,10 +82,9 @@ enum DamageType: int {
DAMAGE_POISON = 0x0002,
DAMAGE_FIRE = 0x0004,
DAMAGE_ENERGY = 0x0008,
- //DAMAGE_DEATH ? = 0x0010
- DAMAGE_POISON_PERIODIC = 0x0020,
- DAMAGE_FIRE_PERIODIC = 0x0040,
- DAMAGE_ENERGY_PERIODIC = 0x0080,
+ DAMAGE_POISON_PERIODIC = 0x0020, // Better name?
+ DAMAGE_FIRE_PERIODIC = 0x0040, // Better name?
+ DAMAGE_ENERGY_PERIODIC = 0x0080, // Better name?
DAMAGE_LIFEDRAIN = 0x0100,
DAMAGE_MANADRAIN = 0x0200,
};
@@ -109,16 +109,19 @@ enum EffectType: int {
EFFECT_MANA_HIT = 2,
EFFECT_POFF = 3,
EFFECT_BLOCK_HIT = 4,
- EFFECT_BURST_ARROW = 7,
+ EFFECT_FIRE_EXPLOSION = 5,
+ EFFECT_FIRE_BURST = 6, // Not sure about this one.
+ EFFECT_FIRE = 7,
EFFECT_POISON = 9,
EFFECT_BONE_HIT = 10,
- EFFECT_TELEPORT = 11,
+ EFFECT_ENERGY = 11,
EFFECT_ENERGY_HIT = 12,
EFFECT_MAGIC_BLUE = 13,
EFFECT_MAGIC_RED = 14,
EFFECT_MAGIC_GREEN = 15,
EFFECT_FIRE_HIT = 16,
EFFECT_POISON_HIT = 17,
+ EFFECT_DEATH = 18,
};
enum FLAG: int {
@@ -232,6 +235,8 @@ enum LiquidType: int {
LIQUID_MUD = 4,
LIQUID_BLOOD = 5,
LIQUID_SLIME = 6,
+ LIQUID_MANA = 10,
+ LIQUID_LIFE = 11,
LIQUID_LEMONADE = 12,
};
diff --git a/src/magic.cc b/src/magic.cc
index 3db1456..b3d70b1 100644
--- a/src/magic.cc
+++ b/src/magic.cc
@@ -7,6 +7,26 @@
#include "stubs.hh"
#include <fstream>
+#include <sstream>
+
+struct TCircle {
+ int x[32];
+ int y[32];
+ int Count;
+};
+
+struct TSpellList {
+ uint8 Syllable[10];
+ uint8 RuneGr;
+ uint8 RuneNr;
+ const char *Comment;
+ uint16 Level;
+ uint16 RuneLevel;
+ uint16 Flags;
+ int Mana;
+ int SoulPoints;
+ int Amount;
+};
static TSpellList SpellList[256];
static TCircle Circle[10];
@@ -593,44 +613,6 @@ void AngleShapeSpell(TCreature *Actor, int Angle, int Range, TImpact *Impact, in
}
}
-// Spell Casting
-// =============================================================================
-int GetDirection(int dx, int dy){
- // TODO(fusion): This function originally returned directions different from
- // the ones used by creatures. I've converted it to use the same values which
- // are also defined in `enums.hh` for simplicity.
- int Result;
- if(dx == 0){
- if(dy < 0){
- Result = DIRECTION_NORTH;
- }else if(dy > 0){
- Result = DIRECTION_SOUTH;
- }else{
- Result = DIRECTION_INVALID;
- }
- }else{
- // NOTE(fusion): This function uses the approximate tangent value, avoiding
- // floating point calculations, for whatever reason. The tangent is unique
- // and odd in the interval (-PI/2, +PI/2). We also need to recall that the
- // Y-axis is inverted in Tibia, so we need to negate `dy`.
- constexpr int Tangent_67_5 = 618; // => 618 / 256 ~ 2.41 ~ tan(67.5 deg)
- constexpr int Tangent_22_5 = 106; // => 106 / 256 ~ 0.41 ~ tan(22.5 deg)
- int Tangent = (-dy * 256) / dx; // => (dy * 256) / dx ~ (dy / dx) * 256
- if(Tangent >= Tangent_67_5){
- Result = DIRECTION_NORTH;
- }else if(Tangent >= Tangent_22_5){
- Result = (dx < 0) ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST;
- }else if(Tangent >= -Tangent_22_5){
- Result = (dx < 0) ? DIRECTION_WEST : DIRECTION_EAST;
- }else if(Tangent >= -Tangent_67_5){
- Result = (dx < 0) ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST;
- }else{
- Result = DIRECTION_SOUTH;
- }
- }
- return Result;
-}
-
void CheckSpellbook(TCreature *Actor, int SpellNr){
if(Actor == NULL){
error("CheckSpellbook: Übergebene Kreatur existiert nicht.\n");
@@ -908,6 +890,42 @@ void Combat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints,
INT_MAX, Animation, 0, &Impact, Effect);
}
+int GetDirection(int dx, int dy){
+ // TODO(fusion): This function originally returned directions different from
+ // the ones used by creatures. I've converted it to use the same values which
+ // are also defined in `enums.hh` for simplicity.
+ int Result;
+ if(dx == 0){
+ if(dy < 0){
+ Result = DIRECTION_NORTH;
+ }else if(dy > 0){
+ Result = DIRECTION_SOUTH;
+ }else{
+ Result = DIRECTION_INVALID;
+ }
+ }else{
+ // NOTE(fusion): This function uses the approximate tangent value, avoiding
+ // floating point calculations, for whatever reason. The tangent is unique
+ // and odd in the interval (-PI/2, +PI/2). We also need to recall that the
+ // Y-axis is inverted in Tibia, so we need to negate `dy`.
+ constexpr int Tangent_67_5 = 618; // => 618 / 256 ~ 2.41 ~ tan(67.5 deg)
+ constexpr int Tangent_22_5 = 106; // => 106 / 256 ~ 0.41 ~ tan(22.5 deg)
+ int Tangent = (-dy * 256) / dx; // => (dy * 256) / dx ~ (dy / dx) * 256
+ if(Tangent >= Tangent_67_5){
+ Result = DIRECTION_NORTH;
+ }else if(Tangent >= Tangent_22_5){
+ Result = (dx < 0) ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST;
+ }else if(Tangent >= -Tangent_22_5){
+ Result = (dx < 0) ? DIRECTION_WEST : DIRECTION_EAST;
+ }else if(Tangent >= -Tangent_67_5){
+ Result = (dx < 0) ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST;
+ }else{
+ Result = DIRECTION_SOUTH;
+ }
+ }
+ return Result;
+}
+
// Spell Functions
// =============================================================================
void KillAllMonsters(TCreature *Actor, int Effect, int Radius){
@@ -966,6 +984,72 @@ void KillAllMonsters(TCreature *Actor, int Effect, int Radius){
}
}
+void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful){
+ if(!FieldPossible(x, y, z, FieldType)){
+ return;
+ }
+
+ SPECIALMEANING Meaning;
+ switch(FieldType){
+ case FIELD_TYPE_FIRE:{
+ if(Peaceful){
+ Meaning = MAGICFIELD_FIRE_HARMLESS;
+ }else{
+ Meaning = MAGICFIELD_FIRE_DANGEROUS;
+ }
+ break;
+ }
+
+ case FIELD_TYPE_POISON:{
+ if(Peaceful){
+ Meaning = MAGICFIELD_POISON_HARMLESS;
+ }else{
+ Meaning = MAGICFIELD_POISON_DANGEROUS;
+ }
+ break;
+ }
+
+ case FIELD_TYPE_ENERGY:{
+ if(Peaceful){
+ Meaning = MAGICFIELD_ENERGY_HARMLESS;
+ }else{
+ Meaning = MAGICFIELD_ENERGY_DANGEROUS;
+ }
+ break;
+ }
+
+ case FIELD_TYPE_MAGICWALL:{
+ Meaning = MAGICFIELD_MAGICWALL;
+ break;
+ }
+
+ case FIELD_TYPE_WILDGROWTH:{
+ Meaning = MAGICFIELD_RUSHWOOD;
+ break;
+ }
+
+ default:{
+ error("CreateField: Ungültiger Feldtyp %d.\n", FieldType);
+ throw ERROR;
+ }
+ }
+
+ // NOTE(fusion): Delete other magic fields?
+ Object Obj = GetFirstObject(x, y, z);
+ while(Obj != NONE){
+ Object Next = Obj.getNextObject();
+ if(Obj.getObjectType().getFlag(MAGICFIELD)){
+ Delete(Obj, -1);
+ }
+ Obj = Next;
+ }
+
+ // NOTE(fusion): Create field, at last.
+ Create(GetMapContainer(x, y, z),
+ GetSpecialObject(Meaning),
+ Owner);
+}
+
void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int FieldType){
if(Actor == NULL){
error("CreateField: Ungültige Kreatur übergeben.\n");
@@ -993,7 +1077,7 @@ void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints
CheckMana(Actor, ManaPoints, SoulPoints, Delay);
int Animation = ANIMATION_ENERGY;
- if(FieldType == 1){
+ if(FieldType == FIELD_TYPE_FIRE){
Animation = ANIMATION_FIRE;
}
Missile(Actor->CrObject, Target, Animation);
@@ -1001,8 +1085,9 @@ void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints
bool Peaceful = (WorldType == NON_PVP && Actor->IsPeaceful());
CreateField(TargetX, TargetY, TargetZ, FieldType, Actor->ID, Peaceful);
- // TODO(fusion): Probably damaging fields?
- if(FieldType == 1 || FieldType == 2 || FieldType == 3){
+ if(FieldType == FIELD_TYPE_FIRE
+ || FieldType == FIELD_TYPE_POISON
+ || FieldType == FIELD_TYPE_ENERGY){
Actor->BlockLogout(60, true);
}
}
@@ -1058,7 +1143,7 @@ void MassCreateField(TCreature *Actor, Object Target,
if(Actor->posx != TargetX || Actor->posy != TargetY || Actor->posz != TargetZ){
int Animation = ANIMATION_ENERGY;
- if(FieldType == 1){
+ if(FieldType == FIELD_TYPE_FIRE){
Animation = ANIMATION_FIRE;
}
Missile(Actor->CrObject, Target, Animation);
@@ -1089,7 +1174,9 @@ void MassCreateField(TCreature *Actor, Object Target,
}
}
- if(FieldType == 1 || FieldType == 2 || FieldType == 3){
+ if(FieldType == FIELD_TYPE_FIRE
+ || FieldType == FIELD_TYPE_POISON
+ || FieldType == FIELD_TYPE_ENERGY){
Actor->BlockLogout(60, true);
}
}
@@ -1121,7 +1208,7 @@ void CreateFieldWall(TCreature *Actor, Object Target,
if(ActorX != TargetX || ActorY != TargetY || ActorZ != TargetZ){
int Animation = ANIMATION_ENERGY;
- if(FieldType == 1){
+ if(FieldType == FIELD_TYPE_FIRE){
Animation = ANIMATION_FIRE;
}
Missile(Actor->CrObject, Target, Animation);
@@ -1216,7 +1303,9 @@ void CreateFieldWall(TCreature *Actor, Object Target,
}
}
- if(FieldType == 1 || FieldType == 2 || FieldType == 3){
+ if(FieldType == FIELD_TYPE_FIRE
+ || FieldType == FIELD_TYPE_POISON
+ || FieldType == FIELD_TYPE_ENERGY){
Actor->BlockLogout(60, true);
}
}
@@ -1321,7 +1410,7 @@ void CleanupField(TCreature *Actor){
CleanupField(Actor, Target, 0, 0);
}
-void Teleport(TCreature *Actor, char *Param){
+void Teleport(TCreature *Actor, const char *Param){
if(Actor == NULL){
error("Teleport: Ungültige Kreatur übergeben.\n");
throw ERROR;
@@ -1414,7 +1503,7 @@ void Teleport(TCreature *Actor, char *Param){
}
Object Dest = GetMapContainer(DestX, DestY, DestZ);
Move(0, Actor->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT);
+ GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY);
}else if(MDGoStrength != Actor->Skills[SKILL_GO_STRENGTH]->MDAct){
Actor->Skills[SKILL_GO_STRENGTH]->SetMDAct(MDGoStrength);
AnnounceChangedCreature(Actor->ID, 4); // CREATURE_GO_STRENGTH_CHANGED ?
@@ -1455,7 +1544,7 @@ void TeleportToCreature(TCreature *Actor, const char *Name){
}
if(Actor == Player){
- GraphicalEffect(Actor->CrObject, EFFECT_TELEPORT);
+ GraphicalEffect(Actor->CrObject, EFFECT_ENERGY);
return;
}
@@ -1475,7 +1564,7 @@ void TeleportToCreature(TCreature *Actor, const char *Name){
Object Dest = GetMapContainer(DestX, DestY, DestZ);
Move(0, Actor->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT);
+ GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY);
Log("banish", "%s teleportiert sich zu %s.\n", Actor->Name, Player->Name);
}
@@ -1523,7 +1612,7 @@ void TeleportPlayerToMe(TCreature *Actor, const char *Name){
Object Dest = GetMapContainer(DestX, DestY, DestZ);
Move(0, Player->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(DestX, DestY, DestZ, EFFECT_TELEPORT);
+ GraphicalEffect(DestX, DestY, DestZ, EFFECT_ENERGY);
}
void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints){
@@ -1543,7 +1632,7 @@ void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints){
Object Dest = GetMapContainer(OrigX, OrigY + 1, OrigZ - 1);
Move(0, Actor->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_TELEPORT);
+ GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_ENERGY);
}
void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char *Param){
@@ -1594,7 +1683,7 @@ void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char
Object Dest = GetMapContainer(DestX, DestY, DestZ);
Move(0, Actor->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_TELEPORT);
+ GraphicalEffect(OrigX, OrigY, OrigZ, EFFECT_ENERGY);
}
void MagicClimbing(TCreature *Actor, const char *Param){
@@ -1825,7 +1914,7 @@ void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God){
GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_BLUE);
}
-void SummonCreature(TCreature *Actor, int Mana, const char *RaceName, bool God){
+void SummonCreature(TCreature *Actor, int ManaPoints, const char *RaceName, bool God){
if(Actor == NULL){
error("SummonCreature: Ungültige Kreatur übergeben.\n");
throw ERROR;
@@ -1841,7 +1930,7 @@ void SummonCreature(TCreature *Actor, int Mana, const char *RaceName, bool God){
throw CREATURENOTEXISTING;
}
- SummonCreature(Actor, Mana, Race, God);
+ SummonCreature(Actor, ManaPoints, Race, God);
}
void StartMonsterraid(TCreature *Actor, const char *RaidName){
@@ -2025,7 +2114,6 @@ void Heal(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount){
}
}
-
void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Amount, int Radius){
if(Actor == NULL){
error("MassHeal: Ungültige Kreatur übergeben.\n");
@@ -2098,7 +2186,7 @@ void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, i
}
}
-void HealFriend(TCreature *Actor, char *TargetName, int ManaPoints, int SoulPoints, int Amount){
+void HealFriend(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints, int Amount){
if(Actor == NULL){
error("HealFriend: Ungültige Kreatur übergeben.\n");
throw ERROR;
@@ -2352,7 +2440,7 @@ void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int Sou
}
}
-void CreatureIllusion(TCreature *Actor, const char *RaceName, int ManaPoints, int SoulPoints, int Duration){
+void CreatureIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, const char *RaceName, int Duration){
if(Actor == NULL){
error("CreatureIllusion: Ungültige Kreatur übergeben.\n");
throw ERROR;
@@ -2381,7 +2469,7 @@ void CreatureIllusion(TCreature *Actor, const char *RaceName, int ManaPoints, in
GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_BLUE);
}
-void ObjectIllusion(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Duration){
+void ObjectIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, Object Target, int Duration){
if(Actor == NULL){
error("ObjectIllusion: Ungültige Kreatur übergeben.\n");
throw ERROR;
@@ -2570,7 +2658,7 @@ void Challenge(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius){
}
}
-void FindPerson(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints){
+void FindPerson(TCreature *Actor, int ManaPoints, int SoulPoints, const char *TargetName){
// TODO(fusion): And we're back.
if(Actor == NULL){
error("FindPerson: Ungültige Kreatur übergeben.\n");
@@ -3220,13 +3308,1040 @@ void HomeTeleport(TCreature *Actor, const char *Name){
Object Dest = GetMapContainer(Player->startx, Player->starty, Player->startz);
GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_POFF);
Move(0, Player->CrObject, Dest, -1, false, NONE);
- GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_TELEPORT);
+ GraphicalEffect(Player->posx, Player->posy, Player->posz, EFFECT_ENERGY);
SendMessage(Actor->Connection, TALK_INFO_MESSAGE,
"Player %s has been moved to the temple.", Player->Name);
Log("banish", "%s teleportiert %s zum Tempel.\n", Actor->Name, Player->Name);
}
+// Spell Casting
+// =============================================================================
+static void SpellFailed(uint32 CreatureID){
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("SpellFailed: Kreatur existiert nicht.\n");
+ return;
+ }
+
+ GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF);
+}
+
+static void CharacterRightSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("CharacterRightSpell: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(Actor->Type != PLAYER){
+ return;
+ }
+
+ switch(SpellNr) {
+ case 34: CreateThing(Actor, SpellStr[2], SpellStr[3]); break;
+ case 35: CreateThing(Actor, SpellStr[2], NULL); break;
+ case 37: Teleport(Actor, SpellStr[2]); break;
+ case 40: CreateKnowledge(Actor, SpellStr[3], NULL); break;
+ case 41: ChangeData(Actor, SpellStr[2]); break;
+ case 46: CreateKnowledge(Actor, SpellStr[3], SpellStr[4]); break;
+ case 47: TeleportToCreature(Actor, SpellStr[3]); break;
+ case 52: TeleportPlayerToMe(Actor, SpellStr[3]); break;
+ case 53: SummonCreature(Actor, 0, SpellStr[3], true); break;
+ case 58: GetPosition(Actor); break;
+ case 63: CreateMoney(Actor, SpellStr[3]); break;
+ case 64: ChangeProfession(Actor, SpellStr[3]); break;
+ case 71: EditGuests(Actor); break;
+ case 72: EditSubowners(Actor); break;
+ case 73: KickGuest(Actor, SpellStr[3]); break;
+ case 74: EditNameDoor(Actor); break;
+ case 96: GetQuestValue(Actor, SpellStr[3]); break;
+ case 97: SetQuestValue(Actor, SpellStr[3], SpellStr[4]); break;
+ case 98: CleanupField(Actor); break;
+ case 99: MagicClimbing(Actor, SpellStr[3]); break;
+ case 100: ClearQuestValues(Actor); break;
+ case 101: KillAllMonsters(Actor, EFFECT_DEATH, 2); break;
+ case 102: StartMonsterraid(Actor, SpellStr[4]); break;
+ }
+}
+
+static void AccountRightSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("AccountRightSpell: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(Actor->Type != PLAYER){
+ return;
+ }
+
+ switch(SpellNr) {
+ case 57: BanishAccount(Actor, SpellStr[3], atoi(SpellStr[4]), SpellStr[5]); break;
+ case 60: HomeTeleport(Actor, SpellStr[2]); break;
+ case 61: DeleteAccount(Actor, SpellStr[4], SpellStr[5]); break;
+ case 62: SetNameRule(Actor, SpellStr[2]); break;
+ case 65: Notation(Actor, SpellStr[2], SpellStr[3]); break;
+ case 66: NameLock(Actor, SpellStr[3]); break;
+ case 67: KickPlayer(Actor, SpellStr[2]); break;
+ case 68: DeleteCharacter(Actor, SpellStr[4], SpellStr[5]); break;
+ case 69: IPBanishment(Actor, SpellStr[3], SpellStr[4]); break;
+ case 70: BanishCharacter(Actor, SpellStr[3], atoi(SpellStr[4]), SpellStr[5]); break;
+ }
+}
+
+static void CastSpell(uint32 CreatureID, int SpellNr, const char (*SpellStr)[512]){
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("CastSpell: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ CheckSpellbook(Actor, SpellNr);
+ CheckAccount(Actor, SpellNr);
+ CheckLevel(Actor, SpellNr);
+ CheckRing(Actor, SpellNr);
+
+ if(Actor->EarliestSpellTime > ServerMilliseconds){
+ throw EXHAUSTED;
+ }
+
+ if(IsAggressiveSpell(SpellNr) && Actor->Type == PLAYER
+ && !CheckRight(Actor->ID, ATTACK_EVERYWHERE)
+ && IsProtectionZone(Actor->posx, Actor->posy, Actor->posz)){
+ throw PROTECTIONZONE;
+ }
+
+ int ManaPoints = SpellList[SpellNr].Mana;
+ int SoulPoints = SpellList[SpellNr].SoulPoints;
+ switch(SpellNr) {
+ case 1:{
+ int Amount = ComputeDamage(Actor, SpellNr, 20, 10);
+ Heal(Actor, ManaPoints, SoulPoints, Amount);
+ break;
+ }
+
+ case 2:{
+ int Amount = ComputeDamage(Actor, SpellNr, 40, 20);
+ Heal(Actor, ManaPoints, SoulPoints, Amount);
+ break;
+ }
+
+ case 3:{
+ int Amount = ComputeDamage(Actor, SpellNr, 250, 50);
+ Heal(Actor, ManaPoints, SoulPoints, Amount);
+ break;
+ }
+
+ case 6:{
+ MagicGoStrength(Actor, Actor, ManaPoints, SoulPoints, 30, 3);
+ break;
+ }
+
+ case 9:{
+ SummonCreature(Actor, ManaPoints, SpellStr[3], false);
+ break;
+ }
+
+ case 10:{
+ Enlight(Actor, ManaPoints, SoulPoints, 6, 500);
+ break;
+ }
+
+ case 11:{
+ Enlight(Actor, ManaPoints, SoulPoints, 8, 1000);
+ break;
+ }
+
+ case 13:{
+ int Damage = ComputeDamage(Actor, SpellNr, 150, 50);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_ENERGY, 5, 30, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 19:{
+ int Damage = ComputeDamage(Actor, SpellNr, 30, 10);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_FIRE, 4, 45, DAMAGE_FIRE);
+ break;
+ }
+
+ case 20:{
+ FindPerson(Actor, ManaPoints, SoulPoints, SpellStr[2]);
+ break;
+ }
+
+ case 22:{
+ int Damage = ComputeDamage(Actor, SpellNr, 60, 20);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_FIRE_BURST, 5, 0, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 23:{
+ int Damage = ComputeDamage(Actor, SpellNr, 120, 80);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_FIRE_BURST, 8, 0, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 24:{
+ int Damage = ComputeDamage(Actor, SpellNr, 250, 50);
+ MassCombat(Actor, Actor->CrObject, ManaPoints, SoulPoints, Damage,
+ EFFECT_FIRE_EXPLOSION, 6, DAMAGE_PHYSICAL, ANIMATION_FIRE);
+ break;
+ }
+
+ case 29:{
+ NegatePoison(Actor, Actor, ManaPoints, SoulPoints);
+ break;
+ }
+
+ case 38:{
+ CreatureIllusion(Actor, ManaPoints, SoulPoints, SpellStr[4], 200);
+ break;
+ }
+
+ case 39:{
+ MagicGoStrength(Actor, Actor, ManaPoints, SoulPoints, 70, 2);
+ break;
+ }
+
+ case 42:{
+ CreateFood(Actor, ManaPoints, SoulPoints);
+ break;
+ }
+
+ case 44:{
+ Shielding(Actor, ManaPoints, SoulPoints, 200);
+ break;
+ }
+
+ case 45:{
+ Invisibility(Actor, ManaPoints, SoulPoints, 200);
+ break;
+ }
+
+ case 48:{
+ CreateArrows(Actor, ManaPoints, SoulPoints, 1, 5);
+ break;
+ }
+
+ case 49:{
+ CreateArrows(Actor, ManaPoints, SoulPoints, 2, 3);
+ break;
+ }
+
+ case 51:{
+ CreateArrows(Actor, ManaPoints, SoulPoints, 0, 10);
+ break;
+ }
+
+ case 56:{
+ int Damage = ComputeDamage(Actor, SpellNr, 200, 50);
+ MassCombat(Actor, Actor->CrObject, ManaPoints, SoulPoints, Damage,
+ EFFECT_POISON, 8, DAMAGE_POISON_PERIODIC, ANIMATION_NONE);
+ break;
+ }
+
+ case 75:{
+ Enlight(Actor, ManaPoints, SoulPoints, 9, 2000);
+ break;
+ }
+
+ case 76:{
+ MagicRope(Actor, ManaPoints, SoulPoints);
+ break;
+ }
+
+ case 79:{
+ CreateArrows(Actor, ManaPoints, SoulPoints, 3, 5);
+ break;
+ }
+
+ case 80:{
+ int Level = Actor->Skills[SKILL_LEVEL]->Get();
+ int Damage = (Level * ComputeDamage(Actor, SpellNr, 80, 20)) / 25;
+ MassCombat(Actor, Actor->CrObject, Level * 4, 0, Damage,
+ EFFECT_BONE_HIT, 2, DAMAGE_PHYSICAL, ANIMATION_NONE);
+ break;
+ }
+
+ case 81:{
+ MagicClimbing(Actor, ManaPoints, SoulPoints, SpellStr[3]);
+ break;
+ }
+
+ case 82:{
+ int Amount = ComputeDamage(Actor, SpellNr, 200, 40);
+ MassHeal(Actor, Actor->CrObject, ManaPoints, SoulPoints, Amount, 4);
+ break;
+ }
+
+ case 84:{
+ int Amount = ComputeDamage(Actor,SpellNr,120,40);
+ HealFriend(Actor, SpellStr[3], ManaPoints, SoulPoints, Amount);
+ break;
+ }
+
+ case 85:{
+ MassRaiseDead(Actor, Actor->CrObject, ManaPoints, SoulPoints, 4);
+ break;
+ }
+
+ case 87:{
+ int Damage = ComputeDamage(Actor, SpellNr, 45, 10);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_DEATH, 1, 0, DAMAGE_PHYSICAL);
+ break;
+ }
+
+ case 88:{
+ int Damage = ComputeDamage(Actor, SpellNr, 45, 10);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_ENERGY, 1, 0, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 89:{
+ int Damage = ComputeDamage(Actor, SpellNr, 45, 10);
+ AngleCombat(Actor, ManaPoints, SoulPoints, Damage,
+ EFFECT_FIRE, 1, 0, DAMAGE_FIRE);
+ break;
+ }
+
+ case 90:{
+ CancelInvisibility(Actor, Actor->CrObject, ManaPoints, SoulPoints, 4);
+ break;
+ }
+
+ case 92:{
+ ObjectType OldType = GetNewObjectType(90, 25);
+ ObjectType NewType = GetNewObjectType(90, 57);
+ EnchantObject(Actor, ManaPoints, SoulPoints, OldType, NewType);
+ break;
+ }
+
+ case 93:{
+ Challenge(Actor, ManaPoints, SoulPoints, 2);
+ break;
+ }
+
+ case 94:{
+ CreateField(Actor, ManaPoints, SoulPoints, FIELD_TYPE_WILDGROWTH);
+ break;
+ }
+
+ case 95:{
+ CreateArrows(Actor, ManaPoints, SoulPoints, 4, 1);
+ break;
+ }
+ }
+
+ if(IsAggressiveSpell(SpellNr)){
+ Actor->BlockLogout(60, false);
+ }
+}
+
+static void RuneSpell(uint32 CreatureID, int SpellNr){
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("RuneSpell: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ uint8 RuneGr = SpellList[SpellNr].RuneGr;
+ uint8 RuneNr = SpellList[SpellNr].RuneNr;
+ int ManaPoints = SpellList[SpellNr].Mana;
+ int SoulPoints = SpellList[SpellNr].SoulPoints;
+ int Amount = SpellList[SpellNr].Amount;
+
+ if(RuneGr == 0){
+ error("RuneSpell: Spell %d ist Runenspruch, hat aber keine Rune.\n", SpellNr);
+ throw ERROR;
+ }
+
+ CheckSpellbook(Actor, SpellNr);
+ CheckAccount(Actor, SpellNr);
+ CheckLevel(Actor, SpellNr);
+ CheckRing(Actor, SpellNr);
+ if(Actor->EarliestSpellTime > ServerMilliseconds){
+ throw EXHAUSTED;
+ }
+
+ bool RuneCreated = false;
+ ObjectType BlankType = GetSpecialObject(RUNE_BLANK);
+
+ Object RightHand = GetBodyObject(Actor->ID, 5); // RIGHTHAND
+ if(RightHand.exists() && RightHand.getObjectType() == BlankType){
+ CheckMana(Actor, ManaPoints, SoulPoints, 1000);
+ ObjectType RuneType = GetNewObjectType(RuneGr, RuneNr);
+ Change(RightHand, RuneType, Amount);
+ RuneCreated = true;
+ }
+
+ Object LeftHand = GetBodyObject(Actor->ID, 6); // LEFTHAND
+ if(LeftHand.exists() && LeftHand.getObjectType() == BlankType){
+ // TODO(fusion): Ughh... I'm not sure why we're trying to cast the spell
+ // twice but we need to make so errors from the second cast are only
+ // relevant if there wasn't a first cast.
+ try{
+ CheckMana(Actor, ManaPoints, SoulPoints, 1000);
+ ObjectType RuneType = GetNewObjectType(RuneGr, RuneNr);
+ Change(LeftHand, RuneType, Amount);
+ RuneCreated = true;
+ }catch(...){
+ if(!RuneCreated){
+ throw;
+ }
+ }
+ }
+
+ if(!RuneCreated){
+ throw MAGICITEM;
+ }
+
+ GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_MAGIC_RED);
+}
+
+static int TypeOfSpell(const char *Text){
+ // NOTE(fusion): We're checking `Text` against the first 5 spell syllables
+ // which are "al", "ad", "ex", "ut", "om". They determine the type of the
+ // spell. See `CheckForSpell`.
+ int SpellType = 0;
+ if(Text != NULL){
+ for(int SyllableNr = 1;
+ SyllableNr < 6;
+ SyllableNr += 1){
+ if(stricmp(Text, SpellSyllable[SyllableNr], 2) == 0){
+ SpellType = SyllableNr;
+ break;
+ }
+ }
+ }
+ return SpellType;
+}
+
+static int FindSpell(const uint8 *Syllable){
+ int BestMatch = 0;
+ int MinParams = 100;
+ for(int SpellNr = 0;
+ SpellNr < NARRAY(SpellList);
+ SpellNr += 1){
+ int Params = 0;
+ TSpellList *Spell = &SpellList[SpellNr];
+ for(int i = 0;
+ i < NARRAY(Spell->Syllable);
+ i += 1){
+ // NOTE(fusion): SpellSyllable[6] is "para" which refers to a spell
+ // parameter.
+ if(Spell->Syllable[i] == 6){
+ if(Syllable[i] == 0)
+ break;
+ Params += 1;
+ }else if(Spell->Syllable[i] != Syllable[i]){
+ break;
+ }
+
+ if(Spell->Syllable[i] == 0){
+ if(Params < MinParams){
+ MinParams = Params;
+ BestMatch = SpellNr;
+ }
+ break;
+ }
+ }
+ }
+
+ return BestMatch;
+}
+
+static int FindSpell(Object Obj){
+ if(!Obj.exists()){
+ error("FindSpell: Übergebenes Objekt existiert nicht.\n");
+ return 0;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(RUNE)){
+ error("FindSpell: Übergebenes Objekt ist nicht magisch.\n");
+ return 0;
+ }
+
+ int Result = 0;
+ for(int SpellNr = 0;
+ SpellNr < NARRAY(SpellList);
+ SpellNr += 1){
+ TSpellList *Spell = &SpellList[SpellNr];
+ ObjectType RuneType = GetNewObjectType(Spell->RuneGr, Spell->RuneNr);
+ if(ObjType == RuneType){
+ Result = SpellNr;
+ break;
+ }
+ }
+ return Result;
+}
+
+static void GetSpellString(int SpellNr, char *Text){
+ Text[0] = 0;
+
+ if(SpellNr < 1 || SpellNr >= NARRAY(SpellList)){
+ error("GetSpellString: Ungültige Zauberspruchnummer %d.\n", SpellNr);
+ return;
+ }
+
+ TSpellList *Spell = &SpellList[SpellNr];
+ for(int i = 0; i < NARRAY(Spell->Syllable); i += 1){
+ const char *Syllable = SpellSyllable[Spell->Syllable[i]];
+ if(Syllable[0] == 0){
+ break;
+ }
+
+ // TODO(fusion): Review. I think we don't add a space between the first
+ // and second syllable and it's probably correct.
+ if(i >= 2){
+ strcat(Text, " ");
+ }
+
+ strcat(Text, Syllable);
+ }
+}
+
+void GetMagicItemDescription(Object Obj, char *SpellString, int *MagicLevel){
+ SpellString[0] = 0;
+ *MagicLevel = 0;
+
+ if(!Obj.exists()){
+ error("GetMagicItemDescription: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(RUNE)){
+ error("GetMagicItemDescription: Übergebenes Objekt ist nicht magisch.\n");
+ return;
+ }
+
+ int SpellNr = FindSpell(Obj);
+ if(SpellNr == 0){
+ error("GetMagicItemDescription: Objekt %d hat keinen Zauberspruch.\n", ObjType.TypeID);
+ return;
+ }
+
+ GetSpellString(SpellNr, SpellString);
+ *MagicLevel = (int)SpellList[SpellNr].RuneLevel;
+}
+
+void GetSpellbook(uint32 CharacterID, char *Buffer){
+ TPlayer *Player = GetPlayer(CharacterID);
+ if(Player == NULL){
+ error("GetSpellbook: Spieler existiert nicht.\n");
+ return;
+ }
+
+ if(Buffer == NULL){
+ error("GetSpellbook: Übergebener Puffer existiert nicht.\n");
+ return;
+ }
+
+ // TODO(fusion): We're iterating over all spells for each level. This is bad
+ // because we could have a list of spell numbers sorted by level and achieve
+ // the same thing. We also had a hard coded max level but since we're already
+ // iterating so many times over the spell list, what's another one?
+ // Also, the overuse of `strcat` doesn't help.
+
+ int MaxLevel = 1;
+ for(int SpellNr = 0;
+ SpellNr < NARRAY(SpellList);
+ SpellNr += 1){
+ int Level = (int)SpellList[SpellNr].Level;
+ if(Level > MaxLevel){
+ Level = MaxLevel;
+ }
+ }
+
+ char Help[256];
+ for(int Level = 1; Level <= MaxLevel; Level += 1){
+ bool First = true;
+ for(int SpellNr = 0;
+ SpellNr < NARRAY(SpellList);
+ SpellNr += 1){
+ TSpellList *Spell = &SpellList[SpellNr];
+ if((int)Spell->Level != Level || !Player->SpellKnown(SpellNr)){
+ continue;
+ }
+
+ if(First){
+ sprintf(Help, "Spells for Level %d\n", Level);
+ strcat(Buffer, Help);
+ First = false;
+ }
+
+ GetSpellString(SpellNr, Help);
+ if(Help[0] == 0){
+ error("GetSpellbook: Zauberspruch %d hat keine Zauberformel.\n", SpellNr);
+ continue;
+ }
+
+ strcat(Buffer, " ");
+ strcat(Buffer, Help);
+ if(Spell->Comment != NULL && Spell->Comment[0] != 0){
+ strcat(Buffer, " - ");
+ strcat(Buffer, Spell->Comment);
+ if(SpellNr == 9 || SpellNr == 12){
+ strcpy(Help, ": var");
+ }else if(SpellNr == 80){
+ strcpy(Help, ": 4*Level");
+ }else{
+ sprintf(Help, ": %d", Spell->Mana);
+ }
+ strcat(Buffer, Help);
+ }
+ strcat(Buffer, "\n");
+ }
+
+ if(!First){
+ strcat(Buffer, "\n");
+ }
+ }
+}
+
+int GetSpellLevel(int SpellNr){
+ if(SpellNr < 1 || SpellNr >= NARRAY(SpellList)){
+ error("GetSpellLevel: Ungültige Spruchnummer %d.\n", SpellNr);
+ return 1;
+ }
+
+ return (int)SpellList[SpellNr].Level;
+}
+
+int CheckForSpell(uint32 CreatureID, const char *Text){
+ // IMPORTANT(fusion): The first syllable also determines the type of the spell
+ // which is why we can quickly rule out a spell cast if it is unknown.
+ // It is also handled separately in the parsing loop below to allow for the
+ // second syllable to be glued together (or not).
+ int SpellType = TypeOfSpell(Text);
+ if(SpellType == 0){
+ return 0;
+ }
+
+ // TODO(fusion): Keeping syllables in text form doesn't make sense. `SpellStr`
+ // should ideally only contain parameters.
+
+ // TODO(fusion): Did we really need to use `stringstream` here? It allocates
+ // an internal `std::string` from the input string which is just wasteful,
+ // specially for the minimal processing we're doing here.
+
+ int SyllableCount = 1;
+ uint8 Syllable[10] = { (uint8)SpellType };
+ char SpellStr[10][512] = {};
+ strcpy(SpellStr[0], SpellSyllable[SpellType]);
+
+ std::istringstream IS(Text);
+ IS.get();
+ IS.get();
+ while(!IS.eof()){
+ while(isSpace(IS.peek())){
+ IS.get();
+ }
+
+ int Index = SyllableCount;
+ if(IS.peek() == '"'){
+ IS.get();
+ IS.get(SpellStr[Index], sizeof(SpellStr[0]), '"');
+ IS.get();
+ }else{
+ IS.get(SpellStr[Index], sizeof(SpellStr[0]), ' ');
+ }
+
+ // TODO(fusion): This could be a problem if there is a "" parameter?
+ if(SpellStr[Index][0] == 0){
+ break;
+ }
+
+ for(int SyllableNr = 0;
+ SyllableNr < NARRAY(SpellSyllable);
+ SyllableNr += 1){
+ if(stricmp(SpellStr[Index], SpellSyllable[SyllableNr]) == 0){
+ Syllable[Index] = (uint8)SyllableNr;
+ break;
+ }
+ }
+
+ // NOTE(fusion): SpellSyllable[6] is "para" which refers to a spell
+ // parameter. This is setting up `Syllable` to be used by `FindSpell`.
+ if(Syllable[Index] == 0){
+ Syllable[Index] = 6;
+ }
+
+ SyllableCount += 1;
+ }
+
+ int SpellNr = FindSpell(Syllable);
+ if(SpellNr != 0){
+ try{
+ switch(SpellType){
+ case 1: CharacterRightSpell(CreatureID, SpellNr, SpellStr); break;
+ case 2: RuneSpell(CreatureID, SpellNr); break;
+ case 3: CastSpell(CreatureID, SpellNr, SpellStr); break;
+ case 4: CastSpell(CreatureID, SpellNr, SpellStr); break;
+ case 5: AccountRightSpell(CreatureID, SpellNr, SpellStr); break;
+ default:{
+ error("CheckForSpell: Spruchklasse %d existiert nicht.\n", SpellType);
+ break;
+ }
+ }
+ }catch(RESULT r){
+ // TODO(fusion): `SpellFailed` is inlined in here but I think it's
+ // cleaner to keep it this way.
+ TCreature *Actor = GetCreature(CreatureID);
+ if(Actor == NULL){
+ error("SpellFailed: Kreatur existiert nicht.\n");
+ }else{
+ if(r != ERROR){
+ GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF);
+ }
+
+ if(Actor->Type == PLAYER){
+ SendResult(Actor->Connection, r);
+ }
+ }
+
+ return -SpellType;
+ }
+ }
+
+ return SpellType;
+}
+
+static void DeleteRune(Object Obj){
+ if(!Obj.exists()){
+ error("DeleteRune: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ // TODO(fusion): Should probably check if object is a rune?
+ uint32 Charges = Obj.getAttribute(CHARGES);
+ if(Charges > 1){
+ Change(Obj, CHARGES, Charges - 1);
+ }else{
+ Delete(Obj, -1);
+ }
+}
+
+void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest){
+ TPlayer *Actor = GetPlayer(CreatureID);
+ if(Actor == NULL){
+ error("UseMagicItem: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Obj.exists()){
+ error("UseMagicItem: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Dest.exists()){
+ error("UseMagicItem: Übergebenes Ziel existiert nicht (Objekt %d).\n",
+ Obj.getObjectType().TypeID);
+ throw ERROR;
+ }
+
+ if(CheckRight(Actor->ID, NO_RUNES)){
+ throw NOTUSABLE;
+ }
+
+ int SpellNr = FindSpell(Obj);
+ if(SpellNr == 0){
+ error("UseMagicItem: Für Objekt %d existiert kein Spruch.\n",
+ Obj.getObjectType().TypeID);
+ throw ERROR;
+ }
+
+
+ // NOTE(fusion): This target picking logic is probably to avoid picking
+ // obviously wrong candidates when there are multiple creatures on a single
+ // field.
+ TCreature *Target = NULL;
+ bool Aggressive = IsAggressiveSpell(SpellNr);
+ {
+ if(Dest.getObjectType().isCreatureContainer()){
+ Target = GetCreature(Dest);
+ }
+
+ Object Other = GetFirstContainerObject(Dest.getContainer());
+ while(Other != NONE){
+ if(Other.getObjectType().isCreatureContainer()){
+ uint32 OtherID = Other.getCreatureID();
+ if(Target == NULL
+ || (Aggressive && OtherID != Actor->ID)
+ || (!Aggressive && OtherID == Actor->ID)){
+ Target = GetCreature(OtherID);
+ Dest = Other;
+ }
+ }
+ Other = Other.getNextObject();
+ }
+ }
+
+ // NOTE(fusion): Rune check.
+ CheckRuneLevel(Actor, SpellNr);
+
+ if(Actor->EarliestSpellTime > ServerMilliseconds){
+ throw EXHAUSTED;
+ }
+
+ if(Aggressive){
+ int DestX, DestY, DestZ;
+ GetObjectCoordinates(Dest, &DestX, &DestY, &DestZ);
+ if(!CheckRight(Actor->ID, ATTACK_EVERYWHERE)
+ && (IsProtectionZone(Actor->posx, Actor->posy, Actor->posz)
+ || IsProtectionZone(DestX, DestY, DestZ))){
+ throw PROTECTIONZONE;
+ }
+
+ if(Dest.getContainer().getObjectType().isMapContainer()){
+ throw NOROOM;
+ }
+ }
+
+ try{
+ switch(SpellNr){
+ case 4:{
+ if(Target == NULL){
+ throw NOCREATURE;
+ }
+
+ int Amount = ComputeDamage(Actor, SpellNr, 70, 30);
+ Heal(Target, -1, 0, Amount); // -1 ?
+ break;
+ }
+
+ case 5:{
+ if(Target == NULL){
+ throw NOCREATURE;
+ }
+
+ int Amount = ComputeDamage(Actor, SpellNr, 250, 30);
+ Heal(Target, -1, 0, Amount); // -1 ?
+ break;
+ }
+
+ case 7:{
+ int Damage = ComputeDamage(Actor, SpellNr, 15, 5);
+ Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_BURST,
+ ANIMATION_FIRE, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 8:{
+ int Damage = ComputeDamage(Actor, SpellNr, 30, 10);
+ Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_BURST,
+ ANIMATION_FIRE, DAMAGE_ENERGY);
+ break;
+ }
+
+ case 12:{
+ if(Target == NULL){
+ throw NOCREATURE;
+ }
+
+ Convince(Actor, Target);
+ break;
+ }
+
+ case 14:{
+ ObjectIllusion(Actor, 0, 0, Dest, 200);
+ break;
+ }
+
+ case 15:{
+ int Damage = ComputeDamage(Actor, SpellNr, 20, 5);
+ MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE,
+ 3, DAMAGE_FIRE, ANIMATION_FIRE);
+ break;
+ }
+
+ case 16:{
+ int Damage = ComputeDamage(Actor, SpellNr, 50, 15);
+ MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE,
+ 4, DAMAGE_FIRE, ANIMATION_FIRE);
+ break;
+ }
+
+ case 17:{
+ MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_FIRE, 3);
+ break;
+ }
+
+ case 18:{
+ int Damage = ComputeDamage(Actor, SpellNr, 60, 40);
+ MassCombat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_EXPLOSION,
+ 1, DAMAGE_PHYSICAL, ANIMATION_FIRE);
+ break;
+ }
+
+ case 21:{
+ int Damage = ComputeDamage(Actor, SpellNr, 150, 20);
+ Combat(Actor, Dest, 0, 0, Damage, EFFECT_DEATH,
+ ANIMATION_DEATH, DAMAGE_PHYSICAL);
+ break;
+ }
+
+ case 25:{
+ CreateField(Actor, Dest, 0, 0, FIELD_TYPE_FIRE);
+ break;
+ }
+
+ case 26:{
+ CreateField(Actor, Dest, 0, 0, FIELD_TYPE_POISON);
+ break;
+ }
+
+ case 27:{
+ CreateField(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY);
+ break;
+ }
+
+ case 28:{
+ CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_FIRE, 2);
+ break;
+ }
+
+ case 30:{
+ DeleteField(Actor, Dest, 0, 0);
+ break;
+ }
+
+ case 31:{
+ if(Target == NULL){
+ throw NOCREATURE;
+ }
+
+ NegatePoison(Actor, Target, 0, 0);
+ break;
+ }
+
+ case 32:{
+ CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_POISON, 2);
+ break;
+ }
+
+ case 33:{
+ CreateFieldWall(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY, 3);
+ break;
+ }
+
+ case 50:{
+ int Damage = ComputeDamage(Actor, SpellNr, 120, 20);
+ Combat(Actor, Dest, 0, 0, Damage, EFFECT_FIRE_HIT,
+ ANIMATION_FIRE, DAMAGE_FIRE_PERIODIC);
+ break;
+ }
+
+ case 54:{
+ if(Target == NULL){
+ throw NOCREATURE;
+ }
+
+ if(Actor->GetEffectiveProfession() != PROFESSION_DRUID){
+ throw NOTUSABLE;
+ }
+
+ int ManaPoints = SpellList[SpellNr].Mana;
+ int SoulPoints = SpellList[SpellNr].SoulPoints;
+ MagicGoStrength(Actor, Target, ManaPoints, SoulPoints, -101, 1);
+ break;
+ }
+
+ case 55:{
+ MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_ENERGY, 3);
+ break;
+ }
+
+ case 77:{
+ int Damage = ComputeDamage(Actor, SpellNr, 70, 20);
+ Combat(Actor, Dest, 0, 0, Damage, EFFECT_POISON_HIT,
+ ANIMATION_ENERGY, DAMAGE_POISON_PERIODIC);
+ break;
+ }
+
+ case 78:{
+ CleanupField(Actor, Dest, 0, 0);
+ break;
+ }
+
+ case 83:{
+ RaiseDead(Actor, Dest, 0, 0);
+ break;
+ }
+
+ case 86:{
+ CreateField(Actor, Dest, 0, 0, FIELD_TYPE_MAGICWALL);
+ break;
+ }
+
+ case 91:{
+ MassCreateField(Actor, Dest, 0, 0, FIELD_TYPE_POISON, 3);
+ break;
+ }
+
+ default:{
+ error("UseMagicItem: Spell %d noch nicht implementiert.\n", SpellNr);
+ throw ERROR;
+ }
+ }
+ }catch(RESULT r){
+ // TODO(fusion): Same as with `CheckForSpell` except that `Actor` is
+ // already in scope.
+ if(r != ERROR){
+ GraphicalEffect(Actor->posx, Actor->posy, Actor->posz, EFFECT_POFF);
+ }
+ SendResult(Actor->Connection, r);
+ return;
+ }
+
+ DeleteRune(Obj);
+
+ if(Aggressive){
+ Actor->BlockLogout(60, false);
+ }
+}
+
+void DrinkPotion(uint32 CreatureID, Object Obj){
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("DrinkPotion: Kreatur existiert nicht.\n");
+ throw ERROR;
+ }
+
+ if(!Obj.exists()){
+ error("DrinkPotion: Übergebenes Objekt existiert nicht.\n");
+ throw ERROR;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ if(!ObjType.getFlag(LIQUIDCONTAINER)){
+ error("DrinkPotion: Übergebenes Objekt ist kein Flüssigkeitscontainer.\n");
+ throw ERROR;
+ }
+
+ int LiquidType = (int)Obj.getAttribute(CONTAINERLIQUIDTYPE);
+ if(LiquidType == LIQUID_MANA){
+ int Amount = ComputeDamage(NULL, 0, 100, 50);
+ RefreshMana(Player, 0, 0, Amount);
+ }else if(LiquidType == LIQUID_LIFE){
+ int Amount = ComputeDamage(NULL, 0, 50, 25);
+ Heal(Player, 0, 0, Amount);
+ }else{
+ error("DrinkPotion: Objekt enthält keinen Zaubertrank.\n");
+ throw ERROR;
+ }
+
+ Change(Obj, CONTAINERLIQUIDTYPE, LIQUID_NONE);
+}
+
// Magic Init Functions
// =============================================================================
static void InitCircles(void){
diff --git a/src/magic.hh b/src/magic.hh
index 0525ad2..e1dc354 100644
--- a/src/magic.hh
+++ b/src/magic.hh
@@ -4,6 +4,14 @@
#include "common.hh"
#include "creature.hh"
+enum : int {
+ FIELD_TYPE_FIRE = 1,
+ FIELD_TYPE_POISON = 2,
+ FIELD_TYPE_ENERGY = 3,
+ FIELD_TYPE_MAGICWALL = 4,
+ FIELD_TYPE_WILDGROWTH = 5,
+};
+
struct TImpact{
// VIRTUAL FUNCTIONS
// =========================================================================
@@ -89,25 +97,105 @@ struct TSummonImpact: TImpact {
int Maximum;
};
-struct TCircle {
- int x[32];
- int y[32];
- int Count;
-};
-
-struct TSpellList {
- uint8 Syllable[10];
- uint8 RuneGr;
- uint8 RuneNr;
- const char *Comment;
- uint16 Level;
- uint16 RuneLevel;
- uint16 Flags;
- int Mana;
- int SoulPoints;
- int Amount;
-};
+void ActorShapeSpell(TCreature *Actor, TImpact *Impact, int Effect);
+void VictimShapeSpell(TCreature *Actor, TCreature *Victim,
+ int Range, int Animation, TImpact *Impact, int Effect);
+void OriginShapeSpell(TCreature *Actor, int Radius, TImpact *Impact, int Effect);
+void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ,
+ int Range, int Animation, int Radius, TImpact *Impact, int Effect);
+void DestinationShapeSpell(TCreature *Actor, TCreature *Victim,
+ int Range, int Animation, int Radius, TImpact *Impact, int Effect);
+void AngleShapeSpell(TCreature *Actor, int Angle, int Range, TImpact *Impact, int Effect);
+void CheckSpellbook(TCreature *Actor, int SpellNr);
+void CheckAccount(TCreature *Actor, int SpellNr);
+void CheckLevel(TCreature *Actor, int SpellNr);
+void CheckRuneLevel(TCreature *Actor, int SpellNr);
+void CheckMagicItem(TCreature *Actor, ObjectType Type);
+void CheckRing(TCreature *Actor, int SpellNr);
+void CheckAffectedPlayers(TCreature *Actor, int x, int y, int z);
+void CheckMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Delay);
+int ComputeDamage(TCreature *Actor, int SpellNr, int Damage, int Variation);
+bool IsAggressiveSpell(int SpellNr);
+void MassCombat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints,
+ int Damage, int Effect, int Radius, int DamageType, int Animation);
+void AngleCombat(TCreature *Actor, int ManaPoints, int SoulPoints,
+ int Damage, int Effect, int Range, int Angle, int DamageType);
+void Combat(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints,
+ int Damage, int Effect, int Animation, int DamageType);
+int GetDirection(int dx, int dy); // TODO(fusion): Move this one elsewhere? Maybe `info.cc`.
+void KillAllMonsters(TCreature *Actor, int Effect, int Radius);
+void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful);
+void CreateField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int FieldType);
+void CreateField(TCreature *Actor, int ManaPoints, int SoulPoints, int FieldType);
+void MassCreateField(TCreature *Actor, Object Target,
+ int ManaPoints, int SoulPoints, int FieldType, int Radius);
+void CreateFieldWall(TCreature *Actor, Object Target,
+ int ManaPoints, int SoulPoints, int FieldType, int Width);
+void DeleteField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints);
+void CleanupField(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints);
+void CleanupField(TCreature *Actor);
+void Teleport(TCreature *Actor, const char *Param);
+void TeleportToCreature(TCreature *Actor, const char *Name);
+void TeleportPlayerToMe(TCreature *Actor, const char *Name);
+void MagicRope(TCreature *Actor, int ManaPoints, int SoulPoints);
+void MagicClimbing(TCreature *Actor, int ManaPoints, int SoulPoints, const char *Param);
+void MagicClimbing(TCreature *Actor, const char *Param);
+void CreateThing(TCreature *Actor, const char *Param1, const char *Param2);
+void CreateMoney(TCreature *Actor, const char *Param);
+void CreateFood(TCreature *Actor, int ManaPoints, int SoulPoints);
+void CreateArrows(TCreature *Actor, int ManaPoints, int SoulPoints, int ArrowType, int Count);
+void SummonCreature(TCreature *Actor, int ManaPoints, int Race, bool God);
+void SummonCreature(TCreature *Actor, int ManaPoints, const char *RaceName, bool God);
+void StartMonsterraid(TCreature *Actor, const char *RaidName);
+void RaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints);
+void MassRaiseDead(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius);
+void Heal(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount);
+void MassHeal(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Amount, int Radius);
+void HealFriend(TCreature *Actor, const char *TargetName, int ManaPoints, int SoulPoints, int Amount);
+void RefreshMana(TCreature *Actor, int ManaPoints, int SoulPoints, int Amount);
+void MagicGoStrength(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints, int Percent, int Duration);
+void Shielding(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration);
+void NegatePoison(TCreature *Actor, TCreature *Target, int ManaPoints, int SoulPoints);
+void Enlight(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius, int Duration);
+void Invisibility(TCreature *Actor, int ManaPoints, int SoulPoints, int Duration);
+void CancelInvisibility(TCreature *Actor, Object Target, int ManaPoints, int SoulPoints, int Radius);
+void CreatureIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, const char *RaceName, int Duration);
+void ObjectIllusion(TCreature *Actor, int ManaPoints, int SoulPoints, Object Target, int Duration);
+void ChangeData(TCreature *Actor, const char *Param);
+void EnchantObject(TCreature *Actor, int ManaPoints, int SoulPoints, ObjectType OldType, ObjectType NewType);
+void Convince(TCreature *Actor, TCreature *Target);
+void Challenge(TCreature *Actor, int ManaPoints, int SoulPoints, int Radius);
+void FindPerson(TCreature *Actor, int ManaPoints, int SoulPoints, const char *TargetName);
+void GetPosition(TCreature *Actor);
+void GetQuestValue(TCreature *Actor, const char *Param);
+void SetQuestValue(TCreature *Actor, const char *Param1, const char *Param2);
+void ClearQuestValues(TCreature *Actor);
+void CreateKnowledge(TCreature *Actor, const char *Param1, const char *Param2);
+void ChangeProfession(TCreature *Actor, const char *Param);
+void EditGuests(TCreature *Actor);
+void EditSubowners(TCreature *Actor);
+void EditNameDoor(TCreature *Actor);
+void KickGuest(TCreature *Actor, const char *GuestName);
+void Notation(TCreature *Actor, const char *Name, const char *Comment);
+void NameLock(TCreature *Actor, const char *Name);
+void BanishAccount(TCreature *Actor, const char *Name, int Duration, const char *Reason);
+void DeleteAccount(TCreature *Actor, const char *Name, const char *Reason);
+void BanishCharacter(TCreature *Actor, const char *Name, int Duration, const char *Reason);
+void DeleteCharacter(TCreature *Actor, const char *Name, const char *Reason);
+void IPBanishment(TCreature *Actor, const char *Name, const char *Reason);
+void SetNameRule(TCreature *Actor, const char *Name);
+void KickPlayer(TCreature *Actor, const char *Name);
+void HomeTeleport(TCreature *Actor, const char *Name);
+
+// TODO(fusion): These are unsafe like strcpy.
+void GetMagicItemDescription(Object Obj, char *SpellString, int *MagicLevel);
+void GetSpellbook(uint32 CharacterID, char *Buffer);
+
+int GetSpellLevel(int SpellNr);
+
+int CheckForSpell(uint32 CreatureID, const char *Text);
+void UseMagicItem(uint32 CreatureID, Object Obj, Object Dest);
+void DrinkPotion(uint32 CreatureID, Object Obj);
-void CheckMana(TCreature *Creature, int ManaPoints, int SoulPoints, int Delay);
#endif //TIBIA_MAGIC_HH_
diff --git a/src/main.cc b/src/main.cc
index d3a0cbd..3925b04 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -541,8 +541,8 @@ int main(int argc, char **argv){
// once we identify all throw statements and how to roughly handle them.
try{
LaunchGame();
- }catch(RESULT result){
- error("main: Nicht abgefangene Exception %d.\n", result);
+ }catch(RESULT r){
+ error("main: Nicht abgefangene Exception %d.\n", r);
}catch(const char *str){
error("main: Nicht abgefangene Exception \"%s\".\n", str);
}catch(const std::exception &e){
diff --git a/src/stubs.hh b/src/stubs.hh
index 6013a17..a1b451a 100644
--- a/src/stubs.hh
+++ b/src/stubs.hh
@@ -22,15 +22,12 @@ extern void Change(Object Obj, ObjectType NewType, uint32 Value);
extern void CharacterDeathOrder(TCreature *Creature, int OldLevel,
uint32 Offender, const char *Remark, bool Unjustified);
extern bool CheckRight(uint32 CreatureID, RIGHT Right);
-extern void CircleShapeSpell(TCreature *Actor, int DestX, int DestY, int DestZ,
- int Range, int Animation, int Radius, TImpact *Impact, int Effect);
extern void CleanHouseField(int x, int y, int z);
-extern int ComputeDamage(TCreature *Creature, int SpellNr, int Damage, int Variation);
extern void ConvinceMonster(TCreature *Master, TCreature *Slave);
extern void ChallengeMonster(TCreature *Challenger, TCreature *Monster);
extern int CountInventoryObjects(uint32 CreatureID, ObjectType Type, uint32 Value);
+extern Object Create(Object Con, ObjectType Type, uint32 Value);
extern Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value);
-extern void CreateField(int x, int y, int z, int FieldType, uint32 Owner, bool Peaceful);
extern void CreateMonster(int Race, int x, int y, int z, int Home, uint32 Master, bool ShowEffect);
extern void CreatePlayerList(bool Online);
extern void CreatePool(Object Con, ObjectType Type, uint32 Value);
@@ -92,6 +89,7 @@ extern void SendMessage(TConnection *Connection, int Mode, const char *Text, ...
extern void SendPlayerData(TConnection *Connection);
extern void SendPlayerSkills(TConnection *Connection);
extern void SendPlayerState(TConnection *Connection, uint8 State);
+extern void SendResult(TConnection *Connection, RESULT r);
extern void ShowGuestList(uint16 HouseID, TPlayer *Player, char *Buffer);
extern void ShowSubownerList(uint16 HouseID, TPlayer *Player, char *Buffer);
extern void ShowNameDoor(Object Door, TPlayer *Player, char *Buffer);