'g'o should not miss items in corners anymore
[k8-i-v-a-n.git] / src / game / materia.cpp
blob82b63291528525bc5fc194d8a1bd402f2dd3b36a
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 /* Compiled through materset.cpp */
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); }
22 sLong material::GetRawPrice() const
23 { return GetPriceModifier() * GetWeight() / 10000; }
24 truth material::CanBeDug(material* ShovelMaterial) const
25 { return ShovelMaterial->GetStrengthValue() > GetStrengthValue(); }
26 sLong material::GetTotalExplosivePower() const
27 { return sLong(double(Volume) * GetExplosivePower() / 1000000); }
28 cchar* material::GetConsumeVerb() const { return "eating"; }
30 materialpredicate TrueMaterialPredicate = &material::True;
32 void material::AddName (festring &Name, truth Articled, truth Adjective) const {
33 if (Articled) {
34 if (GetNameFlags() & USE_AN) Name << "an "; else Name << "a ";
36 Name << (Adjective ? GetAdjectiveStem() : GetNameStem());
39 festring material::GetName(truth Articled, truth Adjective) const
41 festring Name;
42 Name.Empty();
43 AddName(Name, Articled, Adjective);
44 return Name;
47 material* material::TakeDipVolumeAway()
49 if(Volume > 500)
51 EditVolume(-500);
52 return SpawnMore(500);
54 else
55 return MotherEntity->RemoveMaterial(this);
58 material *material::TakeAllVolumeAway () {
59 return MotherEntity->RemoveMaterial(this);
62 void material::Save(outputfile& SaveFile) const
64 SaveFile << Volume;
65 SaveFile << (uShort)GetConfig();
68 void material::Load(inputfile& SaveFile)
70 SaveFile >> Volume;
71 databasecreator<material>::InstallDataBase(this, ReadType(uShort, SaveFile));
75 truth material::Effect (character *Char, int BodyPart, sLong Amount) {
76 /* Receivexxx should return truth! */
78 Amount = Amount*GetEffectStrength()/100;
80 if (!Amount) return false;
82 auto eff = GetEffect();
84 if (eff == EFFECT_POISON) Char->BeginTemporaryState(POISONED, Amount);
85 else if (eff == EFFECT_DARKNESS) Char->ReceiveDarkness(Amount);
86 else if (eff == EFFECT_OMMEL_URINE) Char->ReceiveOmmelUrine(Amount);
87 else if (eff == EFFECT_PEPSI) Char->ReceivePepsi(Amount);
88 else if (eff == EFFECT_KOBOLD_FLESH) Char->ReceiveKoboldFlesh(Amount);
89 else if (eff == EFFECT_HEAL) Char->ReceiveHeal(Amount);
90 else if (eff == EFFECT_LYCANTHROPY) { if (!Char->StateIsActivated(DISEASE_IMMUNITY)) Char->BeginTemporaryState(LYCANTHROPY, Amount); }
91 else if (eff == EFFECT_SCHOOL_FOOD) Char->ReceiveSchoolFood(Amount);
92 else if (eff == EFFECT_ANTIDOTE) Char->ReceiveAntidote(Amount);
93 else if (eff == EFFECT_CONFUSE) Char->BeginTemporaryState(CONFUSED, Amount);
94 else if (eff == EFFECT_POLYMORPH) Char->BeginTemporaryState(POLYMORPH, Amount);
95 else if (eff == EFFECT_ESP) Char->BeginTemporaryState(ESP, Amount);
96 else if (eff == EFFECT_SKUNK_SMELL) Char->BeginTemporaryState(POISONED, Amount);
97 else if (eff == EFFECT_MAGIC_MUSHROOM) {
98 v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
99 Char->ActivateRandomState(SRC_MAGIC_MUSHROOM, Amount, Volume%250+Pos.X+Pos.Y+1);
101 else if (eff == EFFECT_TRAIN_PERCEPTION) Char->EditExperience(PERCEPTION, Amount, 1<<14);
102 else if (eff == EFFECT_HOLY_BANANA) Char->ReceiveHolyBanana(Amount);
103 else if (eff == EFFECT_EVIL_WONDER_STAFF_VAPOUR) {
104 v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
105 Char->ActivateRandomState(SRC_EVIL, Amount, Volume%250+Pos.X+Pos.Y+1);
107 else if (eff == EFFECT_GOOD_WONDER_STAFF_VAPOUR) {
108 v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
109 Char->ActivateRandomState(SRC_GOOD, Amount, Volume%250+Pos.X+Pos.Y+1);
111 else if (eff == EFFECT_PEA_SOUP) Char->ReceivePeaSoup(Amount);
112 else if (eff == EFFECT_BLACK_UNICORN_FLESH) Char->ReceiveBlackUnicorn(Amount);
113 else if (eff == EFFECT_GRAY_UNICORN_FLESH) Char->ReceiveGrayUnicorn(Amount);
114 else if (eff == EFFECT_WHITE_UNICORN_FLESH) Char->ReceiveWhiteUnicorn(Amount);
115 else if (eff == EFFECT_TELEPORT_CONTROL) Char->BeginTemporaryState(TELEPORT_CONTROL, Amount);
116 else if (eff == EFFECT_MUSHROOM) {
117 v2 Pos = GetMotherEntity()->GetSquareUnderEntity()->GetPos();
118 Char->ActivateRandomState(SRC_MUSHROOM, Amount, Volume%250+Pos.X+Pos.Y+1);
120 else if (eff == EFFECT_OMMEL_CERUMEN) Char->ReceiveOmmelCerumen(Amount);
121 else if (eff == EFFECT_OMMEL_SWEAT) Char->ReceiveOmmelSweat(Amount);
122 else if (eff == EFFECT_OMMEL_TEARS) Char->ReceiveOmmelTears(Amount);
123 else if (eff == EFFECT_OMMEL_SNOT) Char->ReceiveOmmelSnot(Amount);
124 else if (eff == EFFECT_OMMEL_BONE) Char->ReceiveOmmelBone(Amount);
125 else if (eff == EFFECT_MUSTARD_GAS) Char->ReceiveMustardGas(BodyPart, Amount);
126 else if (eff == EFFECT_MUSTARD_GAS_LIQUID) Char->ReceiveMustardGasLiquid(BodyPart, Amount);
127 else if (eff == EFFECT_PANIC) { if (!Char->StateIsActivated(FEARLESS)) Char->BeginTemporaryState(PANIC, Amount); }
128 else if (eff == EFFECT_TELEPORT) Char->BeginTemporaryState(TELEPORT, Amount);
129 else if (eff == EFFECT_VAMPIRISM) { if (!Char->StateIsActivated(DISEASE_IMMUNITY)) Char->BeginTemporaryState(VAMPIRISM, Amount); }
130 else if (eff == EFFECT_DETECTING) Char->BeginTemporaryState(DETECTING, Amount);
131 else return false;
133 return true;
136 material* material::EatEffect(character* Eater, sLong Amount)
138 Amount = Volume > Amount ? Amount : Volume;
139 Eater->ReceiveNutrition(GetNutritionValue() * Amount / 50);
140 if(Amount && Volume)
142 if(DisablesPanicWhenConsumed() && Eater->TemporaryStateIsActivated(PANIC))
144 if(Eater->IsPlayer())
146 ADD_MESSAGE("You relax a bit.");
148 else if(Eater->CanBeSeenByPlayer())
150 ADD_MESSAGE("%s relaxes a bit.", Eater->CHAR_NAME(DEFINITE));
152 Eater->DeActivateTemporaryState(PANIC);
156 if(GetInteractionFlags() & AFFECT_INSIDE)
158 head* Head = Eater->GetVirtualHead();
159 sLong NewAmount = Amount;
161 if(Head && Amount >= 8)
163 Head->AddFluid(static_cast<liquid*>(SpawnMore(Amount >> 3)), CONST_S("throat"), 0, true);
164 NewAmount -= Amount >> 3;
167 Eater->GetTorso()->AddFluid(static_cast<liquid*>(SpawnMore(NewAmount)), CONST_S("stomach"), 0, true);
169 else
171 Effect(Eater, TORSO_INDEX, Amount);
173 if(IsLiquid())
174 Eater->EditStamina(int(50. * Amount * Eater->GetMaxStamina() / Eater->GetBodyVolume()),
175 false);
178 if(Volume != Amount)
180 EditVolume(-Amount);
181 return 0;
183 else
184 return MotherEntity->RemoveMaterial(this);
187 truth material::HitEffect(character* Enemy, bodypart* BodyPart)
189 if(!Volume)
190 return false;
192 auto mtp = GetHitMessage();
193 if (mtp == HM_SCHOOL_FOOD) Enemy->AddSchoolFoodHitMessage();
194 else if (mtp == HM_FROG_FLESH) Enemy->AddFrogFleshConsumeEndMessage();
195 else if (mtp == HM_OMMEL) Enemy->AddOmmelConsumeEndMessage();
196 else if (mtp == HM_PEPSI) Enemy->AddPepsiConsumeEndMessage();
197 else if (mtp == HM_KOBOLD_FLESH) Enemy->AddKoboldFleshHitMessage();
198 else if (mtp == HM_HEALING_LIQUID) Enemy->AddHealingLiquidConsumeEndMessage();
199 else if (mtp == HM_ANTIDOTE) Enemy->AddAntidoteConsumeEndMessage();
200 else if (mtp == HM_CONFUSE) Enemy->AddConfuseHitMessage();
201 else if (mtp == HM_HOLY_BANANA) Enemy->AddHolyBananaConsumeEndMessage();
202 else if (mtp == HM_HOLY_MANGO) Enemy->AddHolyMangoConsumeEndMessage();
203 else if (mtp == HM_ALIEN_FLESH) Enemy->AddAlienFleshConsumeEndMessage();
205 sLong Amount = Max<sLong>(GetVolume() >> 1, 1);
206 truth Success;
208 if(GetInteractionFlags() & AFFECT_INSIDE)
210 if(BodyPart)
212 BodyPart->AddFluid(static_cast<liquid*>(SpawnMore(Amount)),
213 CONST_S(""), 0, true);
214 Success = true;
216 else
217 Success = false;
219 else
221 int BPIndex = BodyPart ? BodyPart->GetBodyPartIndex() : NONE_INDEX;
222 Success = Effect(Enemy, BPIndex, Amount);
225 if(Amount != Volume)
226 EditVolume(-Amount);
227 else
228 delete MotherEntity->RemoveMaterial(this);
230 return Success;
234 void material::AddConsumeEndMessage (character *Eater) const {
235 auto mtp = GetConsumeEndMessage();
236 if (mtp == CEM_SCHOOL_FOOD) { Eater->AddSchoolFoodConsumeEndMessage(); return; }
237 if (mtp == CEM_BONE) { Eater->AddBoneConsumeEndMessage(); return; }
238 if (mtp == CEM_FROG_FLESH) { Eater->AddFrogFleshConsumeEndMessage(); return; }
239 if (mtp == CEM_OMMEL) { Eater->AddOmmelConsumeEndMessage(); return; }
240 if (mtp == CEM_PEPSI) { Eater->AddPepsiConsumeEndMessage(); return; }
241 if (mtp == CEM_KOBOLD_FLESH) { Eater->AddKoboldFleshConsumeEndMessage(); return; }
242 if (mtp == CEM_HEALING_LIQUID) { Eater->AddHealingLiquidConsumeEndMessage(); return; }
243 if (mtp == CEM_ANTIDOTE) { Eater->AddAntidoteConsumeEndMessage(); return; }
244 if (mtp == CEM_ESP) { Eater->AddESPConsumeMessage(); return; }
245 if (mtp == CEM_HOLY_BANANA) { Eater->AddHolyBananaConsumeEndMessage(); return; }
246 if (mtp == CEM_PEA_SOUP) { Eater->AddPeaSoupConsumeEndMessage(); return; }
247 if (mtp == CEM_BLACK_UNICORN_FLESH) { Eater->AddBlackUnicornConsumeEndMessage(); return; }
248 if (mtp == CEM_GRAY_UNICORN_FLESH) { Eater->AddGrayUnicornConsumeEndMessage(); return; }
249 if (mtp == CEM_WHITE_UNICORN_FLESH) { Eater->AddWhiteUnicornConsumeEndMessage(); return; }
250 if (mtp == CEM_OMMEL_BONE) { Eater->AddOmmelBoneConsumeEndMessage(); return; }
251 if (mtp == CEM_LIQUID_HORROR) { Eater->AddLiquidHorrorConsumeEndMessage(); return; }
252 if (mtp == CEM_HOLY_MANGO) { Eater->AddHolyMangoConsumeEndMessage(); return; }
253 if (mtp == CEM_ALIEN_FLESH) { Eater->AddAlienFleshConsumeEndMessage(); return; }
257 material *materialprototype::SpawnAndLoad (inputfile &SaveFile) const {
258 material *Material = Spawner(0, 0, true);
259 Material->Load(SaveFile);
260 return Material;
264 material *material::MakeMaterial (int Config, sLong Volume) {
265 if (!Config) return 0;
267 auto cfg = (Config>>12);
268 if (cfg == SOLID_ID >> 12) return solid::Spawn(Config, Volume);
269 if (cfg == ORGANIC_ID >> 12) return organic::Spawn(Config, Volume);
270 if (cfg == GAS_ID >> 12) return gas::Spawn(Config, Volume);
271 if (cfg == LIQUID_ID >> 12) return liquid::Spawn(Config, Volume);
272 if (cfg == FLESH_ID >> 12) return flesh::Spawn(Config, Volume);
273 if (cfg == POWDER_ID >> 12) return powder::Spawn(Config, Volume);
274 if (cfg == IRON_ALLOY_ID >> 12) return ironalloy::Spawn(Config, Volume);
276 ABORT("Odd material configuration number %d of volume %d requested!", Config, Volume);
277 return 0;
280 void material::SetVolume(sLong What)
282 Volume = What;
284 if(MotherEntity)
285 MotherEntity->SignalVolumeAndWeightChange();
288 void material::Initialize(int NewConfig, sLong InitVolume, truth Load)
290 if(!Load)
292 databasecreator<material>::InstallDataBase(this, NewConfig);
293 Volume = InitVolume;
294 PostConstruct();
298 sLong material::GetTotalNutritionValue() const
300 return GetNutritionValue() * GetVolume() / 50;
303 truth material::CanBeEatenByAI(ccharacter* Eater) const
305 return ((Eater->GetAttribute(WISDOM) < GetConsumeWisdomLimit()
306 || (Eater->IsAlcoholic() && (GetCategoryFlags() & IS_BEVERAGE)))
307 && !GetSpoilLevel() && !Eater->CheckCannibalism(this));
310 truth material::BreatheEffect(character* Enemy)
312 return Effect(Enemy, TORSO_INDEX, Max<sLong>(GetVolume() / 10, 50));
316 const materialdatabase* material::GetDataBase (int Config) {
317 const prototype *Proto = 0;
319 auto cfg = (Config >> 12);
320 if (cfg == SOLID_ID >> 12) Proto = &solid::ProtoType;
321 else if (cfg == ORGANIC_ID >> 12) Proto = &organic::ProtoType;
322 else if (cfg == GAS_ID >> 12) Proto = &gas::ProtoType;
323 else if (cfg == LIQUID_ID >> 12) Proto = &liquid::ProtoType;
324 else if (cfg == FLESH_ID >> 12) Proto = &flesh::ProtoType;
325 else if (cfg == POWDER_ID >> 12) Proto = &powder::ProtoType;
326 else if (cfg == IRON_ALLOY_ID >> 12) Proto = &ironalloy::ProtoType;
328 //FIXME: k8: check for nullptr `Proto`?
330 const database *DataBase;
331 databasecreator<material>::FindDataBase(DataBase, Proto, Config);
333 if (DataBase) return DataBase;
335 ABORT("Odd material configuration number %d requested!", Config);
336 return 0;
340 void material::FinishConsuming(character* Consumer)
342 if(!Consumer->IsPlayer() && GetConsumeWisdomLimit() != NO_LIMIT)
343 Consumer->EditExperience(WISDOM, 150, 1 << 13); /** C **/
345 AddConsumeEndMessage(Consumer);
348 void materialdatabase::InitDefaults (const materialprototype *NewProtoType, int NewConfig, cfestring &acfgstrname)
350 ProtoType = NewProtoType;
351 DigProductMaterial = Config = NewConfig;
352 CommonFlags |= IS_ABSTRACT; // dummy value for configcontainer
353 CfgStrName = acfgstrname;
356 item* material::CreateNaturalForm(int Config, sLong Volume)
358 item* Item = GetDataBase(Config)->NaturalForm.Instantiate(NO_MATERIALS
359 |NO_PIC_UPDATE);
360 Item->InitMaterials(MAKE_MATERIAL(Config, Volume));
361 return Item;
364 item* material::CreateNaturalForm(sLong Volume) const
366 item* Item = GetNaturalForm().Instantiate(NO_MATERIALS|NO_PIC_UPDATE);
367 Item->InitMaterials(SpawnMore(Volume));
368 return Item;
372 int material::GetHardenedMaterial (citem *Item) const {
373 const materialdatabase *DB = DataBase;
375 if (!Item->FlexibilityIsEssential()) return DB->HardenedMaterial;
376 while (DB->HardenedMaterial != NONE) {
377 DB = material::GetDataBase(DB->HardenedMaterial);
378 if (DataBase->Flexibility <= DB->Flexibility) return DB->Config;
380 return DB->HardenedMaterial;
384 int material::GetSoftenedMaterial (citem *Item) const {
385 const materialdatabase *DB = DataBase;
387 if (!Item->FlexibilityIsEssential()) return DB->SoftenedMaterial;
388 while (DB->SoftenedMaterial != NONE) {
389 DB = material::GetDataBase(DB->SoftenedMaterial);
390 if (DataBase->Flexibility <= DB->Flexibility) return DB->Config;
392 return DB->SoftenedMaterial;
396 int material::GetHardenModifier(citem* Item) const
398 int M = GetFlexibility() << 2;
400 if(!Item || !Item->FlexibilityIsEssential())
401 M += GetStrengthValue();
403 return M;
406 truth material::IsExplosive() const
408 return DataBase->InteractionFlags & CAN_EXPLODE;
411 truth material::IsSparkling() const
413 return DataBase->CategoryFlags & IS_SPARKLING;
416 truth material::IsStuckTo(ccharacter* Char) const
418 return MotherEntity->IsStuckTo(Char);