unnecessary messing with code
[k8-i-v-a-n.git] / src / game / fluid.cpp
blob9f61a511e511b1b2fc9295559694a798d0182653
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
12 /* Compiled through materset.cpp */
14 /* Used to determine how pixels are distributed when fluid over bodyarmors is shown */
16 const sLong fluid::BodyArmorPartPixels[] = { 34, 7, 7, 8, 6, 6 };
18 fluid::fluid () : entity(HAS_BE), Next(0), MotherItem(0), GearImage(0)
23 fluid::fluid (liquid *Liquid, lsquare *LSquareUnder) :
24 entity(HAS_BE),
25 Next(0),
26 Liquid(Liquid),
27 LSquareUnder(LSquareUnder),
28 MotherItem(0),
29 Image(false),
30 GearImage(0),
31 Flags(0)
33 TrapData.TrapID = game::CreateNewTrapID(this);
34 TrapData.VictimID = 0;
35 Image.ShadowPos = ZERO_V2;
36 Liquid->SetMotherEntity(this);
37 Emitation = Liquid->GetEmitation();
38 AddLiquid(Liquid->GetVolume());
42 fluid::fluid(liquid *Liquid, item *MotherItem, cfestring &LocationName, truth IsInside) :
43 entity(HAS_BE),
44 Next(0),
45 Liquid(Liquid),
46 LSquareUnder(0),
47 MotherItem(MotherItem),
48 Image(false),
49 GearImage(0),
50 Flags(0),
51 LocationName(LocationName)
53 TrapData.TrapID = 0;
54 if (UseImage()) {
55 Image.Picture->InitRandMap();
56 Image.Picture->InitPriorityMap(AVERAGE_PRIORITY);
57 Image.ShadowPos = MotherItem->GetBitmapPos(0);
58 Image.SpecialFlags = MotherItem->GetSpecialFlags();
60 if (IsInside) Flags |= FLUID_INSIDE;
61 Liquid->SetMotherEntity(this);
62 Emitation = Liquid->GetEmitation();
63 AddLiquid(Liquid->GetVolume());
67 fluid::~fluid () {
68 game::RemoveTrapID(TrapData.TrapID);
69 delete Liquid;
70 delete [] GearImage;
74 void fluid::AddLiquid (sLong Volume) {
75 sLong Pixels = Volume>>2;
76 if (Pixels && UseImage()) {
77 col16 Col = Liquid->GetColor();
78 if (MotherItem) {
79 pixelpredicate Pred = MotherItem->GetFluidPixelAllowedPredicate();
80 Image.AddLiquidToPicture(MotherItem->GetRawPicture(), Pixels, 225, Col, Pred);
81 if (GearImage) {
82 if (Flags & HAS_BODY_ARMOR_PICTURES) {
83 for (int c = 0; c < BODY_ARMOR_PARTS; ++c) {
84 GearImage[c].AddLiquidToPicture(igraph::GetHumanoidRawGraphic(), Pixels*BodyArmorPartPixels[c]/HUMAN_BODY_ARMOR_PIXELS, Image.AlphaAverage, Col, Pred);
86 } else {
87 GearImage->AddLiquidToPicture(igraph::GetHumanoidRawGraphic(), Pixels, Image.AlphaAverage, Col, Pred);
90 } else {
91 Image.AddLiquidToPicture(0, Pixels, 225, Col, 0);
97 void fluid::AddLiquidAndVolume (sLong Volume) {
98 AddLiquid(Volume);
99 Liquid->SetVolumeNoSignals(Liquid->GetVolume()+Volume);
103 void fluid::Be () {
104 sLong Rand = RAND();
105 if (!(Rand & 7)) {
106 if (MotherItem) {
107 if (MotherItem->Exists() && MotherItem->AllowFluidBe()) Liquid->TouchEffect(MotherItem, LocationName);
108 } else {
109 Liquid->TouchEffect(LSquareUnder->GetGLTerrain());
110 if (LSquareUnder->GetOLTerrain()) Liquid->TouchEffect(LSquareUnder->GetOLTerrain());
111 if (LSquareUnder->GetCharacter()) LSquareUnder->GetCharacter()->StayOn(Liquid);
114 if (MotherItem ? !(Rand & 15) && MotherItem->Exists() && MotherItem->AllowFluidBe() : !(Rand & 63)) {
115 sLong OldVolume = Liquid->GetVolume();
116 sLong NewVolume = ((OldVolume<<6)-OldVolume)>>6;
117 Liquid->SetVolumeNoSignals(NewVolume);
118 if (UseImage()) {
119 while(NewVolume < Image.AlphaSum >> 6 && FadePictures());
121 if (!MotherItem) {
122 LSquareUnder->SendNewDrawRequest();
123 LSquareUnder->SendMemorizedUpdateRequest();
125 if (!Liquid->GetVolume()) Destroy();
130 void fluid::Save (outputfile &SaveFile) const {
131 SaveFile << TrapData << Liquid;
132 SaveFile << LocationName << Flags;
133 Image.Save(SaveFile);
134 if (GearImage) {
135 SaveFile.Put(true);
136 int Images = Flags & HAS_BODY_ARMOR_PICTURES ? BODY_ARMOR_PARTS : 1;
137 for (int c = 0; c < Images; ++c) GearImage[c].Save(SaveFile);
138 } else {
139 SaveFile.Put(false);
144 void fluid::Load (inputfile &SaveFile) {
145 LSquareUnder = static_cast<lsquare *>(game::GetSquareInLoad());
146 SaveFile >> TrapData;
147 game::AddTrapID(this, TrapData.TrapID);
148 Liquid = static_cast<liquid *>(ReadType(material *, SaveFile));
149 Liquid->SetMotherEntity(this);
150 Emitation = Liquid->GetEmitation();
151 SaveFile >> LocationName >> Flags;
152 Image.Load(SaveFile);
153 if (SaveFile.Get()) {
154 int Images = Flags & HAS_BODY_ARMOR_PICTURES ? BODY_ARMOR_PARTS : 1;
155 GearImage = new imagedata[Images];
156 for (int c = 0; c < Images; ++c) {
157 GearImage[c].Load(SaveFile);
158 GearImage[c].Picture->InitRandMap();
159 GearImage[c].Picture->CalculateRandMap();
165 void fluid::SimpleDraw (blitdata &BlitData) const {
166 Image.Picture->AlphaLuminanceBlit(BlitData);
170 void fluid::Draw (blitdata &BlitData) const {
171 if (!UseImage()) return;
172 bitmap *Picture = Image.Picture;
173 int SpecialFlags = MotherItem ? MotherItem->GetSpecialFlags() : 0;
174 if (SpecialFlags & 0x7) {
175 /* Priority Bug!!! */
176 Picture->BlitAndCopyAlpha(igraph::GetFlagBuffer(), SpecialFlags);
177 igraph::GetFlagBuffer()->AlphaLuminanceBlit(BlitData);
178 } else {
179 Picture->AlphaPriorityBlit(BlitData);
181 if (MotherItem && BlitData.CustomData & ALLOW_ANIMATE) Image.Animate(BlitData, SpecialFlags);
185 outputfile &operator << (outputfile &SaveFile, const fluid *Fluid) {
186 Fluid->Save(SaveFile);
187 return SaveFile;
191 inputfile &operator >> (inputfile &SaveFile, fluid *&Fluid) {
192 Fluid = new fluid;
193 Fluid->Load(SaveFile);
194 return SaveFile;
198 /* If fluid has decreased, fade, otherwise add new pixels */
199 void fluid::SignalVolumeAndWeightChange () {
200 sLong Volume = Liquid->GetVolume();
201 if (UseImage()) {
202 if (Volume < Image.AlphaSum >> 6) {
203 while (FadePictures() && Volume < Image.AlphaSum>>6);
204 } else {
205 AddLiquid(Volume-(Image.AlphaSum>>6));
211 truth fluid::FadePictures () {
212 truth Change = Image.Fade();
213 if (GearImage) {
214 int Images = Flags & HAS_BODY_ARMOR_PICTURES ? BODY_ARMOR_PARTS : 1;
215 for (int c = 0; c < Images; ++c) GearImage[c].Fade();
217 return Change;
221 /* Used by lookmode, item descriptions etc. If there are 1-3 members in
222 Fluid, list them all, otherwise say "a lot of liquids". If there are
223 several types of blood in the list, they are counted as one. */
224 void fluid::AddFluidInfo (const fluid *Fluid, festring &String) {
225 liquid *LiquidStack[4];
226 liquid **Show = LiquidStack+1;
227 int Index = 0;
228 truth Blood = false, OneBlood = true;
229 for (; Fluid; Fluid = Fluid->Next) {
230 liquid *Liquid = Fluid->GetLiquid();
231 truth LiquidBlood = Liquid->GetCategoryFlags() & IS_BLOOD;
232 if (!LiquidBlood || !Blood) {
233 if (Index < 3) {
234 if (LiquidBlood) {
235 --Show;
236 Show[0] = Liquid;
237 ++Index;
238 } else {
239 Show[Index++] = Liquid;
241 } else {
242 ++Index;
243 break;
246 if (LiquidBlood) {
247 if (Blood) OneBlood = false; else Blood = true;
250 if (Index <= 3) {
251 if (!Blood || OneBlood) String << Show[0]->GetName(false, false);
252 else String << "different types of blood";
253 if (Index == 2) String << " and " << Show[1]->GetName(false, false);
254 else if (Index == 3) String << ", " << Show[1]->GetName(false, false) << " and " << Show[2]->GetName(false, false);
255 } else {
256 String << "a lot of liquids";
261 /* Used only when loading fluids. Correcting RandMap here is somewhat a gum solution. */
262 void fluid::SetMotherItem (item *What) {
263 MotherItem = What;
264 if (UseImage()) {
265 Image.Picture->InitRandMap();
266 Image.Picture->CalculateRandMap();
271 /* Ensures the gear pictures are correct after this. ShadowPos is the armor's
272 or weapon's BitmapPos in humanoid.pcx. SpecialFlags should include drawing
273 information of the bodypart in question (ST_RIGHT_ARM etc). BodyArmor should
274 be true iff the picture is part of a body armor, for instance armor covering
275 one's shoulder. */
276 void fluid::CheckGearPicture (v2 ShadowPos, int SpecialFlags, truth BodyArmor) {
277 if (!UseImage()) return;
278 if (BodyArmor && !(Flags & HAS_BODY_ARMOR_PICTURES)) {
279 Flags |= HAS_BODY_ARMOR_PICTURES;
280 delete [] GearImage;
281 GearImage = 0;
282 } else if (!BodyArmor && Flags & HAS_BODY_ARMOR_PICTURES) {
283 Flags &= ~HAS_BODY_ARMOR_PICTURES;
284 delete [] GearImage;
285 GearImage = 0;
287 imagedata *ImagePtr;
288 sLong Pixels;
289 if (BodyArmor) {
290 int Index = (SpecialFlags & 0x38) >> 3;
291 if (Index >= BODY_ARMOR_PARTS) Index = 0;
292 if (GearImage) {
293 if (GearImage[Index].ShadowPos != ShadowPos) GearImage[Index].Clear(false);
294 else return; // the picture already exists and is correct
295 } else {
296 GearImage = new imagedata[BODY_ARMOR_PARTS];
297 for (int c = 0; c < BODY_ARMOR_PARTS; ++c) new (&GearImage[c]) imagedata(false);
299 ImagePtr = &GearImage[Index];
300 Pixels = (Image.AlphaSum * BodyArmorPartPixels[Index] / HUMAN_BODY_ARMOR_PIXELS) >> 10;
301 } else {
302 if (GearImage) {
303 if (GearImage->ShadowPos != ShadowPos) GearImage->Clear(false);
304 else return; // the picture already exists and is correct
305 } else {
306 GearImage = new imagedata[1];
307 new(GearImage) imagedata(false);
309 ImagePtr = GearImage;
310 Pixels = Image.AlphaSum >> 10;
312 ImagePtr->ShadowPos = ShadowPos;
313 ImagePtr->SpecialFlags = SpecialFlags;
314 ImagePtr->Picture->InitRandMap();
315 ImagePtr->Picture->InitPriorityMap(AVERAGE_PRIORITY);
316 if (Pixels)
317 ImagePtr->AddLiquidToPicture(igraph::GetHumanoidRawGraphic(),
318 Pixels,
319 Image.AlphaAverage,
320 Liquid->GetColor(),
321 MotherItem->GetFluidPixelAllowedPredicate());
325 void fluid::DrawGearPicture (blitdata &BlitData, int SpecialFlags) const {
326 if (!UseImage()) return;
327 bitmap *Picture = GearImage->Picture;
328 if (SpecialFlags & 0x7) {
329 Picture->BlitAndCopyAlpha(igraph::GetFlagBuffer(), SpecialFlags);
330 igraph::GetFlagBuffer()->AlphaPriorityBlit(BlitData);
331 } else {
332 GearImage->Picture->AlphaPriorityBlit(BlitData);
334 if (BlitData.CustomData & ALLOW_ANIMATE) GearImage->Animate(BlitData, SpecialFlags);
338 void fluid::DrawBodyArmorPicture (blitdata &BlitData, int SpecialFlags) const {
339 if (!UseImage()) return;
340 /* We suppose body armor pictures are never rotated */
341 int Index = (SpecialFlags & 0x38) >> 3;
342 if (Index >= BODY_ARMOR_PARTS) Index = 0;
343 GearImage[Index].Picture->AlphaPriorityBlit(BlitData);
344 if (BlitData.CustomData & ALLOW_ANIMATE) GearImage[Index].Animate(BlitData, 0);
348 truth fluid::imagedata::Fade () {
349 return ShadowPos != ERROR_V2 ? Picture->Fade(AlphaSum, AlphaAverage, 1) : false;
353 /* Let two pixels fall every now and then over the picture. CurrentFlags
354 should include rotation info. */
355 void fluid::imagedata::Animate (blitdata &BlitData, int CurrentFlags) const {
356 if (!AlphaSum) return;
357 if (!DripTimer) {
358 DripPos = Picture->RandomizePixel();
359 /* DripPos != ERROR_V2 since AlphaSum != 0 */
360 if (DripPos.Y <= 12) {
361 DripColor = Picture->GetPixel(DripPos);
362 DripAlpha = Picture->GetAlpha(DripPos);
363 } else {
364 ++DripTimer;
367 if (DripTimer <= 0) {
368 v2 TrueDripPos = DripPos;
369 Rotate(TrueDripPos, 16, CurrentFlags);
370 TrueDripPos.Y -= DripTimer;
371 if (TrueDripPos.Y < 16) {
372 BlitData.Bitmap->AlphaPutPixel(TrueDripPos+BlitData.Dest, DripColor, BlitData.Luminance, DripAlpha);
374 if (TrueDripPos.Y < 15) {
375 ++TrueDripPos.Y;
376 BlitData.Bitmap->AlphaPutPixel(TrueDripPos+BlitData.Dest, DripColor, BlitData.Luminance, DripAlpha);
377 } else {
378 DripTimer = Min<sLong>(RAND()%(500000/AlphaSum), 25000);
381 --DripTimer;
385 fluid::imagedata::imagedata (truth Load) :
386 Picture(0),
387 DripTimer(0),
388 AlphaSum(0),
389 ShadowPos(ERROR_V2)
391 if (!Load) {
392 Picture = new bitmap(TILE_V2, TRANSPARENT_COLOR);
393 Picture->ActivateFastFlag();
394 Picture->CreateAlphaMap(0);
399 fluid::imagedata::~imagedata () {
400 delete Picture;
404 void fluid::imagedata::Save (outputfile &SaveFile) const {
405 SaveFile << Picture << AlphaSum << ShadowPos << (int)SpecialFlags;
409 void fluid::imagedata::Load (inputfile &SaveFile) {
410 SaveFile >> Picture >> AlphaSum >> ShadowPos >> (int&)SpecialFlags;
414 /* Shadow and this->ShadowPos specify the location of the raw image of
415 MotherItem of which shape the pixels are bound. If the image is meant
416 to be drawn on the ground, Shadow should be zero. The alphas of the
417 pixels will be on average AlphaSuggestion. PixelPredicate is used
418 to determine whether pixels of the Shadow are allowed to be covered
419 by the fluid. It is not used if Shadow == 0. */
420 void fluid::imagedata::AddLiquidToPicture (const rawbitmap *Shadow, sLong Pixels, sLong AlphaSuggestion, col16 Color, pixelpredicate PixelPredicate) {
421 if (ShadowPos == ERROR_V2) return;
422 DripTimer = 0;
423 cint *ValidityMap = igraph::GetBodyBitmapValidityMap(SpecialFlags);
424 v2 PixelAllowed[256];
425 int PixelsAllowed = 0;
426 if (Shadow) {
427 for (int x = 1; x < 14; ++x) {
428 for (int y = 1; y < 14; ++y) {
429 if (ValidityMap[x] & (1 << y) && !(Shadow->*PixelPredicate)(ShadowPos + v2(x, y))) {
430 PixelAllowed[PixelsAllowed++] = v2(x, y);
434 if (!PixelsAllowed) return;
437 sLong Lumps = Pixels-(Pixels<<3)/9; // ceil[Pixels/9]
438 sLong RoomForPixels = (Lumps<<3)+Lumps;
439 int Red = GetRed16(Color);
440 int Green = GetGreen16(Color);
441 int Blue = GetBlue16(Color);
442 if (AlphaSuggestion < 25) AlphaSuggestion = 25;
443 for (sLong c = 0; c < Lumps; ++c) {
444 v2 Cords;
445 if (Shadow) Cords = PixelAllowed[RAND()%PixelsAllowed];
446 else Cords = v2(1+RAND()%14, 1+RAND()%14);
447 Picture->PutPixel(Cords, Color);
448 sLong Alpha = Limit<sLong>(AlphaSuggestion-25+RAND()%50, 0, 0xFF);
449 AlphaSum += Alpha-Picture->GetAlpha(Cords);
450 Picture->SetAlpha(Cords, Alpha);
451 Picture->SafeUpdateRandMap(Cords, true);
452 --Pixels;
453 --RoomForPixels;
454 for (int d = 0; d < 8; ++d) {
455 if (Pixels > RAND()%RoomForPixels) {
456 v2 Pos = Cords+game::GetMoveVector(d);
457 if (!Shadow || (!(Shadow->*PixelPredicate)(ShadowPos + Pos) && ValidityMap[Pos.X] & (1 << Pos.Y))) {
458 --Pixels;
459 Picture->PutPixel(Pos, MakeRGB16(Limit<int>(Red - 25 + RAND() % 51, 0, 0xFF),
460 Limit<int>(Green - 25 + RAND() % 51, 0, 0xFF),
461 Limit<int>(Blue - 25 + RAND() % 51, 0, 0xFF)));
462 sLong Alpha = Limit<sLong>(AlphaSuggestion - 25 + RAND() % 50, 0, 0xFF);
463 AlphaSum += Alpha - Picture->GetAlpha(Pos);
464 Picture->SetAlpha(Pos, Alpha);
465 Picture->SafeUpdateRandMap(Pos, true);
466 if (!Pixels) break; // implies c + 1 == Lumps
469 --RoomForPixels;
472 AlphaAverage = Picture->CalculateAlphaAverage();
477 /* Remakes all images. Usually decreases, and never increases, the liquid's volume */
478 void fluid::Redistribute () {
479 if (!UseImage()) return;
480 truth InitRandMap = truth(MotherItem);
481 Image.Clear(InitRandMap);
482 if (GearImage) {
483 if (Flags & HAS_BODY_ARMOR_PICTURES) {
484 for (int c = 0; c < BODY_ARMOR_PARTS; ++c) GearImage[c].Clear(InitRandMap);
485 } else {
486 GearImage->Clear(InitRandMap);
489 AddLiquid(Liquid->GetVolume());
493 void fluid::imagedata::Clear (truth InitRandMap) {
494 Picture->ClearToColor(TRANSPARENT_COLOR);
495 Picture->FillAlpha(0);
496 AlphaSum = 0;
497 if (InitRandMap) Picture->InitRandMap();
501 material *fluid::RemoveMaterial (material *) {
502 Destroy();
503 return 0;
507 void fluid::Destroy () {
508 if (MotherItem) {
509 if (!MotherItem->Exists()) return;
510 MotherItem->RemoveFluid(this);
511 } else {
512 character *Char = game::SearchCharacter(GetVictimID());
513 if (Char) Char->RemoveTrap(GetTrapID());
514 TrapData.VictimID = 0;
515 LSquareUnder->RemoveFluid(this);
517 SendToHell();
521 truth fluid::UseImage () const {
522 return !(Flags & FLUID_INSIDE) && (!MotherItem || MotherItem->ShowFluids());
526 void fluid::AddTrapName (festring &String, int) const {
527 Liquid->AddName(String, false, false);
531 truth fluid::TryToUnStick (character *Victim, v2) {
532 feuLong TrapID = GetTrapID();
533 int Sum = Victim->GetAttribute(ARM_STRENGTH) + Victim->GetAttribute(LEG_STRENGTH) + Victim->GetAttribute(DEXTERITY) + Victim->GetAttribute(AGILITY);
534 int Modifier = Liquid->GetStickiness() * Liquid->GetVolume() / (Max(Sum, 1) * 500);
535 if (!RAND_N(Max(Modifier, 2))) {
536 Victim->RemoveTrap(TrapID);
537 TrapData.VictimID = 0;
538 if (Victim->IsPlayer())
539 ADD_MESSAGE("You manage to unstick yourself from the %s.", Liquid->GetName(false, false).CStr());
540 else if(Victim->CanBeSeenByPlayer())
541 ADD_MESSAGE("%s manages to unstick %sself from the %s.", Victim->CHAR_NAME(DEFINITE), Victim->CHAR_OBJECT_PRONOUN, Liquid->GetName(false, false).CStr());
542 Victim->EditAP(-250);
543 return true;
545 Modifier = Sum * 10000 / (Liquid->GetStickiness() * Liquid->GetVolume());
546 if (!RAND_N(Max(Modifier, 2))) {
547 int VictimBodyPart = Victim->RandomizeTryToUnStickBodyPart(ALL_BODYPART_FLAGS&~TrapData.BodyParts);
548 if (VictimBodyPart != NONE_INDEX) {
549 TrapData.BodyParts |= 1 << VictimBodyPart;
550 Victim->AddTrap(GetTrapID(), 1 << VictimBodyPart);
551 if (Victim->IsPlayer())
552 ADD_MESSAGE("You fail to free yourself from the %s and your %s is stuck in it in the attempt.", Liquid->GetName(false, false).CStr(), Victim->GetBodyPartName(VictimBodyPart).CStr());
553 else if(Victim->CanBeSeenByPlayer())
554 ADD_MESSAGE("%s tries to free %sself from the %s but is stuck more tightly in it in the attempt.", Victim->CHAR_NAME(DEFINITE), Victim->CHAR_OBJECT_PRONOUN, Liquid->GetName(false, false).CStr());
555 Victim->EditAP(-1000);
556 return true;
559 if (Victim->IsPlayer())
560 ADD_MESSAGE("You are unable to unstick yourself from %s.", Liquid->GetName(false, false).CStr());
561 Victim->EditAP(-1000);
562 return false;
566 void fluid::StepOnEffect (character *Stepper) {
567 if (!Liquid->GetStickiness() || Stepper->IsImmuneToStickiness()) return;
568 int StepperBodyPart = Stepper->GetRandomStepperBodyPart();
569 if (StepperBodyPart == NONE_INDEX) return;
570 TrapData.VictimID = Stepper->GetID();
571 TrapData.BodyParts = 1 << StepperBodyPart;
572 Stepper->AddTrap(GetTrapID(), 1 << StepperBodyPart);
573 if (Stepper->IsPlayer())
574 ADD_MESSAGE("Your %s is stuck to %s.", Stepper->GetBodyPartName(StepperBodyPart).CStr(), Liquid->GetName(false, false).CStr());
575 else if (Stepper->CanBeSeenByPlayer())
576 ADD_MESSAGE("%s gets stuck to %s.", Stepper->CHAR_NAME(DEFINITE), Liquid->GetName(false, false).CStr());
580 void fluid::PreProcessForBone () {
581 game::RemoveTrapID(TrapData.TrapID);
582 TrapData.TrapID = 0;
586 void fluid::PostProcessForBone () {
587 TrapData.TrapID = game::CreateNewTrapID(this);
591 truth fluid::IsStuckTo(ccharacter *Char) const {
592 return TrapData.VictimID == Char->GetID();
596 truth fluid::IsDangerous (ccharacter *Char) const {
597 return Char->GetAttribute(WISDOM) >= Liquid->GetStepInWisdomLimit();