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