1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * aint32 with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 *
22 * Based on the original sources
23 * Faery Tale II -- The Halls of the Dead
24 * (c) 1993-1996 The Wyrmkeep Entertainment Co.
25 */
26
27 //#define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove
28
29 #include "saga2/saga2.h"
30 #include "saga2/cmisc.h"
31 #include "saga2/player.h"
32 #include "saga2/enchant.h"
33
34 namespace Saga2 {
35
36 extern int16 objectProtoCount; // object prototype count
37
38 int enchantmentProto = -1;
39
40 void setEnchantmentDisplay(void);
41
42 //-------------------------------------------------------------------
43 // Enchantment Creation Function
44
EnchantObject(ObjectID target,int enchantmentType,int duration)45 ObjectID EnchantObject(
46 ObjectID target,
47 int enchantmentType,
48 int duration) {
49 GameObject *obj = GameObject::objectAddress(target);
50 GameObject *ench;
51 ProtoObj *enchProto;
52 TilePoint slot;
53
54 assert(enchantmentProto >= 0);
55 assert(enchantmentProto < objectProtoCount);
56
57 enchProto = g_vm->_objectProtos[enchantmentProto];
58
59 ench = GameObject::newObject(); //Create Enchantment
60 if (ench == NULL) return Nothing;
61
62 // Fill in the enchantment object. Note that the 'hitpoints'
63 // of an enchantment are actually the duration of it's life
64 // (in 10-second background cycles).
65 //
66 // Also note the use of the 'enchantment type' field to
67 // indicate the effects of the enchantment. This is to
68 // avoid having to create 50 new classes, representing
69 // 50 new enchantments.
70 ench->setScript(0);
71 ench->setFlags(0, (uint8) - 1);
72 ench->setHitPoints(duration);
73 ench->setExtra(enchantmentType);
74 ench->setProtoNum(enchantmentProto);
75
76 // Put in object's container
77 if (obj->getAvailableSlot(ench, &slot))
78 ench->move(Location(slot, target));
79
80 // Now, change the object base on enchantments
81 obj->evalEnchantments();
82 assert(enchProto->containmentSet() & ProtoObj::isEnchantment);
83 assert((ench->protoAddress(ench->thisID()))->containmentSet() & ProtoObj::isEnchantment);
84 return ench->thisID();
85 }
86
87 //-------------------------------------------------------------------
88 // Function to deliberately dispel an enchantment
89
DispelObjectEnchantment(ObjectID target,int enchantmentType)90 bool DispelObjectEnchantment(
91 ObjectID target,
92 int enchantmentType) {
93 ObjectID enchID;
94
95 enchID = FindObjectEnchantment(target, enchantmentType);
96
97 if (enchID != Nothing) {
98 GameObject *ench = GameObject::objectAddress(enchID);
99 GameObject *obj = GameObject::objectAddress(target);
100
101 // Remove the enchantment and it's effects
102 ench->deleteObject();
103 obj->evalEnchantments();
104 return true;
105 }
106
107 return false;
108 }
109
110 //-------------------------------------------------------------------
111 // Function to locate an enchantment on an object
112
FindObjectEnchantment(ObjectID target,int enchantmentType)113 ObjectID FindObjectEnchantment(
114 ObjectID target,
115 int enchantmentType) {
116 GameObject *obj = GameObject::objectAddress(target);
117 GameObject *containedObj;
118 ObjectID objID;
119 ContainerIterator iter(obj);
120
121 while ((objID = iter.next(&containedObj)) != Nothing) {
122 ProtoObj *proto = containedObj->proto();
123
124 if ((proto->containmentSet() & ProtoObj::isEnchantment)
125 && ((containedObj->getExtra() & 0xFF00) == (enchantmentType & 0xFF00))) {
126 return objID;
127 }
128 }
129
130 return Nothing;
131 }
132
133
clearEnchantments(Actor * a)134 void clearEnchantments(Actor *a) {
135 ActorAttributes *ea = a->getStats();
136 ActorAttributes *ba = a->getBaseStats();
137
138 ea->archery = ba->archery;
139 ea->swordcraft = ba->swordcraft;
140 ea->shieldcraft = ba->shieldcraft;
141 ea->bludgeon = ba->bludgeon;
142 ea->throwing = ba->throwing;
143 ea->spellcraft = ba->spellcraft;
144 ea->stealth = ba->stealth;
145 ea->agility = ba->agility;
146 ea->brawn = ba->brawn;
147 ea->lockpick = ba->lockpick;
148 ea->pilfer = ba->pilfer;
149 ea->firstAid = ba->firstAid;
150 ea->spotHidden = ba->spotHidden;
151
152 a->_enchantmentFlags = a->getBaseEnchantmentEffects();
153 a->_effectiveResistance = a->getBaseResistance();
154 a->_effectiveImmunity = a->getBaseImmunity();
155 a->_recPointsPerUpdate = a->getBaseRecovery();
156 }
157
addEnchantment(Actor * a,uint16 enchantmentID)158 void addEnchantment(Actor *a, uint16 enchantmentID) {
159 ActorAttributes *ea = a->getStats();
160 uint8 *stats = &ea->archery;
161 uint16 eType = getEnchantmentType(enchantmentID);
162 uint16 eSubType = getEnchantmentSubType(enchantmentID);
163 int16 eAmount = getEnchantmentAmount(enchantmentID);
164
165 switch (eType) {
166 case effectAttrib:
167 stats[eSubType] = clamp(0, stats[eSubType] + eAmount, 100);
168 break;
169 case effectResist:
170 a->setResist((effectResistTypes) eSubType, eAmount);
171 break;
172 case effectImmune:
173 a->setImmune((effectImmuneTypes) eSubType, eAmount);
174 break;
175 case effectOthers:
176 a->setEffect((effectOthersTypes) eSubType, eAmount);
177 break;
178 case effectSpecial: // damage shouldn't be an enchantment
179 // Special code needed
180 case effectDamage: // damage shouldn't be an enchantment
181 case effectNone:
182 break;
183 }
184
185 }
186
187 //-------------------------------------------------------------------
188 // Function to eval the enchantments on an actor
189
evalActorEnchantments(Actor * a)190 void evalActorEnchantments(Actor *a) {
191 GameObject *obj = nullptr;
192 ObjectID id;
193 PlayerActorID playerID;
194 EnchantmentIterator iter(a);
195 ContainerIterator cIter(a);
196
197 clearEnchantments(a);
198
199 for (id = iter.first(&obj); id != Nothing; id = iter.next(&obj)) {
200 ProtoObj *proto = obj->proto();
201
202 if (proto->containmentSet() & ProtoObj::isEnchantment) {
203 uint16 enchantmentID = obj->getExtra();
204 addEnchantment(a, enchantmentID);
205 }
206 }
207
208 while (cIter.next(&obj)) {
209 ProtoObj *proto = obj->proto();
210 uint16 cSet = proto->containmentSet();
211
212 if ((cSet & (ProtoObj::isArmor | ProtoObj::isWeapon | ProtoObj::isWearable))
213 && proto->isObjectBeingUsed(obj)) {
214 a->_effectiveResistance |= proto->resistance;
215 a->_effectiveImmunity |= proto->immunity;
216 }
217 }
218
219 if (actorToPlayerID(a, playerID))
220 recalcPortraitType(playerID);
221
222 if (a->thisID() == getCenterActorID())
223 setEnchantmentDisplay();
224 }
225
226 //-------------------------------------------------------------------
227 // Function to eval the enchantments on an actor
228
evalObjectEnchantments(GameObject * obj)229 void evalObjectEnchantments(GameObject *obj) {
230 // The only enchantment that currently works
231 // on objects is the invisibility bit.
232 //
233 // If more enchantment types are added, then we'll
234 // have to do this a bit differently...
235
236 if (FindObjectEnchantment(obj->thisID(), makeEnchantmentID(effectNonActor, objectInvisible, true)))
237 obj->setFlags((uint8) - 1, objectInvisible);
238 else
239 obj->setFlags(0, objectInvisible);
240 if (FindObjectEnchantment(obj->thisID(), makeEnchantmentID(effectNonActor, objectLocked, false)))
241 obj->setFlags((uint8) - 1, objectLocked);
242 }
243
244 //-------------------------------------------------------------------
245 // Enchantment iterator class
246
EnchantmentIterator(GameObject * container)247 EnchantmentIterator::EnchantmentIterator(GameObject *container) {
248 // Get the ID of the 1st object in the sector list
249 baseObject = container;
250 wornObject = NULL;
251 nextID = Nothing;
252 }
253
first(GameObject ** obj)254 ObjectID EnchantmentIterator::first(GameObject **obj) {
255 nextID = baseObject->IDChild();
256
257 return next(obj);
258 }
259
next(GameObject ** obj)260 ObjectID EnchantmentIterator::next(GameObject **obj) {
261 GameObject *object;
262 ObjectID id;
263
264 for (;;) {
265 id = nextID;
266
267 if (id == Nothing) {
268 // If we were searching a 'worn' object, then pop up a level
269 if (wornObject) {
270 nextID = wornObject->IDNext();
271 wornObject = NULL;
272 continue;
273 }
274
275 return Nothing;
276 }
277
278 // Get address of next object
279 object = GameObject::objectAddress(id);
280
281 ProtoObj *proto = object->proto();
282 uint16 cSet = proto->containmentSet();
283
284 if ((cSet & (ProtoObj::isArmor | ProtoObj::isWeapon | ProtoObj::isWearable))
285 && wornObject == NULL
286 && proto->isObjectBeingUsed(object)) {
287 wornObject = object;
288 nextID = object->IDChild();
289 continue;
290 }
291
292 nextID = object->IDNext();
293
294 if (cSet & ProtoObj::isEnchantment) break;
295 }
296
297 if (obj) *obj = object;
298 return id;
299 }
300
301 } // end of namespace Saga2
302