1 /*
2  *
3  *  Iter Vehemens ad Necem (IVAN)
4  *  Copyright (C) Timo Kiviluoto
5  *  Released under the GNU General
6  *  Public License
7  *
8  *  See LICENSING which should be included
9  *  along with this file for more details
10  *
11  */
12 
13 /* Compiled through itemset.cpp */
14 
15 #include "dbgmsgproj.h"
16 
17 cchar* ToHitValueDescription[] =
18 {
19   "unbelievably inaccurate",
20   "extremely inaccurate",
21   "inaccurate",
22   "decently accurate",
23   "accurate",
24   "highly accurate",
25   "extremely accurate",
26   "unbelievably accurate"
27 };
28 cchar* StrengthValueDescription[] =
29 {
30   "fragile",
31   "rather sturdy",
32   "sturdy",
33   "strong",
34   "very strong",
35   "extremely strong",
36   "almost unbreakable"
37 };
38 
itemprototype(const itemprototype * Base,itemspawner Spawner,itemcloner Cloner,cchar * ClassID)39 itemprototype::itemprototype(const itemprototype* Base, itemspawner Spawner, itemcloner Cloner, cchar* ClassID)
40 : Base(Base), Spawner(Spawner), Cloner(Cloner), ClassID(ClassID) { Index = protocontainer<item>::Add(this); }
41 
AllowRandomInstantiation() const42 truth itemdatabase::AllowRandomInstantiation() const { return !(Config & S_LOCK_ID); }
43 
item()44 item::item() : Slot(0), CloneMotherID(0), Fluid(0), LifeExpectancy(0), ItemFlags(0), iRotateFlyingThrownStep(0) { }
IsOnGround() const45 truth item::IsOnGround() const { return Slot[0]->IsOnGround(); }
IsSimilarTo(item * Item) const46 truth item::IsSimilarTo(item* Item) const { return Item->GetType() == GetType() && Item->GetConfig() == GetConfig(); }
GetBaseDamage() const47 double item::GetBaseDamage() const { return Max(0., sqrt(5e-5 * GetWeaponStrength()) + GetDamageBonus()); }
GetBaseMinDamage() const48 int item::GetBaseMinDamage() const { return int(GetBaseDamage() * 0.75); }
GetBaseMaxDamage() const49 int item::GetBaseMaxDamage() const { return int(GetBaseDamage() * 1.25) + 1; }
GetBaseToHitValue() const50 int item::GetBaseToHitValue() const { return int(10000. / (1000 + GetWeight()) + GetTHVBonus()); }
GetBaseBlockValue() const51 int item::GetBaseBlockValue() const { return int((10000. / (1000 + GetWeight()) + GetTHVBonus()) * GetBlockModifier() / 10000.); }
IsInCorrectSlot(int I) const52 truth item::IsInCorrectSlot(int I) const { return I == RIGHT_WIELDED_INDEX || I == LEFT_WIELDED_INDEX; }
IsInCorrectSlot() const53 truth item::IsInCorrectSlot() const { return IsInCorrectSlot(static_cast<gearslot*>(*Slot)->GetEquipmentIndex()); }
GetEquipmentIndex() const54 int item::GetEquipmentIndex() const { return static_cast<gearslot*>(*Slot)->GetEquipmentIndex(); }
GetGraphicsContainerIndex() const55 int item::GetGraphicsContainerIndex() const { return GR_ITEM; }
IsBroken() const56 truth item::IsBroken() const { return GetConfig() & BROKEN; }
IsFood() const57 truth item::IsFood() const { return DataBase->Category & FOOD; }
GetBreakVerb() const58 cchar* item::GetBreakVerb() const { return "breaks"; }
GetSquareUnderEntity(int I) const59 square* item::GetSquareUnderEntity(int I) const { return GetSquareUnder(I); }
GetSquareUnder(int I) const60 square* item::GetSquareUnder(int I) const { return Slot[I] ? Slot[I]->GetSquareUnder() : 0; }
GetLSquareUnder(int I) const61 lsquare* item::GetLSquareUnder(int I) const { return static_cast<lsquare*>(Slot[I]->GetSquareUnder()); }
SignalStackAdd(stackslot * StackSlot,void (stack::*)(item *,truth))62 void item::SignalStackAdd(stackslot* StackSlot, void (stack::*)(item*, truth)) { Slot[0] = StackSlot; }
IsAnimated() const63 truth item::IsAnimated() const { return GraphicData.AnimationFrames > 1 || (Fluid && ShowFluids()) || (IsBurning()); }
IsRusted() const64 truth item::IsRusted() const { return MainMaterial->GetRustLevel() != NOT_RUSTED; }
IsBurnt() const65 truth item::IsBurnt() const { return MainMaterial->GetBurnLevel() != NOT_BURNT; }
IsEatable(ccharacter * Eater) const66 truth item::IsEatable(ccharacter* Eater) const { return GetConsumeMaterial(Eater, &material::IsSolid) && IsConsumable() && !IsBurning(); }
IsDrinkable(ccharacter * Eater) const67 truth item::IsDrinkable(ccharacter* Eater) const { return GetConsumeMaterial(Eater, &material::IsLiquid) && IsConsumable() && !IsBurning(); }
IsValidRecipeIngredient(ccharacter *) const68 truth item::IsValidRecipeIngredient(ccharacter*) const { return ValidRecipeIngredient; }
GetFluidPixelAllowedPredicate() const69 pixelpredicate item::GetFluidPixelAllowedPredicate() const { return &rawbitmap::IsTransparent; }
Cannibalize()70 void item::Cannibalize() { Flags |= CANNIBALIZED; }
SetMainMaterial(material * NewMaterial,int SpecialFlags)71 material* item::SetMainMaterial(material* NewMaterial, int SpecialFlags)
72 { return SetMaterial(MainMaterial, NewMaterial, GetDefaultMainVolume(), SpecialFlags); }
InitMaterials(const materialscript * M,const materialscript *,truth CUP)73 void item::InitMaterials(const materialscript* M, const materialscript*, truth CUP)
74 { InitMaterials(M->Instantiate(), CUP); }
GetMainMaterialRustLevel() const75 int item::GetMainMaterialRustLevel() const { return MainMaterial->GetRustLevel(); }
76 
item(citem & Item)77 item::item(citem& Item)
78 : object(Item), Slot(0), Size(Item.Size), DataBase(Item.DataBase), Volume(Item.Volume), Weight(Item.Weight),
79   iRotateFlyingThrownStep(Item.iRotateFlyingThrownStep),
80   Fluid(0), SquaresUnder(Item.SquaresUnder), LifeExpectancy(Item.LifeExpectancy), ItemFlags(Item.ItemFlags)
81 {
82   Flags &= ENTITY_FLAGS|SQUARE_POSITION_BITS;
83   ID = game::CreateNewItemID(this);
84 
85   CloneMotherID = new idholder(Item.ID);
86   idholder* TI = CloneMotherID;
87   for(idholder* II = Item.CloneMotherID; II; II = II->Next)
88     TI = TI->Next = new idholder(II->ID);
89   TI->Next = 0;
90 
91   Slot = new slot*[SquaresUnder];
92   for(int c = 0; c < SquaresUnder; ++c)
93     Slot[c] = 0;
94 }
95 
~item()96 item::~item()
97 {
98   delete [] Slot;
99   game::RemoveItemID(ID);
100 
101   fluid** FP = Fluid;
102 
103   if(FP)
104   {
105     for(int c = 0; c < SquaresUnder; ++c)
106       for(fluid* F = FP[c]; F;)
107       {
108         fluid* ToDel = F;
109         F = F->Next;
110         delete ToDel;
111       }
112 
113     delete [] FP;
114   }
115 
116   for(idholder* I = CloneMotherID; I;)
117   {
118     idholder* ToDel = I;
119     I = I->Next;
120     delete ToDel;
121   }
122 }
123 
Fly(character * Thrower,int Direction,int Force,bool bTryStartThrownRotation)124 void item::Fly(character* Thrower, int Direction, int Force, bool bTryStartThrownRotation)
125 {
126   if(ivanconfig::GetRotateTimesPerSquare() > 0)
127   {
128     iRotateFlyingThrownStep=0; //simple granted reset
129   }
130   lsquare* LandingSquare=NULL;
131 
132   int Range = Force * 25 / Max(long(sqrt(GetWeight())), 1L);
133 
134   lsquare* LSquareUnder = GetLSquareUnder();
135   RemoveFromSlot();
136   LSquareUnder->GetStack()->AddItem(this, false);
137   LandingSquare=LSquareUnder;
138 
139   if(!Range || GetSquaresUnder() != 1)
140   {
141     if(GetLSquareUnder()->GetRoom())
142       GetLSquareUnder()->GetRoom()->AddItemEffect(this);
143 
144     return;
145   }
146 
147   if(Direction == RANDOM_DIR)
148     Direction = RAND() & 7;
149 
150   v2 StartingPos = GetPos();
151   v2 Pos = StartingPos;
152   v2 DirVector = game::GetMoveVector(Direction);
153   truth Breaks = false;
154   double BaseDamage, BaseToHitValue;
155 
156   /*** check ***/
157   int iRotateTimes = ivanconfig::GetRotateTimesPerSquare();
158 
159   if(Thrower)
160   {
161     int Bonus = Thrower->IsHumanoid() ? Thrower->GetCWeaponSkill(GetWeaponCategory())->GetBonus() : 1000;
162     BaseDamage = sqrt(5e-12 * GetWeaponStrength() * Force / Range) * Bonus;
163     BaseToHitValue = 10 * Bonus * Thrower->GetMoveEase()
164                      / (500 + GetWeight()) * Thrower->GetAttribute(DEXTERITY)
165                      * sqrt(2.5e-8 * Thrower->GetAttribute(PERCEPTION)) / Range;
166 
167     if(bTryStartThrownRotation && ivanconfig::GetRotateTimesPerSquare()>0)
168       iRotateFlyingThrownStep = (clock()%2)==0 ? 1 : -1; //init rotation
169 
170     if(!Thrower->IsPlayer() && iRotateTimes>1)
171       iRotateTimes=1; //disable "dramatic" rotations from NPCs but still keep minimum if enabled
172   }
173   else
174   {
175     BaseDamage = sqrt(5e-6 * GetWeaponStrength() * Force / Range);
176     BaseToHitValue = 10 * 100 / (500 + GetWeight()) / Range;
177     iRotateTimes=0;
178   }
179 
180   int RangeLeft;
181 
182   truth Draw=false;
183   float fFlyDelay = 0.03;
184   bool bLowerRotationsPerSqr = iRotateTimes==5;
185   for(RangeLeft = Range; RangeLeft; --RangeLeft)
186   {
187     if(!GetLevel()->IsValidPos(Pos + DirVector))
188       break;
189 
190     lsquare* JustHit = GetNearLSquare(Pos + DirVector);
191 
192     if(!JustHit->IsFlyable())
193     {
194       Breaks = true;
195       JustHit->GetOLTerrain()->HasBeenHitByItem(Thrower, this, int(BaseDamage * sqrt(RangeLeft)));
196       break;
197     }
198     else
199     {
200       clock_t StartTime = clock();
201       Pos += DirVector;
202       RemoveFromSlot();
203       JustHit->GetStack()->AddItem(this, false);
204       LandingSquare=JustHit;
205       Draw = game::OnScreen(JustHit->GetPos()) && JustHit->CanBeSeenByPlayer();
206 
207       if(Draw)
208         game::DrawEverything();
209 
210       if(JustHit->GetCharacter())
211       {
212         int Damage = int(BaseDamage * sqrt(RangeLeft));
213         double ToHitValue = BaseToHitValue * RangeLeft;
214         int Returned = HitCharacter(Thrower, JustHit->GetCharacter(), Damage, ToHitValue, Direction);
215 
216         if(Returned == HIT)
217           Breaks = true;
218 
219         if(Returned != MISSED)
220           break;
221       }
222 
223       if(Draw)
224         while(clock() - StartTime < fFlyDelay * CLOCKS_PER_SEC);
225 
226       if(ivanconfig::GetRotateTimesPerSquare()>0 && iRotateFlyingThrownStep!=0){
227         if(iRotateTimes==1){
228           iRotateFlyingThrownStep += iRotateFlyingThrownStep>0 ? 1 : -1; //next rotation step on next square
229         }else{ //if rotation steps is >= 2 rotate at least one more time on the same square
230           for(int i=0;i<(iRotateTimes-1);i++){
231             iRotateFlyingThrownStep += iRotateFlyingThrownStep>0 ? 1 : -1;
232             if(Draw){
233               StartTime = clock();
234               RemoveFromSlot();JustHit->GetStack()->AddItem(this, false); //TODO find a better way then remove and re-add to same square to redraw...
235               game::DrawEverything();
236               if(bLowerRotationsPerSqr){
237                 iRotateTimes--;
238                 if(iRotateTimes<1)iRotateTimes=1;
239               }
240               //while(clock() - StartTime < fFlyDelay * CLOCKS_PER_SEC);
241             }
242           }
243         }
244       }
245 
246     }
247 
248   }
249 
250   if(ivanconfig::GetRotateTimesPerSquare()>0 && iRotateFlyingThrownStep!=0){ //must be disabled before exiting Fly()
251     iRotateFlyingThrownStep=4; //default rotation is w/o the rotation flags at the switch(){}
252     //force redraw at default rotation to avoid another spin when player moves TODO how to let it stay in the last rotation?
253     RemoveFromSlot();LandingSquare->GetStack()->AddItem(this, false); //TODO find a better way then remove and re-add to same square...
254     game::DrawEverything();
255     iRotateFlyingThrownStep=0; //disables rotation
256   }
257 
258   if(Breaks)
259     ReceiveDamage(Thrower, int(sqrt(GetWeight() * RangeLeft) / 10), THROW|PHYSICAL_DAMAGE, Direction);
260 
261   if(Exists() && GetLSquareUnder()->GetRoom())
262     GetLSquareUnder()->GetRoom()->AddItemEffect(this);
263 }
264 
HitCharacter(character * Thrower,character * Dude,int Damage,double ToHitValue,int Direction)265 int item::HitCharacter(character* Thrower, character* Dude, int Damage, double ToHitValue, int Direction)
266 {
267   if(Dude->Catches(this))
268     return CATCHED;
269 
270   if(Thrower && !EffectIsGood())
271     Thrower->Hostility(Dude);
272 
273   if(Dude->DodgesFlyingItem(this, ToHitValue))
274   {
275     if(Dude->IsPlayer())
276       ADD_MESSAGE("%s misses you.", CHAR_NAME(DEFINITE));
277     else if(Dude->CanBeSeenByPlayer())
278       ADD_MESSAGE("%s misses %s.", CHAR_NAME(DEFINITE), Dude->CHAR_NAME(DEFINITE));
279 
280     return MISSED;
281   }
282 
283   Dude->HasBeenHitByItem(Thrower, this, Damage, ToHitValue, Direction);
284   return HIT;
285 }
286 
GetWeaponStrength() const287 double item::GetWeaponStrength() const
288 {
289   return GetFormModifier() * GetMainMaterial()->GetStrengthValue() * sqrt(GetMainMaterial()->GetWeight());
290 }
291 
GetStrengthRequirement() const292 int item::GetStrengthRequirement() const
293 {
294   double WeightTimesSize = GetWeight() * GetSize();
295   return int(1.25e-10 * WeightTimesSize * WeightTimesSize);
296 }
297 
Apply(character * Applier)298 truth item::Apply(character* Applier)
299 {
300   if(Applier->IsPlayer())
301     ADD_MESSAGE("You can't apply this!");
302 
303   return false;
304 }
305 
306 /* Returns truth that tells whether the Polymorph really happened */
307 
Polymorph(character * Polymorpher,stack * CurrentStack)308 truth item::Polymorph(character* Polymorpher, stack* CurrentStack)
309 {
310   if(!IsPolymorphable())
311     return false;
312   else
313   {
314     if(Polymorpher && IsOnGround())
315     {
316       room* Room = GetRoom();
317 
318       if(Room)
319         Room->HostileAction(Polymorpher);
320     }
321 
322     if(GetSquarePosition() != CENTER)
323     {
324       stack* Stack = CurrentStack->GetLSquareUnder()->GetStackOfAdjacentSquare(GetSquarePosition());
325 
326       if(Stack)
327         CurrentStack = Stack;
328     }
329 
330     CurrentStack->AddItem(protosystem::BalancedCreateItem(0, MAX_PRICE, ANY_CATEGORY, 0, 0, 0, true));
331     RemoveFromSlot();
332     SendToHell();
333     return true;
334   }
335 }
336 
Polymorph(character * Polymorpher,character * Wielder)337 truth item::Polymorph(character* Polymorpher, character* Wielder)
338 {
339   if(!IsPolymorphable())
340     return false;
341   else if(!Wielder->Equips(this))
342     return false;
343   else
344   {
345     if(Polymorpher && Wielder)
346     {
347       Polymorpher->Hostility(Wielder);
348     }
349 
350     item* NewItem = protosystem::BalancedCreateItem(0, MAX_PRICE, ANY_CATEGORY, 0, 0, 0, true);
351     int EquipSlot = GetEquipmentIndex();
352 
353     if(Wielder->IsPlayer())
354       ADD_MESSAGE("Your %s polymorphs into %s.", CHAR_NAME(UNARTICLED), NewItem->CHAR_NAME(INDEFINITE));
355     else if(CanBeSeenByPlayer())
356       ADD_MESSAGE("%s's %s polymorphs into %s.", Wielder->CHAR_NAME(DEFINITE), CHAR_NAME(UNARTICLED), NewItem->CHAR_NAME(INDEFINITE));
357 
358     RemoveFromSlot();
359     SendToHell();
360 
361     switch (EquipSlot)
362     {
363       /*
364       case HELMET_INDEX: Wielder->SetHelmet(NewItem); break;
365       case AMULET_INDEX: Wielder->SetAmulet(NewItem); break;
366       case CLOAK_INDEX: Wielder->SetCloak(NewItem); break;
367       case BODY_ARMOR_INDEX: Wielder->SetBodyArmor(NewItem); break;
368       case BELT_INDEX: Wielder->SetBelt(NewItem); break;
369       */
370       case RIGHT_WIELDED_INDEX: Wielder->SetRightWielded(NewItem); break;
371       case LEFT_WIELDED_INDEX: Wielder->SetLeftWielded(NewItem); break;
372       /*
373       case RIGHT_RING_INDEX: Wielder->SetRightRing(NewItem); break;
374       case LEFT_RING_INDEX: Wielder->SetLeftRing(NewItem); break;
375       case RIGHT_GAUNTLET_INDEX: Wielder->SetRightGauntlet(NewItem); break;
376       case LEFT_GAUNTLET_INDEX: Wielder->SetLeftGauntlet(NewItem); break;
377       case RIGHT_BOOT_INDEX: Wielder->SetRightBoot(NewItem); break;
378       case LEFT_BOOT_INDEX: Wielder->SetLeftBoot(NewItem); break;
379       */
380       default: Wielder->ReceiveItemAsPresent(NewItem); break;
381     }
382 
383     if(Wielder->IsPlayer())
384       game::AskForKeyPress(CONST_S("Equipment polymorphed! [press any key to continue]"));
385     return true;
386   }
387 }
388 
389 /* Returns truth that tells whether the alchemical conversion really happened. */
390 
Alchemize(character * Midas,stack * CurrentStack)391 truth item::Alchemize(character* Midas, stack* CurrentStack)
392 {
393   if(IsQuestItem())
394     return false;
395   else
396   {
397     if(Midas && IsOnGround())
398     {
399       room* Room = GetRoom();
400 
401       if(Room)
402         Room->HostileAction(Midas);
403     }
404 
405     long Price = GetTruePrice();
406 
407     if(Price)
408     {
409       Price /= 4; /* slightly lower than with 10 Cha */
410       ADD_MESSAGE("Gold pieces clatter on the floor.");
411       Midas->SetMoney(Midas->GetMoney() + Price);
412     }
413 
414     RemoveFromSlot();
415     SendToHell();
416     return true;
417   }
418 }
419 
SoftenMaterial()420 truth item::SoftenMaterial()
421 {
422   if(!IsMaterialChangeable() || !CanBeSoftened())
423   {
424     return false;
425   }
426 
427   int Config = GetMainMaterial()->GetSoftenedMaterial(this);
428 
429   if(!Config)
430   {
431     /* Should not be possible. */
432     return false;
433   }
434 
435   msgsystem::EnterBigMessageMode();
436 
437   if(CanBeSeenByPlayer())
438     ADD_MESSAGE("Suddenly %s starts glowing dull yellow.", CHAR_NAME(INDEFINITE));
439 
440   material* TempMaterial = MAKE_MATERIAL(Config);
441   material* MainMaterial = GetMainMaterial();
442   material* SecondaryMaterial = GetSecondaryMaterial();
443 
444   if(SecondaryMaterial && SecondaryMaterial->IsSameAs(MainMaterial))
445     delete SetSecondaryMaterial(TempMaterial->SpawnMore());
446 
447   delete SetMainMaterial(TempMaterial);
448 
449   if(CanBeSeenByPlayer())
450     ADD_MESSAGE("It softens into %s!", GetMainMaterial()->GetName(false, false).CStr());
451 
452   msgsystem::LeaveBigMessageMode();
453   return true;
454 }
455 
456 /* Returns whether the Eater must stop eating the item */
457 
Consume(character * Eater,long Amount)458 truth item::Consume(character* Eater, long Amount)
459 {
460   material* ConsumeMaterial = GetConsumeMaterial(Eater);
461 
462   if(!ConsumeMaterial)
463     return true;
464 
465   if(Eater->IsPlayer() && !(Flags & CANNIBALIZED) && Eater->CheckCannibalism(ConsumeMaterial))
466   {
467     game::DoEvilDeed(25);
468     ADD_MESSAGE("You feel that this was an evil deed.");
469     Cannibalize();
470   }
471 
472   ulong ID = GetID();
473   material* Garbage = ConsumeMaterial->EatEffect(Eater, Amount);
474   item* NewConsuming = GetID() ? this : game::SearchItem(ID);
475   material* NewConsumeMaterial = NewConsuming->GetConsumeMaterial(Eater);
476 
477   if(!NewConsuming->Exists()
478      || !NewConsumeMaterial
479      || !NewConsumeMaterial->IsSameAs(ConsumeMaterial))
480     ConsumeMaterial->FinishConsuming(Eater);
481 
482   delete Garbage;
483   return !NewConsuming->Exists() || !NewConsumeMaterial;
484 }
485 
CanBeEatenByAI(ccharacter * Eater) const486 truth item::CanBeEatenByAI(ccharacter* Eater) const
487 {
488   material* ConsumeMaterial = GetConsumeMaterial(Eater);
489 
490   return (!Eater->IsPet()
491           || !(Eater->GetCommandFlags() & DONT_CONSUME_ANYTHING_VALUABLE)
492           || !IsValuable())
493     && IsConsumable()
494     && ConsumeMaterial && ConsumeMaterial->CanBeEatenByAI(Eater);
495 }
496 
HasTag(char tag)497 bool item::HasTag(char tag)
498 {
499   static char Tag[3]={'#',0,0};
500   Tag[1]=tag;
501   return label.Find(Tag,0) != festring::NPos;
502 }
503 
504 /**
505  * look for all usages to avoid tag clashes
506  */
SetTag(char tag)507 void item::SetTag(char tag)
508 {
509   if(!HasTag(tag))
510     label<<"#"<<tag;
511 }
512 
ClearTag(char tag)513 void item::ClearTag(char tag)
514 {
515   static char Tag[3]={'#',0,0};
516   Tag[1]=tag;
517   int pos = label.Find(Tag,0);
518   if(pos != festring::NPos)
519     label.Erase(pos,2);
520 }
521 
SetLabel(cfestring & What)522 void item::SetLabel(cfestring& What)
523 {
524   label.Empty();
525   if(!What.IsEmpty())
526     label << What;
527 }
528 
AddName(festring & Name,int Case) const529 void item::AddName(festring& Name, int Case) const
530 {
531   object::AddName(Name,Case);
532 
533   if(label.GetSize())
534     Name << " inscribed " << label;
535 }
536 
Save(outputfile & SaveFile) const537 void item::Save(outputfile& SaveFile) const
538 {
539   SaveFile << static_cast<ushort>(GetType());
540   object::Save(SaveFile);
541   SaveFile << static_cast<ushort>(GetConfig());
542   SaveFile << static_cast<ushort>(Flags);
543   SaveFile << Size << ID << LifeExpectancy << ItemFlags;
544   SaveFile << label;
545   SaveLinkedList(SaveFile, CloneMotherID);
546 
547   if(Fluid)
548   {
549     SaveFile.Put(true);
550 
551     for(int c = 0; c < SquaresUnder; ++c)
552       SaveLinkedList(SaveFile, Fluid[c]);
553   }
554   else
555     SaveFile.Put(false);
556 }
557 
Load(inputfile & SaveFile)558 void item::Load(inputfile& SaveFile)
559 {
560   object::Load(SaveFile);
561   databasecreator<item>::InstallDataBase(this, ReadType<ushort>(SaveFile));
562   Flags |= ReadType<ushort>(SaveFile) & ~ENTITY_FLAGS;
563   SaveFile >> Size >> ID >> LifeExpectancy >> ItemFlags;
564   if(game::GetCurrentSavefileVersion()>=132)
565     SaveFile >> label;
566   LoadLinkedList(SaveFile, CloneMotherID);
567 
568   if(LifeExpectancy)
569     Enable();
570 
571   game::AddItemID(this, ID);
572 
573   if(SaveFile.Get())
574   {
575     Fluid = new fluid*[SquaresUnder];
576 
577     for(int c = 0; c < SquaresUnder; ++c)
578     {
579       LoadLinkedList(SaveFile, Fluid[c]);
580 
581       for(fluid* F = Fluid[c]; F; F = F->Next)
582         F->SetMotherItem(this);
583     }
584   }
585 }
586 
TeleportRandomly()587 void item::TeleportRandomly()
588 {
589   if(GetSquaresUnder() == 1) // gum solution
590   {
591     lsquare* Square = GetNearLSquare(GetLevel()->GetRandomSquare());
592     MoveTo(Square->GetStack());
593 
594     if(Square->CanBeSeenByPlayer())
595       ADD_MESSAGE("Suddenly %s appears!", CHAR_NAME(INDEFINITE));
596   }
597 }
598 
GetStrengthValue() const599 int item::GetStrengthValue() const
600 {
601   return long(GetStrengthModifier()) * GetMainMaterial()->GetStrengthValue() / 2000;
602 }
603 
RemoveFromSlot()604 void item::RemoveFromSlot()
605 {
606   for(int c = 0; c < SquaresUnder; ++c)
607     if(Slot[c])
608     {
609       try
610       {
611         Slot[c]->Empty();
612       }
613       catch(quitrequest)
614       {
615         SendToHell();
616         throw;
617       }
618 
619       Slot[c] = 0;
620     }
621 }
622 
MoveTo(stack * Stack)623 void item::MoveTo(stack* Stack)
624 {
625   RemoveFromSlot();
626   Stack->AddItem(this);
627 }
628 
GetItemCategoryName(long Category)629 cchar* item::GetItemCategoryName(long Category) // convert to array
630 {
631   switch(Category)
632   {
633    case HELMET: return "Helmets";
634    case AMULET: return "Amulets";
635    case CLOAK: return "Cloaks";
636    case BODY_ARMOR: return "Body armors";
637    case WEAPON: return "Weapons";
638    case SHIELD: return "Shields";
639    case RING: return "Rings";
640    case GAUNTLET: return "Gauntlets";
641    case BELT: return "Belts";
642    case BOOT: return "Boots";
643    case FOOD: return "Food";
644    case POTION: return "Potions";
645    case SCROLL: return "Scrolls";
646    case BOOK: return "Books";
647    case WAND: return "Wands";
648    case TOOL: return "Tools";
649    case VALUABLE: return "Valuables";
650    case MISC: return "Miscellaneous items";
651   }
652 
653   return "Warezzz";
654 }
655 
GetResistance(int Type) const656 int item::GetResistance(int Type) const
657 {
658   switch(Type&0xFFF)
659   {
660    case PHYSICAL_DAMAGE: return GetStrengthValue();
661    case ENERGY: return GetEnergyResistance();
662    case DRAIN:
663    case PSI:
664    case MUSTARD_GAS_DAMAGE:
665     return 0;
666    case FIRE: return GetFireResistance();
667    case POISON: return GetPoisonResistance();
668    case ELECTRICITY: return GetElectricityResistance();
669    case ACID: return GetAcidResistance();
670    case SOUND: return GetSoundResistance();
671   }
672 
673   ABORT("Resistance lack detected!");
674   return 0;
675 }
676 
Open(character * Char)677 truth item::Open(character* Char)
678 {
679   if(Char->IsPlayer())
680     ADD_MESSAGE("You can't open %s.", CHAR_NAME(DEFINITE));
681 
682   return false;
683 }
684 
SpawnAndLoad(inputfile & SaveFile) const685 item* itemprototype::SpawnAndLoad(inputfile& SaveFile) const
686 {
687   item* Item = Spawner(0, LOAD);
688   Item->Load(SaveFile);
689   Item->CalculateAll();
690   return Item;
691 }
692 
LoadDataBaseStats()693 void item::LoadDataBaseStats()
694 {
695   SetSize(GetDefaultSize());
696 }
697 
Initialize(int NewConfig,int SpecialFlags)698 void item::Initialize(int NewConfig, int SpecialFlags)
699 {
700   CalculateSquaresUnder();
701   Slot = new slot*[SquaresUnder];
702 
703   for(int c = 0; c < SquaresUnder; ++c)
704     Slot[c] = 0;
705 
706   if(!(SpecialFlags & LOAD))
707   {
708     ID = game::CreateNewItemID(this);
709     databasecreator<item>::InstallDataBase(this, NewConfig);
710     LoadDataBaseStats();
711     RandomizeVisualEffects();
712     Flags |= CENTER << SQUARE_POSITION_SHIFT;
713 
714     if(!(SpecialFlags & NO_MATERIALS))
715       GenerateMaterials();
716   }
717 
718   if(!(SpecialFlags & LOAD))
719     PostConstruct();
720 
721   if(!(SpecialFlags & (LOAD|NO_MATERIALS)))
722   {
723     CalculateAll();
724 
725     if(!(SpecialFlags & NO_PIC_UPDATE))
726       UpdatePictures();
727   }
728 }
729 
ShowMaterial() const730 truth item::ShowMaterial() const
731 {
732   if(GetMainMaterialConfig().Size == 1)
733     return GetMainMaterial()->GetConfig() != GetMainMaterialConfig()[0];
734   else
735     return true;
736 }
737 
GetBlockModifier() const738 long item::GetBlockModifier() const
739 {
740   if(!IsShield(0))
741     return GetSize() * GetRoundness() << 1;
742   else
743     return GetSize() * GetRoundness() << 2;
744 }
745 
CanBeSeenByPlayer() const746 truth item::CanBeSeenByPlayer() const
747 {
748   return CanBeSeenBy(PLAYER);
749 }
750 
CanBeSeenBy(ccharacter * Who) const751 truth item::CanBeSeenBy(ccharacter* Who) const
752 {
753   for(int c = 0; c < SquaresUnder; ++c)
754     if(Slot[c] && Slot[c]->CanBeSeenBy(Who))
755       return true;
756 
757   return Who->IsPlayer() && game::GetSeeWholeMapCheatMode();
758 }
759 
GetDescription(int Case) const760 festring item::GetDescription(int Case) const
761 {
762   if(CanBeSeenByPlayer())
763     return GetName(Case);
764   else
765     return CONST_S("something");
766 }
767 
SignalVolumeAndWeightChange()768 void item::SignalVolumeAndWeightChange()
769 {
770   CalculateVolumeAndWeight();
771 
772   for(int c = 0; c < SquaresUnder; ++c)
773     if(Slot[c])
774       Slot[c]->SignalVolumeAndWeightChange();
775 }
776 
CalculateVolumeAndWeight()777 void item::CalculateVolumeAndWeight()
778 {
779   Volume = Weight = 0;
780 
781   for(int c = 0; c < GetMaterials(); ++c)
782   {
783     cmaterial* Material = GetMaterial(c);
784 
785     if(Material)
786     {
787       Volume += Material->GetVolume();
788       Weight += Material->GetWeight();
789     }
790   }
791 }
792 
SignalEmitationIncrease(col24 EmitationUpdate)793 void item::SignalEmitationIncrease(col24 EmitationUpdate)
794 {
795   if(game::CompareLights(EmitationUpdate, Emitation) > 0)
796   {
797     game::CombineLights(Emitation, EmitationUpdate);
798 
799     for(int c = 0; c < SquaresUnder; ++c)
800       if(Slot[c])
801         Slot[c]->SignalEmitationIncrease(EmitationUpdate);
802   }
803 }
804 
SignalEmitationDecrease(col24 EmitationUpdate)805 void item::SignalEmitationDecrease(col24 EmitationUpdate)
806 {
807   if(game::CompareLights(EmitationUpdate, Emitation) >= 0 && Emitation)
808   {
809     col24 Backup = Emitation;
810     CalculateEmitation();
811 
812     if(Backup != Emitation)
813       for(int c = 0; c < SquaresUnder; ++c)
814         if(Slot[c])
815           Slot[c]->SignalEmitationDecrease(EmitationUpdate);
816   }
817 }
818 
CalculateAll()819 void item::CalculateAll()
820 {
821   CalculateVolumeAndWeight();
822   CalculateEmitation();
823 }
824 
825 /* Temporary and buggy. */
826 
WeaponSkillHit(int Hits)827 void item::WeaponSkillHit(int Hits)
828 {
829   if(Slot[0] && Slot[0]->IsGearSlot())
830     static_cast<arm*>(static_cast<gearslot*>(*Slot)->GetBodyPart())->WieldedSkillHit(Hits);
831 }
832 
833 /* Returns 0 if item cannot be cloned */
834 
Duplicate(ulong Flags)835 item* item::Duplicate(ulong Flags)
836 {
837   if(!(Flags & IGNORE_PROHIBITIONS)
838      && ((!(Flags & MIRROR_IMAGE) && !CanBeCloned())
839          || (Flags & MIRROR_IMAGE && (!CanBeMirrored()
840                                       || (MainMaterial
841                                           && !(MainMaterial->GetCommonFlags() & CAN_BE_MIRRORED))
842                                       || (GetSecondaryMaterial()
843                                           && !(GetSecondaryMaterial()->GetCommonFlags() & CAN_BE_MIRRORED))))))
844     return 0;
845 
846   item* Clone = GetProtoType()->Clone(this);
847 
848   if(Flags & MIRROR_IMAGE)
849     Clone->SetLifeExpectancy(Flags >> LE_BASE_SHIFT & LE_BASE_RANGE,
850                              Flags >> LE_RAND_SHIFT & LE_RAND_RANGE);
851 
852   Clone->UpdatePictures();
853   return Clone;
854 }
855 
AddInventoryEntry(ccharacter *,festring & Entry,int Amount,truth ShowSpecialInfo) const856 void item::AddInventoryEntry(ccharacter*, festring& Entry, int Amount, truth ShowSpecialInfo) const
857 {
858   if(Amount == 1)
859     AddName(Entry, INDEFINITE);
860   else
861   {
862     Entry << Amount << ' ';
863     AddName(Entry, PLURAL);
864   }
865 
866   if(ShowSpecialInfo){
867     Entry << " [" << GetWeight() * Amount << "g"; //TODO if the 1st and 2nd of 3 items have 100g and the last has 2000g, the weight shown would be 300g ... now that lumps, stones and sticks are useful, this may not be that good...
868     if(ivanconfig::IsShowVolume()){
869       Entry << " " << GetVolume() * Amount << "cm3"; //the item can be seen therefore it's volume guessed already
870       if(GetSecondaryMaterial()==NULL){ //simple items like ingots sticks etc
871         static char density[20];
872         sprintf(density, "%.1f", GetWeight()/(float)GetVolume());
873         Entry << " " << density << "g/cm3"; //the item can be seen and weighted already so this just helps avoiding having to mentally calc density for every item
874         if(game::WizardModeIsActive()) //TODO || Char-> possess item <materialmanual*>
875           Entry << " " << GetStrengthValue() << "str"; //this is special info tho.
876       }
877     }
878     Entry << "]";
879   }
880 }
881 
ChooseBaseForConfig(itemdatabase ** TempConfig,int Configs,int ConfigNumber)882 const itemdatabase* itemprototype::ChooseBaseForConfig(itemdatabase** TempConfig, int Configs, int ConfigNumber)
883 {
884   if(!(ConfigNumber & BROKEN))
885     return *TempConfig;
886   else
887   {
888     ConfigNumber ^= BROKEN;
889 
890     for(int c = 0; c < Configs; ++c)
891       if(TempConfig[c]->Config == ConfigNumber)
892         return TempConfig[c];
893 
894     return *TempConfig;
895   }
896 }
897 
ReceiveDamage(character * Damager,int Damage,int Type,int Dir)898 truth item::ReceiveDamage(character* Damager, int Damage, int Type, int Dir)
899 {
900   if(MainMaterial && IsDestroyable(Damager))
901   {
902     if(CanBeBurned() && (MainMaterial->GetInteractionFlags() & CAN_BURN) && !IsBurning() && Type & FIRE)
903     {
904       TestActivationEnergy(Damage);
905     }
906     else if(IsBurning() && Type & FIRE)
907       GetMainMaterial()->AddToThermalEnergy(Damage);
908   }
909 
910   if(CanBeBroken() && !IsBroken() && Type & (PHYSICAL_DAMAGE|SOUND|ENERGY|ACID))
911   {
912     int StrengthValue = GetStrengthValue();
913 
914     if(!StrengthValue)
915       StrengthValue = 1;
916 
917     if(Damage > StrengthValue << 2 && RAND() & 3 && RAND() % (25 * Damage / StrengthValue) >= 100)
918     {
919       Break(Damager, Dir);
920       return true;
921     }
922   }
923 
924   if(Type & ACID && IsBroken() && IsDestroyable(Damager))
925   {
926     int StrengthValue = GetStrengthValue();
927 
928     if(!StrengthValue)
929       StrengthValue = 1;
930 
931     if(Damage > StrengthValue << 4 && !(RAND() & 3) && RAND() % (100 * Damage / StrengthValue) >= 100)
932     {
933       Destroy(Damager, Dir);
934       return true;
935     }
936   }
937 
938   return false;
939 }
940 
InitDefaults(const itemprototype * NewProtoType,int NewConfig)941 void itemdatabase::InitDefaults(const itemprototype* NewProtoType, int NewConfig)
942 {
943   IsAbstract = false;
944   ProtoType = NewProtoType;
945   Config = NewConfig;
946 
947   if(NewConfig & BROKEN)
948   {
949     if(Adjective.GetSize())
950       Adjective.Insert(0, "broken ");
951     else
952       Adjective = CONST_S("broken");
953 
954     DefaultSize >>= 1;
955     FormModifier >>= 2;
956     StrengthModifier >>= 1;
957   }
958 }
959 
GetNutritionValue() const960 long item::GetNutritionValue() const
961 {
962   long NV = 0;
963 
964   for(int c = 0; c < GetMaterials(); ++c)
965     if(GetMaterial(c))
966       NV += GetMaterial(c)->GetTotalNutritionValue();
967 
968   return NV;
969 }
970 
SignalSpoil(material *)971 void item::SignalSpoil(material*)
972 {
973   if(!Exists())
974     return;
975 
976   if(CanBeSeenByPlayer())
977     ADD_MESSAGE("%s spoils completely.", GetExtendedDescription().CStr());
978 
979   truth Equipped = PLAYER->Equips(this);
980   Disappear();
981 
982   if(Equipped)
983     game::AskForKeyPress(CONST_S("Equipment destroyed! [press any key to continue]"));
984 }
985 
SignalBurn(material * Material)986 void item::SignalBurn(material* Material)
987 {
988   if(!Exists())
989     return;
990 
991   if(CanBeSeenByPlayer())
992     ADD_MESSAGE("%s burns away completely.", GetExtendedDescription().CStr());
993 
994   truth Equipped = PLAYER->Equips(this);
995   Disappear();
996 
997   if(Equipped)
998     game::AskForKeyPress(CONST_S("Equipment destroyed! [press any key to continue]"));
999 }
1000 
DuplicateToStack(stack * CurrentStack,ulong Flags)1001 item* item::DuplicateToStack(stack* CurrentStack, ulong Flags)
1002 {
1003   item* Duplicated = Duplicate(Flags);
1004 
1005   if(!Duplicated)
1006     return 0;
1007 
1008   CurrentStack->AddItem(Duplicated);
1009   return Duplicated;
1010 }
1011 
CanBePiledWith(citem * Item,ccharacter * Viewer) const1012 truth item::CanBePiledWith(citem* Item, ccharacter* Viewer) const
1013 {
1014   return (GetType() == Item->GetType()
1015           && GetConfig() == Item->GetConfig()
1016           && ItemFlags == Item->ItemFlags
1017           && ((!ivanconfig::IsAllWeightIsRelevant() && WeightIsIrrelevant()) || Weight == Item->Weight)
1018           && MainMaterial->IsSameAs(Item->MainMaterial)
1019           && MainMaterial->GetSpoilLevel() == Item->MainMaterial->GetSpoilLevel()
1020           && MainMaterial->GetRustLevel() == Item->MainMaterial->GetRustLevel()
1021           && MainMaterial->GetBurnLevel() == Item->MainMaterial->GetBurnLevel()
1022           && Viewer->GetCWeaponSkillLevel(this) == Viewer->GetCWeaponSkillLevel(Item)
1023           && Viewer->GetSWeaponSkillLevel(this) == Viewer->GetSWeaponSkillLevel(Item)
1024           && !Fluid && !Item->Fluid
1025           && !LifeExpectancy == !Item->LifeExpectancy
1026           && !IsBurning() == !Item->IsBurning()
1027           && label==Item->label );
1028 }
1029 
Break(character * Breaker,int)1030 void item::Break(character* Breaker, int)
1031 {
1032   if(!CanBeBroken() || IsBroken())
1033     return;
1034 
1035   if(CanBeSeenByPlayer())
1036     ADD_MESSAGE("%s %s.", GetExtendedDescription().CStr(), GetBreakVerb());
1037 
1038   if(Breaker && IsOnGround())
1039   {
1040     room* Room = GetRoom();
1041 
1042     if(Room)
1043       Room->HostileAction(Breaker);
1044   }
1045 
1046   item* Broken = GetProtoType()->Clone(this);
1047   Broken->SetConfig(GetConfig() | BROKEN);
1048   Broken->SetSize(Broken->GetSize() >> 1);
1049   DonateFluidsTo(Broken);
1050   DonateIDTo(Broken);
1051   DonateSlotTo(Broken);
1052   SendToHell();
1053 
1054   if(PLAYER->Equips(Broken))
1055     game::AskForKeyPress(CONST_S("Equipment broken! [press any key to continue]"));
1056 }
1057 
Be()1058 void item::Be()
1059 {
1060   MainMaterial->Be(ItemFlags);
1061 
1062   if(Exists() && LifeExpectancy)
1063   {
1064     if(LifeExpectancy == 1)
1065     {
1066       if(CanBeSeenByPlayer())
1067         ADD_MESSAGE("%s disappears.", GetExtendedDescription().CStr());
1068 
1069       truth Equipped = PLAYER->Equips(this);
1070       Disappear();
1071 
1072       if(Equipped)
1073         game::AskForKeyPress(CONST_S("Equipment destroyed! [press any key to continue]"));
1074     }
1075     else
1076     {
1077       --LifeExpectancy;
1078       //TODO fluids emitation on weapons require updating in case it lowers with time, this didnt work: if(Fluid)CalculateEmitation();
1079     }
1080   }
1081 }
1082 
GetOfferValue(int Receiver) const1083 int item::GetOfferValue(int Receiver) const
1084 {
1085   /* Temporary */
1086 
1087   int OfferValue = int(sqrt(GetTruePrice()));
1088 
1089   if(Receiver == GetAttachedGod())
1090     OfferValue <<= 1;
1091   else
1092     OfferValue >>= 1;
1093 
1094   return OfferValue;
1095 }
1096 
SignalEnchantmentChange()1097 void item::SignalEnchantmentChange()
1098 {
1099   for(int c = 0; c < SquaresUnder; ++c)
1100     if(Slot[c])
1101       Slot[c]->SignalEnchantmentChange();
1102 }
1103 
GetEnchantedPrice(int Enchantment) const1104 long item::GetEnchantedPrice(int Enchantment) const
1105 {
1106   if(!PriceIsProportionalToEnchantment())
1107     return item::GetPrice();
1108   else
1109     return Max<int>(item::GetPrice() * Enchantment * Enchantment, 0);
1110 }
1111 
Fix()1112 item* item::Fix()
1113 {
1114   item* Fixed = this;
1115 
1116   if(IsBroken())
1117   {
1118     Fixed = GetProtoType()->Clone(this);
1119     Fixed->SetConfig(GetConfig() ^ BROKEN);
1120     Fixed->SetSize(Fixed->GetSize() << 1);
1121     DonateFluidsTo(Fixed);
1122     DonateIDTo(Fixed);
1123     DonateSlotTo(Fixed);
1124     SendToHell();
1125   }
1126 
1127   return Fixed;
1128 }
1129 
DonateSlotTo(item * Item)1130 void item::DonateSlotTo(item* Item)
1131 {
1132   if(Slot[0])
1133   {
1134     Slot[0]->DonateTo(Item);
1135     Slot[0] = 0;
1136 
1137     for(int c = 1; c < SquaresUnder; ++c)
1138       if(Slot[c])
1139       {
1140         Slot[c]->Empty();
1141         Slot[c] = 0;
1142       }
1143   }
1144 }
1145 
GetSpoilLevel() const1146 int item::GetSpoilLevel() const
1147 {
1148   return MainMaterial->GetSpoilLevel();
1149 }
1150 
GetBurnLevel() const1151 int item::GetBurnLevel() const
1152 {
1153   return MainMaterial->GetBurnLevel();
1154 }
1155 
SignalSpoilLevelChange(material *)1156 void item::SignalSpoilLevelChange(material*)
1157 {
1158   if(!IsAnimated() && GetSpoilLevel() && Slot[0] && Slot[0]->IsVisible())
1159     for(int c = 0; c < SquaresUnder; ++c)
1160       GetSquareUnder(c)->IncStaticAnimatedEntities();
1161 
1162   SignalVolumeAndWeightChange(); // gum
1163   UpdatePictures();
1164 }
1165 
AllowSpoil() const1166 truth item::AllowSpoil() const
1167 {
1168   DBG5(GetName(DEFINITE).CStr(),GetID(),GetSquareUnder(),GetWearer(),GetSlot());
1169   DBGEXEC( //this wont even compile if DBGMSG is not enabled
1170     /** crash helper
1171      * THE CAUSE SEEMS TO BE from crafting a new item, it being a chest, and cancelling. The code was not sending the canceled spawned chest to hell and it was not placed anywhere. Fixed that, this may not happen again.
1172      * TODO remove this debug code and the workaround below after sure wont need anymore. despite the debug code will only compile if using DBGMSG
1173      * seems to be about a buggy organic spawned "on floor" that has no square under...
1174     *  happens below at: if(IsOnGround())
1175    *   item.cpp:1048:AllowSpoil:{GetName(2).CStr()}="the loaf of dark bread";{GetID()}="92980";{GetSquareUnder()}="0";{GetWearer()}="0";{GetSlot()}="0x8272630";
1176  2018/07/17-15:36:08(1122986) item.cpp:1049:AllowSpoil:{GetName(2).CStr()}="the loaf of dark bread";{GetID()}="780622";{GetSquareUnder()}="0";{GetWearer()}="0";{GetSlot()}="0x38be0e0";
1177  2018/07/17-15:36:08(1122987) item.cpp:1091:AllowSpoil:{itSS}="0x3293700";{ss->GetSquareUnder()}="0";
1178  2018/07/17-15:36:08(1122988) item.cpp:1091:AllowSpoil:{itSS->GetID()}="780622";{itSS->GetNameSingular().CStr()}="loaf";
1179  2018/07/17-15:36:08(1122989) item.cpp:1091:AllowSpoil:{ss->GetMotherStack()->GetItems()}="3";{ent}="0x62a17d0";{ss->GetMotherStack()->GetSquareUnder()}="0";
1180  2018/07/17-15:36:08(1122990) item.cpp:1091:AllowSpoil:{ent->GetSquareUnderEntity()}="0";
1181  2018/07/17-15:36:08(1122991) item.cpp:1091:AllowSpoil:{itM->GetID()}="780621";{itM->GetNameSingular().CStr()}="chest";
1182    *     ./bin//ivan(_ZN6dbgmsg20getCurrentStackTraceEbRi+0xaa) [0x92f43e]
1183    *     ./bin//ivan(_ZN6dbgmsg22getCurrentStackTraceSSB5cxx11Ebb+0x54) [0x92f53c]
1184     *    ./bin//ivan(_ZN6dbgmsg8SigHndlrEi+0x1ae) [0x92fcdc]
1185      *   /lib/x86_64-linux-gnu/libc.so.6(+0x354b0) [0x7f6b145e14b0]
1186       *  ./bin//ivan(_ZNK4item10IsOnGroundEv+0x17) [0x8963c7]
1187       *  ./bin//ivan(_ZNK5stack10IsOnGroundEv+0x38) [0x8f12a8]
1188       *  ./bin//ivan(_ZNK9stackslot10IsOnGroundEv+0x20) [0x8ebb96]
1189      *   ./bin//ivan(_ZNK4item10IsOnGroundEv+0x31) [0x8963e1]
1190     *    ./bin//ivan(_ZNK4item10AllowSpoilEv+0x27f) [0x89b0ab]
1191    *     ./bin//ivan(_ZN7organic2BeEm+0x4f) [0x839d0d]
1192    *     ./bin//ivan(_ZN4item2BeEv+0x49) [0x89a819]
1193    *     ./bin//ivan(_ZN4pool2BeEv+0x3b) [0x69593b]
1194     *    ./bin//ivan(_ZN4game3RunEv+0x3da) [0x77ad5a]
1195      *   ./bin//ivan(main+0x4d7) [0x70d462]
1196       *  /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f6b145cc830]
1197       *  ./bin//ivan(_start+0x29) [0x68f999]
1198      */
1199     if(dynamic_cast<stackslot*>(GetSlot())!=NULL){
1200       stackslot* ss = (stackslot*)GetSlot();
1201       item* itSS = ss->GetItem();
1202       DBG2(itSS,ss->GetSquareUnder());
1203       if(itSS!=NULL)DBG2(itSS->GetID(),itSS->GetNameSingular().CStr());
1204       stack* stkM=ss->GetMotherStack();
1205       if(stkM!=NULL){
1206         entity* ent = stkM->GetMotherEntity();
1207         DBG3(ss->GetMotherStack()->GetItems(), ent, ss->GetMotherStack()->GetSquareUnder());
1208         if(ent!=NULL){
1209           DBG1(ent->GetSquareUnderEntity());
1210 //          if(dynamic_cast<id*>(ent))DBG1(((id*)ent)->GetID());
1211           if(dynamic_cast<item*>(ent)){
1212             item* itM = (item*)ent;
1213             DBG2(itM->GetID(),itM->GetNameSingular().CStr());
1214           }
1215         }
1216       }
1217     }
1218   );
1219 //TODO remove this. The workaround is NOT good as SquareUnder is essential everywhere!!!!
1220 //  bool bIsOnGround=false;
1221 //  lsquare* Square = GetLSquareUnder(); //TODO what could cause Square to be NULL ?????
1222 //  static bool bAllowSpoilBugWorkaround=true;
1223 //  if(bAllowSpoilBugWorkaround){ //See the above bug track code, the origin of the problem is still NOT solved/understood/discovered!!!!!!
1224 //    if(Square!=NULL){
1225 //      bIsOnGround=IsOnGround();
1226 //      DBG1("WorkaroundForItemPlacedNoWhere");
1227 //    }
1228 //    /**
1229 //     * what this means?
1230 //     *
1231 //     * not knowing where the item is, if on a room, the item will ALWAYS spoil,
1232 //     * so if the buggy item "is" on a shop, it will spoil.
1233 //     *
1234 //     * and, on this stack/flow that game wont crash, but still may on other code flows!!!
1235 //     */
1236 //  }else{
1237 //    bIsOnGround=IsOnGround();
1238 //  }
1239 
1240   if(IsOnGround())
1241   {
1242     lsquare* Square = GetLSquareUnder();
1243     int RoomNumber = Square->GetRoomIndex();
1244     return !RoomNumber || Square->GetLevel()->GetRoom(RoomNumber)->AllowSpoil(this);
1245   }
1246   else
1247     return true;
1248 }
1249 
ResetSpoiling()1250 void item::ResetSpoiling()
1251 {
1252   for(int c = 0; c < GetMaterials(); ++c)
1253     if(GetMaterial(c))
1254       GetMaterial(c)->ResetSpoiling();
1255 }
1256 
ResetBurning()1257 void item::ResetBurning()
1258 {
1259   for(int c = 0; c < GetMaterials(); ++c)
1260     if(GetMaterial(c))
1261       GetMaterial(c)->ResetBurning();
1262   SignalEmitationDecrease(MakeRGB24(150, 120, 90));
1263 }
1264 
1265 /* be careful calling this function, it will also cause flames to extinguish */
ResetThermalEnergies()1266 void item::ResetThermalEnergies()
1267 {
1268   for(int c = 0; c < GetMaterials(); ++c)
1269     if(GetMaterial(c))
1270       GetMaterial(c)->ResetThermalEnergies();
1271 }
1272 
GetBaseToHitValueDescription() const1273 cchar* item::GetBaseToHitValueDescription() const
1274 {
1275   if(GetBaseToHitValue() < 10)
1276     return ToHitValueDescription[Min(GetBaseToHitValue(), 6)];
1277   else
1278     return ToHitValueDescription[7];
1279 }
1280 
GetBaseBlockValueDescription() const1281 cchar* item::GetBaseBlockValueDescription() const
1282 {
1283   if(GetBaseBlockValue() < 20)
1284     return ToHitValueDescription[Min(GetBaseBlockValue() >> 1, 6)];
1285   else
1286     return ToHitValueDescription[7];
1287 }
1288 
GetStrengthValueDescription() const1289 cchar* item::GetStrengthValueDescription() const
1290 {
1291   int SV = GetStrengthValue();
1292 
1293   if(SV < 3)
1294     return StrengthValueDescription[0];
1295   else if(SV < 5)
1296     return StrengthValueDescription[1];
1297   else if(SV < 8)
1298     return StrengthValueDescription[2];
1299   else if(SV < 11)
1300     return StrengthValueDescription[3];
1301   else if(SV < 16)
1302     return StrengthValueDescription[4];
1303   else if(SV < 20)
1304     return StrengthValueDescription[5];
1305   else
1306     return StrengthValueDescription[6];
1307 }
1308 
SpecialGenerationHandler()1309 void item::SpecialGenerationHandler()
1310 {
1311   if(HandleInPairs())
1312     Slot[0]->AddFriendItem(Duplicate());
1313 }
1314 
SortAllItems(const sortdata & SortData) const1315 void item::SortAllItems(const sortdata& SortData) const
1316 {
1317   if(SortData.Sorter == 0 || (this->*SortData.Sorter)(SortData.Character))
1318     SortData.AllItems.push_back(const_cast<item*>(this));
1319 }
1320 
GetAttachedGod() const1321 int item::GetAttachedGod() const
1322 {
1323   return DataBase->AttachedGod ? DataBase->AttachedGod : MainMaterial->GetAttachedGod();
1324 }
1325 
GetMaterialPrice() const1326 long item::GetMaterialPrice() const
1327 {
1328   return MainMaterial->GetRawPrice();
1329 }
1330 
GetTruePrice() const1331 long item::GetTruePrice() const
1332 {
1333   if(LifeExpectancy)
1334     return 0;
1335 
1336   long Price = Max(GetPrice(), GetMaterialPrice());
1337 
1338   if(Spoils())
1339     Price = Price * (100 - GetMaxSpoilPercentage()) / 500;
1340 
1341   return Price;
1342 }
1343 
1344 #ifdef WIZARD
1345 
AddAttackInfo(felist & List) const1346 void item::AddAttackInfo(felist& List) const
1347 {
1348   festring Entry(40, ' ');
1349   Entry << int(GetWeight());
1350   Entry.Resize(50);
1351   Entry << GetSize();
1352   Entry.Resize(60);
1353   Entry << GetStrengthRequirement();
1354   Entry.Resize(70);
1355   Entry << GetBaseMinDamage() << '-' << GetBaseMaxDamage();
1356   List.AddEntry(Entry, LIGHT_GRAY);
1357 }
1358 
AddMiscellaneousInfo(felist & List) const1359 void item::AddMiscellaneousInfo(felist& List) const
1360 {
1361   festring Entry(40, ' ');
1362   Entry << int(GetTruePrice());
1363   Entry.Resize(55);
1364   Entry << GetOfferValue(0);
1365   Entry.Resize(70);
1366   Entry << int(GetNutritionValue());
1367   List.AddEntry(Entry, LIGHT_GRAY);
1368 }
1369 
1370 #endif
1371 
PreProcessForBone()1372 void item::PreProcessForBone()
1373 {
1374   if(IsQuestItem())
1375   {
1376     RemoveFromSlot();
1377     SendToHell();
1378   }
1379   else
1380   {
1381     game::RemoveItemID(ID);
1382     ID = -ID;
1383     game::AddItemID(this, ID);
1384   }
1385 }
1386 
_BugWorkaround_ItemDup(ulong key)1387 void item::_BugWorkaround_ItemDup(ulong key){ID=key;} //keep it simple!
1388 
PostProcessForBone()1389 void item::PostProcessForBone()
1390 {
1391   boneidmap::iterator BI = game::GetBoneItemIDMap().find(-ID);
1392   game::RemoveItemID(ID);
1393 
1394   if(BI == game::GetBoneItemIDMap().end())
1395   {
1396     ulong NewID = game::CreateNewItemID(this);
1397     game::GetBoneItemIDMap().insert(std::make_pair(-ID, NewID));
1398     ID = NewID;
1399   }
1400   else
1401   {
1402     ID = BI->second;
1403     game::AddItemID(this, ID);
1404   }
1405 
1406   for(idholder* I = CloneMotherID; I; I = I->Next)
1407   {
1408     BI = game::GetBoneItemIDMap().find(I->ID);
1409 
1410     if(BI == game::GetBoneItemIDMap().end())
1411     {
1412       ulong NewCloneMotherID = game::CreateNewItemID(0);
1413       game::GetBoneItemIDMap().insert(std::make_pair(I->ID, NewCloneMotherID));
1414       I->ID = NewCloneMotherID;
1415     }
1416     else
1417       I->ID = BI->second;
1418   }
1419 }
1420 
SetConfig(int NewConfig,int SpecialFlags)1421 void item::SetConfig(int NewConfig, int SpecialFlags)
1422 {
1423   databasecreator<item>::InstallDataBase(this, NewConfig);
1424   CalculateAll();
1425 
1426   if(!(SpecialFlags & NO_PIC_UPDATE))
1427     UpdatePictures();
1428 }
1429 
SetConfigIfPossible(int NewConfig,int SpecialFlags)1430 truth item::SetConfigIfPossible(int NewConfig, int SpecialFlags)
1431 {
1432   if(databasecreator<item>::InstallDataBaseIfPossible(this, NewConfig, GetConfig())){
1433     CalculateAll();
1434 
1435     if(!(SpecialFlags & NO_PIC_UPDATE))
1436       UpdatePictures();
1437 
1438     return true;
1439   }
1440 
1441   return false;
1442 }
1443 
GetMasterGod() const1444 god* item::GetMasterGod() const
1445 {
1446   return game::GetGod(GetConfig());
1447 }
1448 
CreateSpecialConfigurations(itemdatabase ** TempConfig,int Configs,int Level)1449 int itemprototype::CreateSpecialConfigurations(itemdatabase** TempConfig, int Configs, int Level)
1450 {
1451   if(Level)
1452     return Configs;
1453 
1454   if(TempConfig[0]->CreateDivineConfigurations)
1455     Configs = databasecreator<item>::CreateDivineConfigurations(this, TempConfig, Configs);
1456 
1457   /* Gum solution */
1458 
1459   if(TempConfig[0]->CreateLockConfigurations)
1460   {
1461     const item::database*const* KeyConfigData = key::ProtoType.GetConfigData();
1462     int KeyConfigSize = key::ProtoType.GetConfigSize();
1463     int OldConfigs = Configs;
1464 
1465     for(int c1 = 0; c1 < OldConfigs; ++c1)
1466       if(!TempConfig[c1]->IsAbstract)
1467       {
1468         int BaseConfig = TempConfig[c1]->Config;
1469         int NewConfig = BaseConfig | BROKEN_LOCK;
1470         itemdatabase* ConfigDataBase = new itemdatabase(*TempConfig[c1]);
1471         ConfigDataBase->InitDefaults(this, NewConfig);
1472         ConfigDataBase->PostFix << "with a broken lock";
1473         ConfigDataBase->Possibility = 0;
1474         TempConfig[Configs++] = ConfigDataBase;
1475 
1476         for(int c2 = 0; c2 < KeyConfigSize; ++c2)
1477         {
1478           NewConfig = BaseConfig | KeyConfigData[c2]->Config;
1479           ConfigDataBase = new itemdatabase(*TempConfig[c1]);
1480           ConfigDataBase->InitDefaults(this, NewConfig);
1481           ConfigDataBase->PostFix << "with ";
1482 
1483           if(KeyConfigData[c2]->UsesLongAdjectiveArticle)
1484             ConfigDataBase->PostFix << "an ";
1485           else
1486             ConfigDataBase->PostFix << "a ";
1487 
1488           ConfigDataBase->PostFix << KeyConfigData[c2]->Adjective << " lock";
1489           ConfigDataBase->Possibility = 0;
1490           TempConfig[Configs++] = ConfigDataBase;
1491         }
1492       }
1493   }
1494 
1495   return Configs;
1496 }
1497 
Draw(blitdata & BlitData) const1498 void item::Draw(blitdata& BlitData) const
1499 {
1500   cint AF = GraphicData.AnimationFrames;
1501   cint F = !(BlitData.CustomData & ALLOW_ANIMATE) || AF == 1 ? 0 : GET_TICK() & (AF - 1);
1502 
1503   bitmap* bmp = GraphicData.Picture[F];
1504   if(ivanconfig::GetRotateTimesPerSquare()>0 && iRotateFlyingThrownStep!=0){ // tests made using a single bladed (unbalanced) thrown axe where 0 degrees was: blade at topRight poiting downwards
1505     static blitdata B = [](){B=DEFAULT_BLITDATA; //to reuse tmp bitmap memory
1506       B.Bitmap = new bitmap(TILE_V2); //bmp->GetSize());
1507       B.Border = TILE_V2; return B; }();
1508 
1509     bitmap::ConfigureBlitdataRotation(B,iRotateFlyingThrownStep);
1510 
1511     bmp->NormalBlit(B); //or NormalMaskedBlit() only way to rotate...
1512     bmp = B.Bitmap;
1513   }
1514   cbitmap* P = bmp;
1515 
1516   if(BlitData.CustomData & ALLOW_ALPHA)
1517     P->AlphaLuminanceBlit(BlitData);
1518   else
1519     P->LuminanceMaskedBlit(BlitData);
1520 
1521   if(Fluid && ShowFluids())
1522     DrawFluids(BlitData);
1523 }
1524 
GetLargeBitmapPos(v2 BasePos,int I) const1525 v2 item::GetLargeBitmapPos(v2 BasePos, int I) const
1526 {
1527   cint SquareIndex = I ? I / (GraphicData.AnimationFrames >> 2) : 0;
1528   return v2(SquareIndex & 1 ? BasePos.X + 16 : BasePos.X, SquareIndex & 2 ? BasePos.Y + 16 : BasePos.Y);
1529 }
1530 
LargeDraw(blitdata & BlitData) const1531 void item::LargeDraw(blitdata& BlitData) const
1532 {
1533   cint TrueAF = GraphicData.AnimationFrames >> 2;
1534   cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
1535   cint F = !(BlitData.CustomData & ALLOW_ANIMATE) ? SquareIndex * TrueAF : SquareIndex * TrueAF + (GET_TICK() & (TrueAF - 1));
1536   cbitmap* P = GraphicData.Picture[F];
1537 
1538   if(BlitData.CustomData & ALLOW_ALPHA)
1539     P->AlphaLuminanceBlit(BlitData);
1540   else
1541     P->LuminanceMaskedBlit(BlitData);
1542 }
1543 
DonateIDTo(item * Item)1544 void item::DonateIDTo(item* Item)
1545 {
1546   game::RemoveItemID(Item->ID);
1547   game::UpdateItemID(Item, ID);
1548   Item->ID = ID;
1549   ID = 0;
1550 }
1551 
SignalRustLevelChange()1552 void item::SignalRustLevelChange()
1553 {
1554   SignalVolumeAndWeightChange();
1555   UpdatePictures();
1556   SendNewDrawAndMemorizedUpdateRequest();
1557 }
1558 
SignalBurnLevelTransitionMessage()1559 void item::SignalBurnLevelTransitionMessage()
1560 {
1561   if(CanBeSeenByPlayer())
1562   {
1563     if(MainMaterial->GetBurnLevel() == NOT_BURNT)
1564       ADD_MESSAGE("%s burns.", GetExtendedDescription().CStr());
1565     else
1566       ADD_MESSAGE("%s burns more.", GetExtendedDescription().CStr());
1567   }
1568 }
1569 
SignalBurnLevelChange()1570 void item::SignalBurnLevelChange()
1571 {
1572   if(!IsAnimated() && GetBurnLevel() && Slot[0] && Slot[0]->IsVisible())
1573     for(int c = 0; c < SquaresUnder; ++c)
1574       GetSquareUnder(c)->IncStaticAnimatedEntities();
1575 
1576   SignalEmitationDecrease(MakeRGB24(150, 120, 90)); // completely remove previously applied emitation increases
1577   SignalEmitationIncrease(GetEmitationDueToBurnLevel()); // apply an emitation increase according to the current burn level
1578 
1579   SignalVolumeAndWeightChange();
1580   UpdatePictures();
1581   SendNewDrawAndMemorizedUpdateRequest();
1582 }
1583 
1584 /* emitation slowly ramps down with increasing item BurnLevel, in the beginning the
1585    light is bright, with light decreasing in intensity as the item gets more burnt */
GetEmitationDueToBurnLevel()1586 col24 item::GetEmitationDueToBurnLevel()
1587 {
1588   if(MainMaterial)
1589   {
1590     int CurrentBurnLevel = GetBurnLevel();
1591 
1592     int Red = 150 - 10 * CurrentBurnLevel;
1593     int Green = 120 - 8 * CurrentBurnLevel;
1594     int Blue = 90 - 6 * CurrentBurnLevel;
1595     //ADD_MESSAGE("Emitation due to BurnLevel: R%d G%d B%d", Red, Green, Blue); // por debug
1596     return MakeRGB24(Red, Green, Blue);
1597   }
1598   else
1599     return MakeRGB24(0, 0, 0);
1600 }
1601 
GetRawPicture() const1602 const rawbitmap* item::GetRawPicture() const
1603 {
1604   return igraph::GetRawGraphic(GetGraphicsContainerIndex());
1605 }
1606 
RemoveFluid(fluid * ToBeRemoved)1607 void item::RemoveFluid(fluid* ToBeRemoved)
1608 {
1609   truth WasAnimated = IsAnimated();
1610   truth HasFluids = false;
1611 
1612   for(int c = 0; c < SquaresUnder; ++c)
1613   {
1614     fluid* F = Fluid[c];
1615 
1616     if(F == ToBeRemoved)
1617       Fluid[c] = F->Next;
1618     else if(F)
1619     {
1620       fluid* LF = F;
1621 
1622       for(F = F->Next; F; LF = F, F = F->Next)
1623         if(F == ToBeRemoved)
1624         {
1625           LF->Next = F->Next;
1626           break;
1627         }
1628     }
1629 
1630     if(Fluid[c])
1631       HasFluids = true;
1632   }
1633 
1634   UpdatePictures();
1635 
1636   if(!HasFluids)
1637   {
1638     delete [] Fluid;
1639     Fluid = 0;
1640 
1641     if(!IsAnimated() != !WasAnimated && Slot[0]->IsVisible())
1642       GetSquareUnder()->DecStaticAnimatedEntities();
1643   }
1644 
1645   SignalEmitationDecrease(ToBeRemoved->GetEmitation());
1646   SignalVolumeAndWeightChange();
1647 }
1648 
AddFluid(liquid * ToBeAdded,festring LocationName,int SquareIndex,truth IsInside)1649 void item::AddFluid(liquid* ToBeAdded, festring LocationName, int SquareIndex, truth IsInside)
1650 {
1651   truth WasAnimated = IsAnimated();
1652 
1653   if(Fluid)
1654   {
1655     fluid* F = Fluid[SquareIndex];
1656 
1657     if(!F)
1658       Fluid[SquareIndex] = new fluid(ToBeAdded, this, LocationName, IsInside);
1659     else
1660     {
1661       fluid* LF;
1662 
1663       do
1664       {
1665         if(ToBeAdded->IsSameAs(F->GetLiquid()))
1666         {
1667           F->AddLiquidAndVolume(ToBeAdded->GetVolume());
1668           delete ToBeAdded;
1669           return;
1670         }
1671 
1672         LF = F;
1673         F = F->Next;
1674       }
1675       while(F);
1676 
1677       LF->Next = new fluid(ToBeAdded, this, LocationName, IsInside);
1678     }
1679   }
1680   else
1681   {
1682     Fluid = new fluid*[SquaresUnder];
1683     memset(Fluid, 0, SquaresUnder * sizeof(fluid*));
1684     Fluid[SquareIndex] = new fluid(ToBeAdded, this, LocationName, IsInside);
1685   }
1686 
1687   UpdatePictures();
1688   SignalVolumeAndWeightChange();
1689   SignalEmitationIncrease(ToBeAdded->GetEmitation());
1690 
1691   if(Slot[0])
1692   {
1693     if(!IsAnimated() != !WasAnimated && Slot[0]->IsVisible())
1694       GetSquareUnder()->IncStaticAnimatedEntities();
1695 
1696     SendNewDrawAndMemorizedUpdateRequest();
1697   }
1698 }
1699 
SendNewDrawAndMemorizedUpdateRequest() const1700 void item::SendNewDrawAndMemorizedUpdateRequest() const
1701 {
1702   if(!game::IsInWilderness())
1703     for(int c = 0; c < SquaresUnder; ++c)
1704       if(Slot[c])
1705       {
1706         lsquare* Square = GetLSquareUnder(c);
1707         Square->SendNewDrawRequest();
1708         Square->SendMemorizedUpdateRequest();
1709       }
1710 }
1711 
CalculateEmitation()1712 void item::CalculateEmitation()
1713 {
1714   object::CalculateEmitation();
1715 
1716   if(Fluid){
1717     for(int c = 0; c < SquaresUnder; ++c){
1718       for(const fluid* F = Fluid[c]; F; F = F->Next){
1719         DBG2(F->GetEmitation(),F->GetLiquid()->GetVolume());
1720         game::CombineLights(Emitation,
1721           CalcEmitationBasedOnVolume( 0, F->GetEmitation(), F->GetLiquid()->GetVolume() ) );
1722       }
1723     }
1724   }
1725 }
1726 
FillFluidVector(fluidvector & Vector,int SquareIndex) const1727 void item::FillFluidVector(fluidvector& Vector, int SquareIndex) const
1728 {
1729   if(Fluid)
1730     for(fluid* F = Fluid[SquareIndex]; F; F = F->Next)
1731       Vector.push_back(F);
1732 }
1733 
SpillFluid(character *,liquid * Liquid,int SquareIndex)1734 void item::SpillFluid(character*, liquid* Liquid, int SquareIndex)
1735 {
1736   if(AllowFluids() && Liquid->GetVolume())
1737     AddFluid(Liquid, "", SquareIndex, false);
1738   else
1739     delete Liquid;
1740 }
1741 
TryToRust(long LiquidModifier)1742 void item::TryToRust(long LiquidModifier)
1743 {
1744   if(MainMaterial->TryToRust(LiquidModifier))
1745   {
1746     if(CanBeSeenByPlayer())
1747     {
1748       if(MainMaterial->GetRustLevel() == NOT_RUSTED)
1749         ADD_MESSAGE("%s rusts.", CHAR_NAME(DEFINITE));
1750       else
1751         ADD_MESSAGE("%s rusts more.", CHAR_NAME(DEFINITE));
1752     }
1753 
1754     MainMaterial->SetRustLevel(MainMaterial->GetRustLevel() + 1);
1755   }
1756 }
1757 
TestActivationEnergy(int Damage)1758 truth item::TestActivationEnergy(int Damage)
1759 {
1760   if(Damage <= 0)
1761       return false;
1762 
1763   truth Success = false;
1764 //  if(MainMaterial)
1765 //  {
1766 //    int molamola = ((GetMainMaterial()->GetStrengthValue() >> 1) + 5 * MainMaterial->GetFireResistance() + GetResistance(FIRE));
1767 //    ADD_MESSAGE("%s is being tested (Damage is %d, AE is %d)", CHAR_NAME(DEFINITE), Damage, molamola);
1768 //  }
1769 
1770   if(MainMaterial)
1771   {
1772     int TestDamage = Damage + MainMaterial->GetTransientThermalEnergy();
1773     GetMainMaterial()->AddToTransientThermalEnergy(Damage);
1774     if(CanBeBurned() && GetMainMaterial()->GetInteractionFlags() & CAN_BURN
1775        && TestDamage >= ((GetMainMaterial()->GetStrengthValue() >> 1)
1776                          + 5 * MainMaterial->GetFireResistance() + GetResistance(FIRE)))
1777     {
1778       if(CanBeSeenByPlayer())
1779       {
1780         ADD_MESSAGE("%s catches fire!", CHAR_NAME(DEFINITE));
1781         //ADD_MESSAGE("%s catches fire! (TestDamage was %d)", CHAR_NAME(DEFINITE), TestDamage);
1782       }
1783       Ignite();
1784       GetMainMaterial()->AddToSteadyStateThermalEnergy(Damage);
1785       Success = true;
1786     }
1787   }
1788   return Success;
1789 }
1790 
Ignite()1791 void item::Ignite(/*character* Arsonist*/)
1792 {
1793   truth WasAnimated = IsAnimated();
1794 
1795   MainMaterial->SetIsBurning(true);
1796   SignalEmitationIncrease(GetEmitationDueToBurnLevel()); // kick this off by applying an emitation increase proportional to the burn level of the item
1797   UpdatePictures();
1798   //ADD_MESSAGE("The %s now burns brightly.", CHAR_NAME(DEFINITE));
1799 
1800   if(Slot[0])
1801   {
1802     if(!IsAnimated() != !WasAnimated && Slot[0]->IsVisible())
1803       GetSquareUnder()->IncStaticAnimatedEntities();
1804 
1805     SendNewDrawAndMemorizedUpdateRequest();
1806   }
1807 }
1808 
1809 /* This causes the main material to stop burning, resets the thermal energies
1810    and does a picture update on the level, as well as wielded pictures */
Extinguish(truth SendMessages)1811 void item::Extinguish(/*character* FireFighter, */truth SendMessages)
1812 {
1813   truth WasAnimated = IsAnimated();
1814   truth WasSeen = CanBeSeenByPlayer();
1815   truth WasBurning = IsBurning();
1816 
1817   MainMaterial->SetIsBurning(false);
1818   MainMaterial->ResetThermalEnergies();
1819   SignalEmitationDecrease(MakeRGB24(150, 120, 90));
1820 
1821   if(Slot[0])
1822   {
1823     if(!IsAnimated() != !WasAnimated && Slot[0]->IsVisible())
1824       GetSquareUnder()->DecStaticAnimatedEntities();
1825   }
1826 
1827   if(WasBurning && WasSeen && SendMessages) // by now it is dark...
1828     AddExtinguishMessage();
1829 
1830   SignalVolumeAndWeightChange();
1831   UpdatePictures();
1832   SendNewDrawAndMemorizedUpdateRequest();
1833 }
1834 
AddExtinguishMessage()1835 void item::AddExtinguishMessage()
1836 {
1837   ADD_MESSAGE("The flames on %s die away.", GetExtendedDescription().CStr());
1838 }
1839 
1840 // This is for anything made from phoenix feather
AddSpecialExtinguishMessageForPF()1841 void item::AddSpecialExtinguishMessageForPF()
1842 {
1843   if(CanBeSeenByPlayer())
1844     ADD_MESSAGE("%s burns even more! But lo', even as it does so, the ashes "
1845                 "peel away from it and it is made new by some innate virtue.", GetExtendedDescription().CStr());
1846 }
1847 
CheckFluidGearPictures(v2 ShadowPos,int SpecialFlags,truth BodyArmor)1848 void item::CheckFluidGearPictures(v2 ShadowPos, int SpecialFlags, truth BodyArmor)
1849 {
1850   if(Fluid)
1851     for(fluid* F = Fluid[0]; F; F = F->Next)
1852       F->CheckGearPicture(ShadowPos, SpecialFlags, BodyArmor);
1853 }
1854 
DrawFluidGearPictures(blitdata & BlitData,int SpecialFlags) const1855 void item::DrawFluidGearPictures(blitdata& BlitData, int SpecialFlags) const
1856 {
1857   if(Fluid)
1858     for(const fluid* F = Fluid[0]; F; F = F->Next)
1859       F->DrawGearPicture(BlitData, SpecialFlags);
1860 }
1861 
DrawFluidBodyArmorPictures(blitdata & BlitData,int SpecialFlags) const1862 void item::DrawFluidBodyArmorPictures(blitdata& BlitData, int SpecialFlags) const
1863 {
1864   if(Fluid)
1865     for(const fluid* F = Fluid[0]; F; F = F->Next)
1866       F->DrawBodyArmorPicture(BlitData, SpecialFlags);
1867 }
1868 
DrawFluids(blitdata & BlitData) const1869 void item::DrawFluids(blitdata& BlitData) const
1870 {
1871   cint SquareIndex = BlitData.CustomData & SQUARE_INDEX_MASK;
1872 
1873   for(const fluid* F = Fluid[SquareIndex]; F; F = F->Next)
1874     F->Draw(BlitData);
1875 }
1876 
ReceiveAcid(material *,cfestring &,long Modifier)1877 void item::ReceiveAcid(material*, cfestring&, long Modifier)
1878 {
1879   if(GetMainMaterial()->GetInteractionFlags() & CAN_DISSOLVE)
1880   {
1881     int Damage = Modifier / 1000;
1882 
1883     if(Damage)
1884     {
1885       Damage += RAND() % Damage;
1886       ReceiveDamage(0, Damage, ACID);
1887     }
1888   }
1889 }
1890 
ReceiveHeat(material *,cfestring &,long Modifier)1891 void item::ReceiveHeat(material*, cfestring&, long Modifier)
1892 {
1893   if(GetMainMaterial()->GetInteractionFlags() & CAN_BURN)
1894   {
1895     int Damage = Modifier / 1000;
1896 
1897     if(Damage)
1898     {
1899       Damage += RAND() % Damage;
1900       ReceiveDamage(0, Damage, FIRE);
1901     }
1902   }
1903 }
1904 
FightFire(material *,cfestring &,long Volume)1905 void item::FightFire(material*, cfestring&, long Volume)
1906 {
1907   int Amount = sqrt(Volume);
1908   GetMainMaterial()->RemoveFromThermalEnergy(Amount);
1909 }
1910 
DonateFluidsTo(item * Item)1911 void item::DonateFluidsTo(item* Item)
1912 {
1913   if(Fluid)
1914     for(int c = 0; c < GetSquaresUnder(); ++c)
1915       for(fluid* F = Fluid[c]; F; F = F->Next)
1916       {
1917         liquid* Liquid = F->GetLiquid();
1918         Item->AddFluid(Liquid->SpawnMoreLiquid(Liquid->GetVolume()), F->GetLocationName(), c, F->IsInside());
1919       }
1920 }
1921 
Destroy(character * Destroyer,int)1922 void item::Destroy(character* Destroyer, int)
1923 {
1924   if(CanBeSeenByPlayer())
1925     ADD_MESSAGE("%s is destroyed.", GetExtendedDescription().CStr());
1926 
1927   if(Destroyer && IsOnGround())
1928   {
1929     room* Room = GetRoom();
1930 
1931     if(Room)
1932       Room->HostileAction(Destroyer);
1933   }
1934 
1935   truth Equipped = PLAYER->Equips(this);
1936   RemoveFromSlot();
1937   SendToHell();
1938 
1939   if(Equipped)
1940     game::AskForKeyPress(CONST_S("Equipment destroyed! [press any key to continue]"));
1941 }
1942 
RemoveRust()1943 void item::RemoveRust()
1944 {
1945   for(int c = 0; c < GetMaterials(); ++c)
1946     if(GetMaterial(c))
1947       GetMaterial(c)->SetRustLevel(NOT_RUSTED);
1948 }
1949 
RemoveBurns()1950 void item::RemoveBurns()
1951 {
1952   for(int c = 0; c < GetMaterials(); ++c)
1953     if(GetMaterial(c))
1954       GetMaterial(c)->SetBurnLevel(NOT_BURNT, true);
1955 }
1956 
SetSpoilPercentage(int Value)1957 void item::SetSpoilPercentage(int Value)
1958 {
1959   for(int c = 0; c < GetMaterials(); ++c)
1960   {
1961     material* Material = GetMaterial(c);
1962 
1963     if(Material && Material->CanSpoil())
1964       Material->SetSpoilCounter(Material->GetSpoilModifier() * Value / 100);
1965   }
1966 }
1967 
RedistributeFluids()1968 void item::RedistributeFluids()
1969 {
1970   if(Fluid)
1971     for(int c = 0; c < GetSquaresUnder(); ++c)
1972       for(fluid* F = Fluid[c]; F; F = F->Next)
1973         F->Redistribute();
1974 }
1975 
GetConsumeMaterial(ccharacter * Consumer,materialpredicate Predicate) const1976 material* item::GetConsumeMaterial(ccharacter* Consumer, materialpredicate Predicate) const
1977 {
1978   return (MainMaterial->*Predicate)() && Consumer->CanConsume(MainMaterial) ? MainMaterial : 0;
1979 }
1980 
1981 /* The parameter can only be MainMaterial */
1982 
RemoveMaterial(material *)1983 material* item::RemoveMaterial(material*)
1984 {
1985   RemoveFromSlot();
1986   SendToHell();
1987   return 0;
1988 }
1989 
InitMaterials(material * FirstMaterial,truth CallUpdatePictures)1990 void item::InitMaterials(material* FirstMaterial, truth CallUpdatePictures)
1991 {
1992   InitMaterial(MainMaterial, FirstMaterial, GetDefaultMainVolume());
1993   SignalVolumeAndWeightChange();
1994 
1995   if(CallUpdatePictures)
1996     UpdatePictures();
1997 }
1998 
GenerateMaterials()1999 void item::GenerateMaterials()
2000 {
2001   int Chosen = RandomizeMaterialConfiguration();
2002   const fearray<long>& MMC = GetMainMaterialConfig();
2003   InitMaterial(MainMaterial,
2004                MAKE_MATERIAL(MMC.Data[MMC.Size == 1 ? 0 : Chosen]),
2005                GetDefaultMainVolume());
2006 }
2007 
SignalSquarePositionChange(int Position)2008 void item::SignalSquarePositionChange(int Position)
2009 {
2010   Flags &= ~SQUARE_POSITION_BITS;
2011   Flags |= Position << SQUARE_POSITION_SHIFT;
2012 }
2013 
Read(character * Reader)2014 truth item::Read(character* Reader)
2015 {
2016   Reader->StartReading(this, GetReadDifficulty());
2017   return true;
2018 }
2019 
CanBeHardened(ccharacter *) const2020 truth item::CanBeHardened(ccharacter*) const
2021 {
2022   return MainMaterial->GetHardenedMaterial(this) != NONE;
2023 }
2024 
CanBeSoftened() const2025 truth item::CanBeSoftened() const
2026 {
2027   return MainMaterial->GetSoftenedMaterial(this) != NONE;
2028 }
2029 
SetLifeExpectancy(int Base,int RandPlus)2030 void item::SetLifeExpectancy(int Base, int RandPlus)
2031 {
2032   LifeExpectancy = RandPlus > 1 ? Base + RAND_N(RandPlus) : Base;
2033   Enable();
2034 }
2035 
IsVeryCloseToSpoiling() const2036 truth item::IsVeryCloseToSpoiling() const
2037 {
2038   for(int c = 0; c < GetMaterials(); ++c)
2039     if(GetMaterial(c) && !GetMaterial(c)->IsVeryCloseToSpoiling())
2040       return false;
2041 
2042   return true;
2043 }
2044 
IsVeryCloseToBurning() const2045 truth item::IsVeryCloseToBurning() const
2046 {
2047   for(int c = 0; c < GetMaterials(); ++c)
2048     if(GetMaterial(c) && !GetMaterial(c)->IsVeryCloseToBurning())
2049       return false;
2050 
2051   return true;
2052 }
2053 
IsValuable() const2054 truth item::IsValuable() const
2055 {
2056   if(DataBase->IsValuable)
2057     return true;
2058 
2059   for(int c = 0; c < GetMaterials(); ++c)
2060   {
2061     material* M = GetMaterial(c);
2062 
2063     if(M && M->GetCommonFlags() & IS_VALUABLE)
2064       return true;
2065   }
2066 
2067   return false;
2068 }
2069 
GetHinderVisibilityBonus(ccharacter * Char) const2070 int item::GetHinderVisibilityBonus(ccharacter* Char) const
2071 {
2072   int Bonus = 0;
2073 
2074   if(GetGearStates() & INFRA_VISION
2075      && !Char->TemporaryStateIsActivated(INFRA_VISION))
2076     Bonus += 20000;
2077 
2078   if(GetGearStates() & ESP
2079      && !Char->TemporaryStateIsActivated(ESP))
2080     Bonus += 20000;
2081 
2082   if(!game::IsDark(GetEmitation()))
2083     Bonus += 5000;
2084 
2085   return Bonus;
2086 }
2087 
GetFixPrice() const2088 long item::GetFixPrice() const
2089 {
2090   item* Clone = GetProtoType()->Clone(this);
2091   Clone = Clone->Fix();
2092   Clone->RemoveRust();
2093   Clone->RemoveBurns();
2094   long FixPrice = Clone->GetTruePrice();
2095   Clone->SendToHell();
2096   return Max(long(3.5 * sqrt(FixPrice)), 10L);
2097 }
2098 
AddTrapName(festring & String,int Amount) const2099 void item::AddTrapName(festring& String, int Amount) const
2100 {
2101   if(Amount == 1)
2102     AddName(String, DEFINITE);
2103   else
2104   {
2105     String << Amount << ' ';
2106     AddName(String, PLURAL);
2107   }
2108 }
2109 
Spoils() const2110 truth item::Spoils() const
2111 {
2112   for(int c = 0; c < GetMaterials(); ++c)
2113   {
2114     cmaterial* Material = GetMaterial(c);
2115 
2116     if(Material && Material->Spoils())
2117       return true;
2118   }
2119 
2120   return false;
2121 }
2122 
GetMaxSpoilPercentage() const2123 int item::GetMaxSpoilPercentage() const
2124 {
2125   int MaxPercentage = 0;
2126 
2127   for(int c = 0; c < GetMaterials(); ++c)
2128   {
2129     cmaterial* Material = GetMaterial(c);
2130 
2131     if(Material)
2132       MaxPercentage = Max(MaxPercentage, Material->GetSpoilPercentage());
2133   }
2134 
2135   return MaxPercentage;
2136 }
2137 
HasPrice() const2138 truth item::HasPrice() const
2139 {
2140   return GetPrice() || GetMaterialPrice();
2141 }
2142 
Disappear()2143 void item::Disappear()
2144 {
2145   RemoveFromSlot();
2146   SendToHell();
2147 }
2148 
operator <<(outputfile & SaveFile,const idholder * IdHolder)2149 outputfile& operator<<(outputfile& SaveFile, const idholder* IdHolder)
2150 {
2151   SaveFile << IdHolder->ID;
2152   return SaveFile;
2153 }
2154 
operator >>(inputfile & SaveFile,idholder * & IdHolder)2155 inputfile& operator>>(inputfile& SaveFile, idholder*& IdHolder)
2156 {
2157   IdHolder = new idholder(ReadType<ulong>(SaveFile));
2158   return SaveFile;
2159 }
2160 
GetExtendedDescription() const2161 festring item::GetExtendedDescription() const
2162 {
2163   if(!CanBeSeenByPlayer())
2164     return CONST_S("something");
2165 
2166   festring Desc;
2167   ccharacter* Carrier = FindCarrier();
2168 
2169   if(Carrier)
2170   {
2171     if(Carrier->IsPlayer())
2172     {
2173       Desc << "your ";
2174       AddName(Desc, UNARTICLED);
2175       return Desc;
2176     }
2177     else if(Carrier->CanBeSeenByPlayer())
2178     {
2179       Carrier->AddName(Desc, DEFINITE);
2180       Desc << "'s ";
2181       AddName(Desc, UNARTICLED);
2182       return Desc;
2183     }
2184   }
2185 
2186   AddName(Desc, DEFINITE);
2187 
2188   if(IsOnGround())
2189     GetLSquareUnder()->AddLocationDescription(Desc);
2190 
2191   return Desc;
2192 }
2193 
FindCarrier() const2194 ccharacter* item::FindCarrier() const
2195 {
2196   return Slot[0]->FindCarrier();
2197 }
2198 
2199 /* returns 0 if not worn or wielded else the wearer */
2200 
GetWearer() const2201 const character* item::GetWearer() const
2202 {
2203   if(!GetSlot()->IsGearSlot())
2204     return 0;
2205 
2206   return FindCarrier();
2207 }
2208 
PostConstruct()2209 void itemlock::PostConstruct()
2210 {
2211   /* Terrible gum solution! */
2212 
2213   if(!(GetVirtualConfig() & LOCK_BITS))
2214   {
2215     int NormalLockTypes = 0;
2216     const itemdatabase*const* ConfigData = GetVirtualProtoType()->GetConfigData();
2217     int c, ConfigSize = GetVirtualProtoType()->GetConfigSize();
2218 
2219     for(c = 0; c < ConfigSize; ++c)
2220       if(ConfigData[c]->Config & LOCK_BITS
2221          && (ConfigData[c]->Config & ~LOCK_BITS) == GetVirtualConfig()
2222          && !(ConfigData[c]->Config & S_LOCK_ID))
2223         ++NormalLockTypes;
2224 
2225     int ChosenLock = RAND() % NormalLockTypes;
2226 
2227     for(c = 0; c < ConfigSize; ++c)
2228       if(ConfigData[c]->Config & LOCK_BITS
2229          && (ConfigData[c]->Config & ~LOCK_BITS) == GetVirtualConfig()
2230          && !(ConfigData[c]->Config & S_LOCK_ID)
2231          && !ChosenLock--)
2232       {
2233         SetVirtualConfig(ConfigData[c]->Config, NO_PIC_UPDATE);
2234         break;
2235       }
2236   }
2237 }
2238 
TryKey(item * Key,character * Applier)2239 truth itemlock::TryKey(item* Key, character* Applier)
2240 {
2241   if(GetVirtualConfig() & BROKEN_LOCK)
2242   {
2243     ADD_MESSAGE("The lock is broken.");
2244     return true;
2245   }
2246 
2247   if(Key->CanOpenLockType(GetVirtualConfig()&LOCK_BITS))
2248   {
2249     if(Locked)
2250     {
2251       if(Applier->IsPlayer())
2252         ADD_MESSAGE("You unlock %s.", GetVirtualDescription(DEFINITE).CStr());
2253       else if(Applier->CanBeSeenByPlayer())
2254         ADD_MESSAGE("%s unlocks %s.", Applier->CHAR_NAME(DEFINITE), GetVirtualDescription(DEFINITE).CStr());
2255     }
2256     else
2257     {
2258       if(Applier->IsPlayer())
2259         ADD_MESSAGE("You lock %s.", GetVirtualDescription(DEFINITE).CStr());
2260       else if(Applier->CanBeSeenByPlayer())
2261         ADD_MESSAGE("%s locks %s.", Applier->CHAR_NAME(DEFINITE), GetVirtualDescription(DEFINITE).CStr());
2262     }
2263 
2264     // Add a tiny chance that any key you use breaks, so that there is some value in having
2265     // multiples of a key, and in keys of better materials.
2266     if(!RAND_N(Key->GetMainMaterial()->GetStrengthValue()))
2267     {
2268       Key->Break(Applier);
2269     }
2270 
2271     Locked = !Locked;
2272   }
2273   else
2274   {
2275     if(Applier->IsPlayer())
2276       ADD_MESSAGE("%s doesn't fit in the lock.", Key->CHAR_NAME(DEFINITE));
2277     else if(Applier->CanBeSeenByPlayer())
2278       ADD_MESSAGE("%s tries to fit %s in the lock, but fails.",
2279                   Applier->CHAR_NAME(DEFINITE), Key->CHAR_NAME(DEFINITE));
2280   }
2281 
2282   return true;
2283 }
2284 
Save(outputfile & SaveFile) const2285 void itemlock::Save(outputfile& SaveFile) const
2286 {
2287   SaveFile << Locked;
2288 }
2289 
Load(inputfile & SaveFile)2290 void itemlock::Load(inputfile& SaveFile)
2291 {
2292   SaveFile >> Locked;
2293 }
2294 
IsBeverage(ccharacter *) const2295 truth item::IsBeverage(ccharacter*) const
2296 {
2297   for(int c = 0; c < GetMaterials(); ++c)
2298   {
2299     cmaterial* Material = GetMaterial(c);
2300 
2301     if(Material && (Material->GetCategoryFlags() & IS_BEVERAGE))
2302       return true;
2303   }
2304 
2305   return false;
2306 }
2307 
Haste()2308 void item::Haste()
2309 {
2310   ItemFlags |= HASTE;
2311   ItemFlags &= ~SLOW;
2312   SendMemorizedUpdateRequest();
2313 }
2314 
Slow()2315 void item::Slow()
2316 {
2317   ItemFlags |= SLOW;
2318   ItemFlags &= ~HASTE;
2319   SendMemorizedUpdateRequest();
2320 }
2321 
SendMemorizedUpdateRequest() const2322 void item::SendMemorizedUpdateRequest() const
2323 {
2324   if(!game::IsInWilderness())
2325     for(int c = 0; c < SquaresUnder; ++c)
2326       if(Slot[c])
2327       {
2328         lsquare* Square = GetLSquareUnder(c);
2329         Square->SendMemorizedUpdateRequest();
2330       }
2331 }
2332 
AddContainerPostFix(festring & String) const2333 void item::AddContainerPostFix(festring& String) const
2334 {
2335   if(GetSecondaryMaterial()){
2336     float fRatio = GetSecondaryMaterial()->GetVolume()/(float)GetDefaultSecondaryVolume();
2337     const char* c="full of";
2338     if     (fRatio<=0.10)c="with just a little bit of";
2339     else if(fRatio<=0.25)c="with a bit of";
2340     else if(fRatio<=0.45)c="with some";
2341     else if(fRatio<=0.55)c="half full of";
2342     else if(fRatio<=0.75)c="well filled with"; //TODO any better phrasing for this?
2343     else if(fRatio<=0.93)c="almost full of"; //nice arguable arbitrary percent :)
2344     GetSecondaryMaterial()->AddName(String << " "<< c << " ", false, false);
2345   }
2346 }
2347 
AddStateDescription(festring & Name,truth Articled) const2348 truth item::AddStateDescription(festring& Name, truth Articled) const
2349 {
2350   if(!Spoils() || !(ItemFlags & (HASTE|SLOW)))
2351     return false;
2352 
2353   if(Articled)
2354     Name << "a ";
2355 
2356   if(ItemFlags & HASTE)
2357     Name << "hasted ";
2358 
2359   if(ItemFlags & SLOW)
2360     Name << "slowed ";
2361 
2362   return true;
2363 }
2364