3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
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
) :
27 LSquareUnder(LSquareUnder
),
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
) :
47 MotherItem(MotherItem
),
51 LocationName(LocationName
)
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());
68 game::RemoveTrapID(TrapData
.TrapID
);
74 void fluid::AddLiquid (sLong Volume
) {
75 sLong Pixels
= Volume
>>2;
76 if (Pixels
&& UseImage()) {
77 col16 Col
= Liquid
->GetColor();
79 pixelpredicate Pred
= MotherItem
->GetFluidPixelAllowedPredicate();
80 Image
.AddLiquidToPicture(MotherItem
->GetRawPicture(), Pixels
, 225, Col
, Pred
);
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
);
87 GearImage
->AddLiquidToPicture(igraph::GetHumanoidRawGraphic(), Pixels
, Image
.AlphaAverage
, Col
, Pred
);
91 Image
.AddLiquidToPicture(0, Pixels
, 225, Col
, 0);
97 void fluid::AddLiquidAndVolume (sLong Volume
) {
99 Liquid
->SetVolumeNoSignals(Liquid
->GetVolume()+Volume
);
107 if (MotherItem
->Exists() && MotherItem
->AllowFluidBe()) Liquid
->TouchEffect(MotherItem
, LocationName
);
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
);
119 while(NewVolume
< Image
.AlphaSum
>> 6 && FadePictures());
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
);
136 int Images
= Flags
& HAS_BODY_ARMOR_PICTURES
? BODY_ARMOR_PARTS
: 1;
137 for (int c
= 0; c
< Images
; ++c
) GearImage
[c
].Save(SaveFile
);
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
);
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
);
191 inputfile
&operator >> (inputfile
&SaveFile
, fluid
*&Fluid
) {
193 Fluid
->Load(SaveFile
);
198 /* If fluid has decreased, fade, otherwise add new pixels */
199 void fluid::SignalVolumeAndWeightChange () {
200 sLong Volume
= Liquid
->GetVolume();
202 if (Volume
< Image
.AlphaSum
>> 6) {
203 while (FadePictures() && Volume
< Image
.AlphaSum
>>6);
205 AddLiquid(Volume
-(Image
.AlphaSum
>>6));
211 truth
fluid::FadePictures () {
212 truth Change
= Image
.Fade();
214 int Images
= Flags
& HAS_BODY_ARMOR_PICTURES
? BODY_ARMOR_PARTS
: 1;
215 for (int c
= 0; c
< Images
; ++c
) GearImage
[c
].Fade();
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;
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
) {
239 Show
[Index
++] = Liquid
;
247 if (Blood
) OneBlood
= false; else Blood
= true;
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);
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
) {
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
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
;
282 } else if (!BodyArmor
&& Flags
& HAS_BODY_ARMOR_PICTURES
) {
283 Flags
&= ~HAS_BODY_ARMOR_PICTURES
;
290 int Index
= (SpecialFlags
& 0x38) >> 3;
291 if (Index
>= BODY_ARMOR_PARTS
) Index
= 0;
293 if (GearImage
[Index
].ShadowPos
!= ShadowPos
) GearImage
[Index
].Clear(false);
294 else return; // the picture already exists and is correct
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;
303 if (GearImage
->ShadowPos
!= ShadowPos
) GearImage
->Clear(false);
304 else return; // the picture already exists and is correct
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
);
317 ImagePtr
->AddLiquidToPicture(igraph::GetHumanoidRawGraphic(),
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
);
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;
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
);
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) {
376 BlitData
.Bitmap
->AlphaPutPixel(TrueDripPos
+BlitData
.Dest
, DripColor
, BlitData
.Luminance
, DripAlpha
);
378 DripTimer
= Min
<sLong
>(RAND()%(500000/AlphaSum
), 25000);
385 fluid::imagedata::imagedata (truth Load
) :
392 Picture
= new bitmap(TILE_V2
, TRANSPARENT_COLOR
);
393 Picture
->ActivateFastFlag();
394 Picture
->CreateAlphaMap(0);
399 fluid::imagedata::~imagedata () {
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;
423 cint
*ValidityMap
= igraph::GetBodyBitmapValidityMap(SpecialFlags
);
424 v2 PixelAllowed
[256];
425 int PixelsAllowed
= 0;
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
) {
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);
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
))) {
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
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
);
483 if (Flags
& HAS_BODY_ARMOR_PICTURES
) {
484 for (int c
= 0; c
< BODY_ARMOR_PARTS
; ++c
) GearImage
[c
].Clear(InitRandMap
);
486 GearImage
->Clear(InitRandMap
);
489 AddLiquid(Liquid
->GetVolume());
493 void fluid::imagedata::Clear (truth InitRandMap
) {
494 Picture
->ClearToColor(TRANSPARENT_COLOR
);
495 Picture
->FillAlpha(0);
497 if (InitRandMap
) Picture
->InitRandMap();
501 material
*fluid::RemoveMaterial (material
*) {
507 void fluid::Destroy () {
509 if (!MotherItem
->Exists()) return;
510 MotherItem
->RemoveFluid(this);
512 character
*Char
= game::SearchCharacter(GetVictimID());
513 if (Char
) Char
->RemoveTrap(GetTrapID());
514 TrapData
.VictimID
= 0;
515 LSquareUnder
->RemoveFluid(this);
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);
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);
559 if (Victim
->IsPlayer())
560 ADD_MESSAGE("You are unable to unstick yourself from %s.", Liquid
->GetName(false, false).CStr());
561 Victim
->EditAP(-1000);
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
);
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();