1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2003 The GemRB Project
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  *
19  */
20 
21 // This class represents the .itm (item) files of the engine
22 // Items are all the weapons, armor, carriable quest objects, etc.
23 
24 #include "Item.h"
25 
26 #include "voodooconst.h"
27 
28 #include "Interface.h"
29 #include "Projectile.h"
30 #include "ProjectileServer.h"
31 
32 namespace GemRB {
33 
ITMExtHeader(void)34 ITMExtHeader::ITMExtHeader(void)
35 {
36 	features = NULL;
37 	Location = Range = RechargeFlags = IDReq = 0;
38 	Charges = ChargeDepletion = Tooltip = Target = TargetNumber = 0;
39 	AttackType = THAC0Bonus = DiceSides = DiceThrown = DamageBonus = DamageType = 0;
40 	ProjectileAnimation = ProjectileQualifier = FeatureCount = FeatureOffset = 0;
41 }
42 
~ITMExtHeader(void)43 ITMExtHeader::~ITMExtHeader(void)
44 {
45 	delete [] features;
46 }
47 
Item(void)48 Item::Item(void)
49 {
50 	ext_headers = NULL;
51 	equipping_features = NULL;
52 	MinStrength = MinStrengthBonus = MinLevel = Weight = MaxStackAmount = ItemType = 0;
53 	MinIntelligence = MinDexterity = MinWisdom = MinConstitution = MinCharisma = 0;
54 	WeaProf = WieldColor = Enchantment = KitUsability = Flags = UsabilityBitmask = 0;
55 	Price = LoreToID = ItemDesc = ItemDescIdentified = ItemNameIdentified = ItemName = 0;
56 	ExtHeaderOffset = ExtHeaderCount = FeatureBlockOffset = 0;
57 	EquippingFeatureOffset = EquippingFeatureCount = 0;
58 	unknown1 = unknown2 = unknown3 = 0;
59 	ItemExcl = DialogName = 0;
60 }
61 
~Item(void)62 Item::~Item(void)
63 {
64 	delete [] ext_headers;
65 	delete [] equipping_features;
66 }
67 
68 //-1 will return equipping feature block
69 //otherwise returns the n'th feature block
GetEffectBlock(Scriptable * self,const Point & pos,int usage,ieDwordSigned invslot,ieDword pro) const70 EffectQueue *Item::GetEffectBlock(Scriptable *self, const Point &pos, int usage, ieDwordSigned invslot, ieDword pro) const
71 {
72 	Effect *features;
73 	int count;
74 
75 	if (usage>=ExtHeaderCount) {
76 		return NULL;
77 	}
78 	if (usage>=0) {
79 		features = ext_headers[usage].features;
80 		count = ext_headers[usage].FeatureCount;
81 	} else {
82 		features = equipping_features;
83 		count = EquippingFeatureCount;
84 	}
85 
86 	//collecting all self affecting effects in a single queue, so the random value is rolled only once
87 	EffectQueue *fxqueue = new EffectQueue();
88 	EffectQueue *selfqueue = new EffectQueue();
89 	Actor *target = (self->Type==ST_ACTOR)?(Actor *) self:NULL;
90 
91 	for (int i=0;i<count;i++) {
92 		Effect *fx = features+i;
93 		fx->InventorySlot = invslot;
94 		fx->CasterLevel = ITEM_CASTERLEVEL; //items all have casterlevel 10
95 		fx->CasterID = self->GetGlobalID();
96 		if (usage >= 0) {
97 			//this is not coming from the item header, but from the recharge flags
98 			fx->SourceFlags = ext_headers[usage].RechargeFlags;
99 		} else {
100 			fx->SourceFlags = 0;
101 		}
102 
103 		if (fx->Target != FX_TARGET_PRESET && EffectQueue::OverrideTarget(fx)) {
104 			fx->Target = FX_TARGET_PRESET;
105 		}
106 
107 		if (fx->Target != FX_TARGET_SELF) {
108 			fx->Projectile = pro;
109 			fxqueue->AddEffect( fx );
110 		} else {
111 			//Actor *target = (self->Type==ST_ACTOR)?(Actor *) self:NULL;
112 			fx->Projectile = 0;
113 			fx->PosX=pos.x;
114 			fx->PosY=pos.y;
115 			if (target) {
116 				//core->ApplyEffect(fx, target, self);
117 				selfqueue->AddEffect(fx);
118 			}
119 		}
120 	}
121 	if (target && selfqueue->GetEffectsCount()) {
122 		core->ApplyEffectQueue(selfqueue, target, self);
123 	}
124 	delete selfqueue;
125 
126 	//adding a pulse effect for weapons (PST)
127 	//if it is an equipping effect block
128 	if (usage == -1 && WieldColor != 0xffff && Flags & IE_ITEM_PULSATING) {
129 		Effect *tmp = BuildGlowEffect(WieldColor);
130 		if (tmp) {
131 			tmp->InventorySlot = invslot;
132 			tmp->Projectile=pro;
133 			fxqueue->AddEffect( tmp );
134 			delete tmp;
135 		}
136 	}
137 	return fxqueue;
138 }
139 
140 /** returns the average damage this weapon would cause */
141 // there might not be any target, so we can't consider also AltDiceThrown ...
GetDamagePotential(bool ranged,ITMExtHeader * & header) const142 int Item::GetDamagePotential(bool ranged, ITMExtHeader *&header) const
143 {
144 	header = GetWeaponHeader(ranged);
145 	if (header) {
146 		return header->DiceThrown*(header->DiceSides+1)/2+header->DamageBonus;
147 	}
148 	return -1;
149 }
150 
GetWeaponHeaderNumber(bool ranged) const151 int Item::GetWeaponHeaderNumber(bool ranged) const
152 {
153 	for(int ehc=0; ehc<ExtHeaderCount; ehc++) {
154 		ITMExtHeader *ext_header = GetExtHeader(ehc);
155 		if (ext_header->Location!=ITEM_LOC_WEAPON) {
156 			continue;
157 		}
158 		unsigned char AType = ext_header->AttackType;
159 		if (ranged) {
160 			if ((AType!=ITEM_AT_PROJECTILE) && (AType!=ITEM_AT_BOW) ) {
161 				continue;
162 			}
163 		} else {
164 			if (AType!=ITEM_AT_MELEE) {
165 				continue;
166 			}
167 		}
168 		return ehc;
169 	}
170 	return 0xffff; //invalid extheader number
171 }
172 
GetEquipmentHeaderNumber(int cnt) const173 int Item::GetEquipmentHeaderNumber(int cnt) const
174 {
175 	for(int ehc=0; ehc<ExtHeaderCount; ehc++) {
176 		ITMExtHeader *ext_header = GetExtHeader(ehc);
177 		if (ext_header->Location!=ITEM_LOC_EQUIPMENT) {
178 			continue;
179 		}
180 		if (ext_header->AttackType!=ITEM_AT_MAGIC) {
181 			continue;
182 		}
183 
184 		if (cnt) {
185 			cnt--;
186 			continue;
187 		}
188 		return ehc;
189 	}
190 	return 0xffff; //invalid extheader number
191 }
192 
GetWeaponHeader(bool ranged) const193 ITMExtHeader *Item::GetWeaponHeader(bool ranged) const
194 {
195 	//start from the beginning
196 	return GetExtHeader(GetWeaponHeaderNumber(ranged)) ;
197 }
198 
UseCharge(ieWord * Charges,int header,bool expend) const199 int Item::UseCharge(ieWord *Charges, int header, bool expend) const
200 {
201 	ITMExtHeader *ieh = GetExtHeader(header);
202 	if (!ieh) return 0;
203 	int type = ieh->ChargeDepletion;
204 
205 	int ccount = 0;
206 	if ((header>=CHARGE_COUNTERS) || (header<0) || MaxStackAmount) {
207 		header = 0;
208 	}
209 	ccount=Charges[header];
210 
211 	//if the item started from 0 charges, then it isn't depleting
212 	if (ieh->Charges==0) {
213 		return CHG_NONE;
214 	}
215 	if (expend) {
216 		Charges[header] = --ccount;
217 	}
218 
219 	if (ccount>0) {
220 		return CHG_NONE;
221 	}
222 	if (type == CHG_NONE) {
223 		Charges[header]=0;
224 	}
225 	return type;
226 }
227 
228 //returns a projectile loaded with the effect queue
GetProjectile(Scriptable * self,int header,const Point & target,ieDwordSigned invslot,int miss) const229 Projectile *Item::GetProjectile(Scriptable *self, int header, const Point &target, ieDwordSigned invslot, int miss) const
230 {
231 	ITMExtHeader *eh = GetExtHeader(header);
232 	if (!eh) {
233 		return NULL;
234 	}
235 	ieDword idx = eh->ProjectileAnimation;
236 	Projectile *pro = core->GetProjectileServer()->GetProjectileByIndex(idx);
237 	int usage ;
238 	if (header>= 0)
239 		usage = header;
240 	else
241 		usage = GetWeaponHeaderNumber(header==-2);
242 	if (!miss) {
243 		EffectQueue *fx = GetEffectBlock(self, target, usage, invslot, idx);
244 		pro->SetEffects(fx);
245 	}
246 	pro->Range = eh->Range;
247 	return pro;
248 }
249 
250 //this is the implementation of the weapon glow effect in PST
251 static EffectRef glow_ref = { "Color:PulseRGB", -1 };
252 
BuildGlowEffect(int gradient) const253 Effect *Item::BuildGlowEffect(int gradient) const
254 {
255 	//this type of colour uses PAL32, a PST specific palette
256 	//palette entry to to RGB conversion
257 	const auto& pal32 = core->GetPalette32( gradient );
258 	ieDword rgb = (pal32[16].r<<16) | (pal32[16].g<<8) | pal32[16].b;
259 	ieDword location = 0;
260 	ieDword speed = 128;
261 	Effect *fx = EffectQueue::CreateEffect(glow_ref, rgb, location|(speed<<16), FX_DURATION_INSTANT_WHILE_EQUIPPED);
262 	return fx;
263 }
264 
GetCastingDistance(int idx) const265 unsigned int Item::GetCastingDistance(int idx) const
266 {
267 	ITMExtHeader *seh = GetExtHeader(idx);
268 	if (!seh) {
269 		Log(ERROR, "Item", "Cannot retrieve item header!!! required header: %d, maximum: %d",
270 			idx, (int) ExtHeaderCount);
271 		return 0;
272 	}
273 	return (unsigned int) seh->Range;
274 }
275 
276 static EffectRef fx_damage_ref = { "Damage", -1 };
277 // returns a vector with details about any extended headers containing fx_damage
GetDamageOpcodesDetails(const ITMExtHeader * header) const278 std::vector<DMGOpcodeInfo> Item::GetDamageOpcodesDetails(const ITMExtHeader *header) const
279 {
280 	ieDword damage_opcode = EffectQueue::ResolveEffect(fx_damage_ref);
281 	std::multimap<ieDword, DamageInfoStruct>::iterator it;
282 	std::vector<DMGOpcodeInfo> damage_opcodes;
283 	if (!header) return damage_opcodes;
284 	for (int i=0; i< header->FeatureCount; i++) {
285 		Effect *fx = header->features+i;
286 		if (fx->Opcode == damage_opcode) {
287 			// damagetype is the same as in dmgtype.ids but GemRB uses those values
288 			// shifted by two bytes
289 			// 0-3 -> 0 (crushing)
290 			// 2^16+[0-3] -> 1 (acid)
291 			// 2^17+[0-3] -> 2 (cold)
292 			// 2^18+[0-3] -> 4 (electricity)
293 			// and so on. Should be fine up until DAMAGE_MAGICFIRE, where we may start making wrong lookups
294 			ieDword damagetype = fx->Parameter2 >> 16;
295 			it = core->DamageInfoMap.find(damagetype);
296 			if (it == core->DamageInfoMap.end()) {
297 				Log(ERROR, "Combat", "Unhandled damagetype: %u", damagetype);
298 				continue;
299 			}
300 			DMGOpcodeInfo damage;
301 			// it's lower case instead of title case, but let's see how long it takes for anyone to notice - 26.12.2012
302 			damage.TypeName = core->GetCString(it->second.strref, 0);
303 			damage.DiceThrown = fx->DiceThrown;
304 			damage.DiceSides = fx->DiceSides;
305 			damage.DiceBonus = fx->Parameter1;
306 			damage.Chance = fx->ProbabilityRangeMax - fx->ProbabilityRangeMin;
307 			damage_opcodes.push_back(damage);
308 		}
309 	}
310 	return damage_opcodes;
311 }
312 
313 
314 }
315