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