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 materset.cpp */
14 
materialprototype(const materialprototype * Base,materialspawner Spawner,materialcloner Cloner,cchar * ClassID)15 materialprototype::materialprototype(const materialprototype* Base,
16                                      materialspawner Spawner,
17                                      materialcloner Cloner,
18                                      cchar* ClassID)
19 : Base(Base), Spawner(Spawner), Cloner(Cloner), ClassID(ClassID)
20 { Index = protocontainer<material>::Add(this); }
21 
GetRawPrice() const22 long material::GetRawPrice() const
23 { return GetPriceModifier() * GetWeight() / 10000; }
CanBeDug(material * ShovelMaterial) const24 truth material::CanBeDug(material* ShovelMaterial) const
25 { return ShovelMaterial->GetStrengthValue() > GetStrengthValue(); }
GetTotalExplosivePower() const26 long material::GetTotalExplosivePower() const
27 { return long(double(Volume) * GetExplosivePower() / 1000000); }
GetConsumeVerb() const28 cchar* material::GetConsumeVerb() const { return "eating"; }
29 
30 materialpredicate TrueMaterialPredicate = &material::True;
31 
AddName(festring & Name,truth Articled,truth Adjective) const32 void material::AddName(festring& Name, truth Articled, truth Adjective) const
33 {
34   if(Articled)
35   {
36     if(GetNameFlags() & USE_AN)
37       Name << "an ";
38     else
39       Name << "a ";
40   }
41 
42   Name << (Adjective ? GetAdjectiveStem() : GetNameStem());
43 }
44 
GetName(truth Articled,truth Adjective) const45 festring material::GetName(truth Articled, truth Adjective) const
46 {
47   static festring Name;
48   Name.Empty();
49   AddName(Name, Articled, Adjective);
50   return Name;
51 }
52 
TakeDipVolumeAway(long MaxVolume)53 material* material::TakeDipVolumeAway(long MaxVolume)
54 {
55   if(Volume > MaxVolume)
56   {
57     EditVolume(-MaxVolume);
58     return SpawnMore(MaxVolume);
59   }
60   else
61     return MotherEntity->RemoveMaterial(this);
62 }
63 
Save(outputfile & SaveFile) const64 void material::Save(outputfile& SaveFile) const
65 {
66   SaveFile << static_cast<ushort>(GetType());
67   SaveFile << Volume;
68   SaveFile << static_cast<ushort>(GetConfig());
69 }
70 
Load(inputfile & SaveFile)71 void material::Load(inputfile& SaveFile)
72 {
73   SaveFile >> Volume;
74   databasecreator<material>::InstallDataBase(this, ReadType<ushort>(SaveFile));
75 }
76 
Effect(character * Char,int BodyPart,long Amount)77 truth material::Effect(character* Char, int BodyPart, long Amount)
78 {
79   /* Receivexxx should return truth! */
80 
81   Amount = Amount * GetEffectStrength() / 100;
82 
83   if(!Amount)
84     return false;
85 
86   switch(GetEffect())
87   {
88    case EFFECT_POISON: Char->BeginTemporaryState(POISONED, Amount); break;
89    case EFFECT_DARKNESS: Char->ReceiveDarkness(Amount); break;
90    case EFFECT_OMMEL_URINE: Char->ReceiveOmmelUrine(Amount); break;
91    case EFFECT_PEPSI: Char->ReceivePepsi(Amount); break;
92    case EFFECT_KOBOLD_FLESH: Char->ReceiveKoboldFlesh(Amount); break;
93    case EFFECT_HEAL: Char->ReceiveHeal(Amount); break;
94    case EFFECT_LYCANTHROPY:
95     {
96       if(!Char->StateIsActivated(DISEASE_IMMUNITY))
97       {
98         if(!RAND_N(Char->GetAttribute(ENDURANCE)))
99           Char->GainIntrinsic(LYCANTHROPY);
100         else
101           Char->BeginTemporaryState(LYCANTHROPY, Amount);
102       }
103 
104       break;
105     }
106    case EFFECT_SCHOOL_FOOD: Char->ReceiveSchoolFood(Amount); break;
107    case EFFECT_ANTIDOTE: Char->ReceiveAntidote(Amount); break;
108    case EFFECT_CONFUSE: Char->BeginTemporaryState(CONFUSED, Amount); break;
109    case EFFECT_POLYMORPH: Char->BeginTemporaryState(POLYMORPH, Amount); break;
110    case EFFECT_ESP: Char->BeginTemporaryState(ESP, Amount); break;
111    case EFFECT_SKUNK_SMELL: Char->BeginTemporaryState(POISONED, Amount); break;
112    case EFFECT_MAGIC_MUSHROOM:
113     {
114       v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
115       Char->ActivateRandomState(SRC_MAGIC_MUSHROOM, Amount,
116                                 Volume % 250 + Pos.X + Pos.Y + 1);
117       break;
118     }
119    case EFFECT_TRAIN_PERCEPTION:
120     {
121       Char->EditExperience(PERCEPTION, Amount, 1 << 14);
122       break;
123     }
124    case EFFECT_HOLY_BANANA: Char->ReceiveHolyBanana(Amount); break;
125    case EFFECT_EVIL_WONDER_STAFF_VAPOUR:
126     {
127       v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
128       Char->ActivateRandomState(SRC_EVIL, Amount,
129                                 Volume % 250 + Pos.X + Pos.Y + 1);
130       break;
131     }
132    case EFFECT_GOOD_WONDER_STAFF_VAPOUR:
133     {
134       v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
135       Char->ActivateRandomState(SRC_GOOD, Amount,
136                                 Volume % 250 + Pos.X + Pos.Y + 1);
137       break;
138     }
139    case EFFECT_PEA_SOUP: Char->ReceivePeaSoup(Amount); break;
140    case EFFECT_BLACK_UNICORN_FLESH: Char->ReceiveBlackUnicorn(Amount); break;
141    case EFFECT_GRAY_UNICORN_FLESH: Char->ReceiveGrayUnicorn(Amount); break;
142    case EFFECT_WHITE_UNICORN_FLESH: Char->ReceiveWhiteUnicorn(Amount); break;
143    case EFFECT_TELEPORT_CONTROL: Char->BeginTemporaryState(TELEPORT_CONTROL, Amount); break;
144    case EFFECT_MUSHROOM:
145     {
146       v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
147       Char->ActivateRandomState(SRC_MUSHROOM, Amount,
148                                 Volume % 250 + Pos.X + Pos.Y + 1);
149       break;
150     }
151    case EFFECT_OMMEL_CERUMEN: Char->ReceiveOmmelCerumen(Amount); break;
152    case EFFECT_OMMEL_SWEAT: Char->ReceiveOmmelSweat(Amount); break;
153    case EFFECT_OMMEL_TEARS: Char->ReceiveOmmelTears(Amount); break;
154    case EFFECT_OMMEL_SNOT: Char->ReceiveOmmelSnot(Amount); break;
155    case EFFECT_OMMEL_BONE: Char->ReceiveOmmelBone(Amount); break;
156    case EFFECT_MUSTARD_GAS: Char->ReceiveMustardGas(BodyPart, Amount); break;
157    case EFFECT_MUSTARD_GAS_LIQUID: Char->ReceiveMustardGasLiquid(BodyPart, Amount); break;
158    case EFFECT_VAMPIRISM:
159     {
160       if(!Char->StateIsActivated(DISEASE_IMMUNITY))
161         Char->BeginTemporaryState(VAMPIRISM, Amount);
162 
163       break;
164     }
165    case EFFECT_PANACEA:
166     {
167       Char->ReceiveHeal(Amount);
168       Char->ReceiveAntidote(Amount);
169       Char->RestoreStamina();
170       break;
171     }
172    case EFFECT_OMMEL_BLOOD: Char->ReceiveOmmelBlood(Amount); break;
173    case EFFECT_PANIC:
174     {
175       if(!Char->StateIsActivated(FEARLESS) && Char->GetPanicLevel() > 0)
176         Char->BeginTemporaryState(PANIC, Amount);
177 
178       break;
179     }
180    case EFFECT_TRAIN_WISDOM:
181     {
182       Char->EditExperience(WISDOM, Amount, 1 << 14);
183       break;
184     }
185    case EFFECT_REGENERATION: Char->BeginTemporaryState(REGENERATION, Amount); break;
186    case EFFECT_TELEPORTATION:
187     {
188       Char->BeginTemporaryState(TELEPORT, Amount);
189       Char->TeleportRandomly(false);
190       break;
191     }
192    case EFFECT_LAUGH:
193     {
194       game::CallForAttention(Char->GetPos(), Amount);
195       Char->BeginTemporaryState(HICCUPS, Amount);
196       break;
197     }
198    case EFFECT_POLYJUICE: Char->PolymorphRandomly(Amount, 999999, Amount * 10); break;
199    //case EFFECT_PUKE: Char->VomitAtRandomDirection(Amount); break;
200    case EFFECT_SICKNESS: Char->ReceiveSickness(Amount); break;
201    case EFFECT_PHASE: Char->BeginTemporaryState(ETHEREAL_MOVING, Amount); break;
202    case EFFECT_ACID_GAS: Char->SpillFluid(0, liquid::Spawn(SULPHURIC_ACID, Amount)); break;
203    case EFFECT_FIRE_GAS: Char->ReceiveFlames(Amount); break;
204    default: return false;
205   }
206 
207   return true;
208 }
209 
EatEffect(character * Eater,long Amount)210 material* material::EatEffect(character* Eater, long Amount)
211 {
212   Amount = Volume > Amount ? Amount : Volume;
213 
214   if(Eater->StateIsActivated(VAMPIRISM) && (GetCategoryFlags() & IS_BLOOD))
215   {
216     Amount *= 10; // Vampires are nourished by blood.
217   }
218   Eater->ReceiveNutrition(GetNutritionValue() * Amount / 50);
219 
220   if(Amount && Volume)
221   {
222     if(DisablesPanicWhenConsumed() && Eater->TemporaryStateIsActivated(PANIC))
223     {
224       if(Eater->IsPlayer())
225       {
226         ADD_MESSAGE("You relax a bit.");
227       }
228       else if(Eater->CanBeSeenByPlayer())
229       {
230         ADD_MESSAGE("%s relaxes a bit.", Eater->CHAR_NAME(DEFINITE));
231       }
232       Eater->DeActivateTemporaryState(PANIC);
233     }
234   }
235 
236   if(GetInteractionFlags() & AFFECT_INSIDE)
237   {
238     head* Head = Eater->GetVirtualHead();
239     long NewAmount = Amount;
240 
241     if(Head && Amount >= 8)
242     {
243       Head->AddFluid(static_cast<liquid*>(SpawnMore(Amount >> 3)),
244                      CONST_S("throat"), 0, true);
245       NewAmount -= Amount >> 3;
246     }
247 
248     Eater->GetTorso()->AddFluid(static_cast<liquid*>(SpawnMore(NewAmount)),
249                                 CONST_S("stomach"), 0, true);
250   }
251   else
252   {
253     Effect(Eater, TORSO_INDEX, Amount);
254 
255     if(IsLiquid())
256       Eater->EditStamina(int(50. * Amount * Eater->GetMaxStamina()
257                              / Eater->GetBodyVolume()),
258                          false);
259   }
260 
261   if(Volume != Amount)
262   {
263     EditVolume(-Amount);
264     return 0;
265   }
266   else
267     return MotherEntity->RemoveMaterial(this);
268 }
269 
HitEffect(character * Enemy,bodypart * BodyPart)270 truth material::HitEffect(character* Enemy, bodypart* BodyPart)
271 {
272   if(!Volume)
273     return false;
274 
275   switch(GetHitMessage())
276   {
277    case HM_SCHOOL_FOOD: Enemy->AddSchoolFoodHitMessage(); break;
278    case HM_FROG_FLESH: Enemy->AddFrogFleshConsumeEndMessage(); break;
279    case HM_OMMEL: Enemy->AddOmmelConsumeEndMessage(); break;
280    case HM_PEPSI: Enemy->AddPepsiConsumeEndMessage(); break;
281    case HM_KOBOLD_FLESH: Enemy->AddKoboldFleshHitMessage(); break;
282    case HM_HEALING_LIQUID: Enemy->AddHealingLiquidConsumeEndMessage(); break;
283    case HM_ANTIDOTE: Enemy->AddAntidoteConsumeEndMessage(); break;
284    case HM_CONFUSE: Enemy->AddConfuseHitMessage(); break;
285    case HM_HOLY_BANANA: Enemy->AddHolyBananaConsumeEndMessage(); break;
286   }
287 
288   long Amount = Max<long>(GetVolume() >> 1, 1);
289   truth Success;
290 
291   if(GetInteractionFlags() & AFFECT_INSIDE)
292   {
293     if(BodyPart)
294     {
295       BodyPart->AddFluid(static_cast<liquid*>(SpawnMore(Amount)),
296                          CONST_S(""), 0, true);
297       Success = true;
298     }
299     else
300       Success = false;
301   }
302   else
303   {
304     int BPIndex = BodyPart ? BodyPart->GetBodyPartIndex() : NONE_INDEX;
305     Success = Effect(Enemy, BPIndex, Amount);
306   }
307 
308   if(Amount != Volume)
309     EditVolume(-Amount);
310   else
311     delete MotherEntity->RemoveMaterial(this);
312 
313   return Success;
314 }
315 
AddConsumeEndMessage(character * Eater) const316 void material::AddConsumeEndMessage(character* Eater) const
317 {
318   switch(GetConsumeEndMessage())
319   {
320    case CEM_SCHOOL_FOOD: Eater->AddSchoolFoodConsumeEndMessage(); break;
321    case CEM_BONE: Eater->AddBoneConsumeEndMessage(); break;
322    case CEM_FROG_FLESH: Eater->AddFrogFleshConsumeEndMessage(); break;
323    case CEM_OMMEL: Eater->AddOmmelConsumeEndMessage(); break;
324    case CEM_PEPSI: Eater->AddPepsiConsumeEndMessage(); break;
325    case CEM_KOBOLD_FLESH: Eater->AddKoboldFleshConsumeEndMessage(); break;
326    case CEM_HEALING_LIQUID: Eater->AddHealingLiquidConsumeEndMessage(); break;
327    case CEM_ANTIDOTE: Eater->AddAntidoteConsumeEndMessage(); break;
328    case CEM_ESP: Eater->AddESPConsumeMessage(); break;
329    case CEM_HOLY_BANANA: Eater->AddHolyBananaConsumeEndMessage(); break;
330    case CEM_PEA_SOUP: Eater->AddPeaSoupConsumeEndMessage(); break;
331    case CEM_BLACK_UNICORN_FLESH:
332     Eater->AddBlackUnicornConsumeEndMessage();
333     break;
334    case CEM_GRAY_UNICORN_FLESH:
335     Eater->AddGrayUnicornConsumeEndMessage();
336     break;
337    case CEM_WHITE_UNICORN_FLESH:
338     Eater->AddWhiteUnicornConsumeEndMessage();
339     break;
340    case CEM_OMMEL_BONE: Eater->AddOmmelBoneConsumeEndMessage(); break;
341    case CEM_COCA_COLA: Eater->AddCocaColaConsumeEndMessage(); break;
342    case CEM_LIQUID_HORROR: Eater->AddLiquidHorrorConsumeEndMessage(); break;
343   }
344 }
345 
SpawnAndLoad(inputfile & SaveFile) const346 material* materialprototype::SpawnAndLoad(inputfile& SaveFile) const
347 {
348   material* Material = Spawner(0, 0, true);
349   Material->Load(SaveFile);
350   return Material;
351 }
352 
MakeMaterial(int Config,long Volume)353 material* material::MakeMaterial(int Config, long Volume)
354 {
355   if(!Config)
356     return 0;
357 
358   switch(Config >> 12)
359   {
360    case SOLID_ID >> 12: return solid::Spawn(Config, Volume);
361    case ORGANIC_ID >> 12: return organic::Spawn(Config, Volume);
362    case GAS_ID >> 12: return gas::Spawn(Config, Volume);
363    case LIQUID_ID >> 12: return liquid::Spawn(Config, Volume);
364    case FLESH_ID >> 12: return flesh::Spawn(Config, Volume);
365    case POWDER_ID >> 12: return powder::Spawn(Config, Volume);
366    case IRON_ALLOY_ID >> 12: return ironalloy::Spawn(Config, Volume);
367   }
368 
369   ABORT("Odd material configuration number %d of volume %ld requested!",
370         Config, Volume);
371   return 0;
372 }
373 
SetVolume(long What)374 void material::SetVolume(long What)
375 {
376   Volume = What;
377 
378   if(MotherEntity)
379     MotherEntity->SignalVolumeAndWeightChange();
380 }
381 
Initialize(int NewConfig,long InitVolume,truth Load)382 void material::Initialize(int NewConfig, long InitVolume, truth Load)
383 {
384   if(!Load)
385   {
386     databasecreator<material>::InstallDataBase(this, NewConfig);
387     Volume = InitVolume;
388     PostConstruct();
389   }
390 }
391 
GetTotalNutritionValue() const392 long material::GetTotalNutritionValue() const
393 {
394   return GetNutritionValue() * GetVolume() / 50;
395 }
396 
CanBeEatenByAI(ccharacter * Eater) const397 truth material::CanBeEatenByAI(ccharacter* Eater) const
398 {
399   return ((Eater->GetAttribute(WISDOM) < GetConsumeWisdomLimit()
400            || (Eater->IsAlcoholic() && (GetCategoryFlags() & IS_BEVERAGE)))
401           && !GetSpoilLevel() && !Eater->CheckCannibalism(this));
402 }
403 
BreatheEffect(character * Enemy)404 truth material::BreatheEffect(character* Enemy)
405 {
406   return Effect(Enemy, TORSO_INDEX, Max<long>(GetVolume() / 10, 50));
407 }
408 
CauseExplosion(character * Idiot,long Damage)409 truth material::CauseExplosion(character* Idiot, long Damage)
410 {
411   lsquare* Square = Idiot->GetLSquareUnder();
412 
413   if(!Damage)
414     return false;
415 
416   if(IsExplosive())
417   {
418     if(Idiot->IsPlayer())
419         ADD_MESSAGE("Suddenly you are engulfed in flames!");
420     else if(Idiot->CanBeSeenByPlayer())
421       ADD_MESSAGE("%s steps in %s.", Idiot->CHAR_NAME(DEFINITE), CHAR_NAME(INDEFINITE));
422     else if(Square->CanBeSeenByPlayer(true))
423       ADD_MESSAGE("Something explodes!");
424 
425     Square->GetLevel()->Explosion(0, "killed in a gas explosion", Square->GetPos(), Damage);
426     return true;
427   }
428 
429   return false;
430 }
431 
ExplosiveEffect(character * Enemy)432 truth material::ExplosiveEffect(character* Enemy)
433 {
434   return CauseExplosion(Enemy, Max<long>(GetVolume() / 6, 10));
435 }
436 
GetDataBase(int Config)437 const materialdatabase* material::GetDataBase(int Config)
438 {
439   const prototype* Proto = 0;
440 
441   switch(Config >> 12)
442   {
443    case SOLID_ID >> 12: Proto = &solid::ProtoType; break;
444    case ORGANIC_ID >> 12: Proto = &organic::ProtoType; break;
445    case GAS_ID >> 12: Proto = &gas::ProtoType; break;
446    case LIQUID_ID >> 12: Proto = &liquid::ProtoType; break;
447    case FLESH_ID >> 12: Proto = &flesh::ProtoType; break;
448    case POWDER_ID >> 12: Proto = &powder::ProtoType; break;
449    case IRON_ALLOY_ID >> 12: Proto = &ironalloy::ProtoType; break;
450   }
451 
452   const database* DataBase;
453   databasecreator<material>::FindDataBase(DataBase, Proto, Config);
454 
455   if(DataBase)
456     return DataBase;
457 
458   ABORT("Odd material configuration number %d requested!", Config);
459   return 0;
460 }
461 
FinishConsuming(character * Consumer)462 void material::FinishConsuming(character* Consumer)
463 {
464   if(!Consumer->IsPlayer() && GetConsumeWisdomLimit() != NO_LIMIT)
465     Consumer->EditExperience(WISDOM, 150, 1 << 13); /** C **/
466 
467   AddConsumeEndMessage(Consumer);
468 }
469 
InitDefaults(const materialprototype * NewProtoType,int NewConfig)470 void materialdatabase::InitDefaults(const materialprototype* NewProtoType, int NewConfig)
471 {
472   ProtoType = NewProtoType;
473   DigProductMaterial = Config = NewConfig;
474   CommonFlags |= IS_ABSTRACT; // dummy value for configcontainer
475 }
476 
CreateNaturalForm(int Config,long Volume)477 item* material::CreateNaturalForm(int Config, long Volume)
478 {
479   item* Item = GetDataBase(Config)->NaturalForm.Instantiate(NO_MATERIALS
480                                                             |NO_PIC_UPDATE);
481   Item->InitMaterials(MAKE_MATERIAL(Config, Volume));
482   return Item;
483 }
484 
CreateNaturalForm(long Volume) const485 item* material::CreateNaturalForm(long Volume) const
486 {
487   item* Item = GetNaturalForm().Instantiate(NO_MATERIALS|NO_PIC_UPDATE);
488   Item->InitMaterials(SpawnMore(Volume));
489   return Item;
490 }
491 
GetHardenedMaterial(citem * Item) const492 int material::GetHardenedMaterial(citem* Item) const
493 {
494   const materialdatabase* DB = DataBase;
495 
496   if(!Item->FlexibilityIsEssential())
497     return DB->HardenedMaterial;
498 
499   while(DB->HardenedMaterial != NONE)
500   {
501     DB = material::GetDataBase(DB->HardenedMaterial);
502 
503     if(DataBase->Flexibility <= DB->Flexibility)
504       return DB->Config;
505   }
506 
507   return DB->HardenedMaterial;
508 }
509 
GetSoftenedMaterial(citem * Item) const510 int material::GetSoftenedMaterial(citem* Item) const
511 {
512   const materialdatabase* DB = DataBase;
513   return DB->SoftenedMaterial;
514 }
515 
GetHardenModifier(citem * Item) const516 int material::GetHardenModifier(citem* Item) const
517 {
518   int M = GetFlexibility() << 2;
519 
520   if(!Item || !Item->FlexibilityIsEssential())
521     M += GetStrengthValue();
522 
523   return M;
524 }
525 
IsExplosive() const526 truth material::IsExplosive() const
527 {
528   return DataBase->InteractionFlags & CAN_EXPLODE;
529 }
530 
IsSparkling() const531 truth material::IsSparkling() const
532 {
533   return DataBase->CategoryFlags & IS_SPARKLING;
534 }
535 
IsStuckTo(ccharacter * Char) const536 truth material::IsStuckTo(ccharacter* Char) const
537 {
538   return MotherEntity->IsStuckTo(Char);
539 }
540