moved almost all hardcoded constants to "define.dat"
[k8-i-v-a-n.git] / src / game / stack.cpp
blobac4e6af27914e9d1fc137292a776347418b0548a
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 slotset.cpp */
15 /* If REMEMBER_SELECTED flag is used, DrawContents() will use this to determine the initial selected item */
17 int stack::Selected;
20 stack::stack (square *MotherSquare, entity* MotherEntity, feuLong Flags) :
21 Bottom(0), Top(0), MotherSquare(MotherSquare), MotherEntity(MotherEntity),
22 Volume(0), Weight(0), Emitation(0), Flags(Flags), Items(0)
27 stack::~stack () { Clean(true); }
30 square *stack::GetSquareUnder () const { return !MotherEntity ? MotherSquare : MotherEntity->GetSquareUnderEntity(); }
33 /* Modifies the square index bits of BlitData.CustomData */
34 void stack::Draw (ccharacter *Viewer, blitdata &BlitData, int RequiredSquarePosition) const {
35 if (!Items) return;
36 int VisibleItems = 0;
37 v2 StackPos = GetPos();
38 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
39 if (i->GetSquarePosition() == RequiredSquarePosition && (i->CanBeSeenBy(Viewer) || game::GetSeeWholeMapCheatMode())) {
40 BlitData.CustomData |= i->GetSquareIndex(StackPos);
41 i->Draw(BlitData);
42 BlitData.CustomData &= ~SQUARE_INDEX_MASK;
43 ++VisibleItems;
46 if (RequiredSquarePosition == CENTER) {
47 truth PlusSymbol = VisibleItems > 1, Dangerous = NeedDangerSymbol(Viewer);
48 if (PlusSymbol || Dangerous) {
49 col24 L = BlitData.Luminance;
50 BlitData.Luminance = ivanconfig::GetContrastLuminance();
51 BlitData.Src.Y = 16;
52 if (PlusSymbol) igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
53 if (Dangerous) {
54 BlitData.Src.X = 160;
55 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
57 BlitData.Src.X = BlitData.Src.Y = 0; /// check
58 BlitData.Luminance = L;
64 void stack::AddItem (item *ToBeAdded, truth RunRoomEffects) {
65 if (!ToBeAdded) return;
66 AddElement(ToBeAdded);
67 if (Flags & HIDDEN) return;
68 lsquare *SquareUnder = GetLSquareTrulyUnder(ToBeAdded->GetSquarePosition());
69 if (!SquareUnder) return;
70 if (ToBeAdded->IsAnimated()) SquareUnder->IncStaticAnimatedEntities();
71 if (!game::IsGenerating()) {
72 if (RunRoomEffects && GetLSquareUnder()->GetRoom()) GetLSquareUnder()->GetRoom()->AddItemEffect(ToBeAdded);
73 SquareUnder->SendNewDrawRequest();
74 SquareUnder->SendMemorizedUpdateRequest();
79 void stack::RemoveItem (stackslot *Slot) {
80 item *Item = Slot->GetItem();
81 truth WasAnimated = Item->IsAnimated();
82 col24 Emit = Item->GetEmitation();
83 RemoveElement(Slot);
84 SignalVolumeAndWeightChange();
85 SignalEmitationDecrease(Item->GetSquarePosition(), Emit);
86 if (Flags & HIDDEN) return;
87 lsquare *SquareUnder = GetLSquareTrulyUnder(Item->GetSquarePosition());
88 if (Item->GetSquarePosition() != CENTER) Item->SignalSquarePositionChange(CENTER);
89 if (!SquareUnder) return;
90 if (WasAnimated) SquareUnder->DecStaticAnimatedEntities();
91 if (!game::IsGenerating()) {
92 SquareUnder->SendNewDrawRequest();
93 SquareUnder->SendMemorizedUpdateRequest();
98 /* Removes all items. LastClean should be true only if the stack is being deleted (the default is false) */
99 void stack::Clean (truth LastClean) {
100 if (!Items) return;
101 stackslot *Slot = Bottom;
102 if (!LastClean) {
103 Bottom = Top = 0;
104 Volume = Weight = Items = 0;
105 SignalVolumeAndWeightChange();
107 while (Slot) {
108 item *Item = Slot->GetItem();
109 if (!(Flags & HIDDEN) && Item->IsAnimated() && !LastClean) {
110 lsquare *Square = GetLSquareTrulyUnder(Item->GetSquarePosition());
111 if (Square) Square->DecStaticAnimatedEntities();
113 if (LastClean && Item->GetSquaresUnder() == 1) delete Item; else Item->SendToHell();
114 stackslot *Rubbish = Slot;
115 Slot = Slot->Next;
116 delete Rubbish;
117 if (!LastClean) SignalEmitationDecrease(Item->GetSquarePosition(), Item->GetEmitation());
122 void stack::Save (outputfile &SaveFile) const {
123 if (!Items) {
124 SaveFile << uShort(0);
125 return;
127 uShort SavedItems = 0;
128 for (stackiterator i1 = GetBottom(); i1.HasItem(); ++i1) if (i1->IsMainSlot(&i1.GetSlot())) ++SavedItems;
129 SaveFile << SavedItems;
130 /* Save multitiled items only to one stack */
131 for (stackiterator i2 = GetBottom(); i2.HasItem(); ++i2) if (i2->IsMainSlot(&i2.GetSlot())) SaveFile << i2.GetSlot();
135 void stack::Load (inputfile &SaveFile) {
136 uShort SavedItems = 0;
137 SaveFile >> SavedItems;
138 for (int c = 0; c < SavedItems; ++c) {
139 if (!c && !Items) Bottom = Top = new stackslot(this, 0);
140 else Top = Top->Next = new stackslot(this, Top);
141 SaveFile >> *Top;
142 Volume += (*Top)->GetVolume();
143 Weight += (*Top)->GetWeight();
144 if ((*Top)->GetSquarePosition() == CENTER) Emitation = game::CombineConstLights(Emitation, (*Top)->GetEmitation());
146 Items += SavedItems;
150 v2 stack::GetPos () const { return GetSquareUnder()->GetPos(); }
153 /* Returns whether there are any items satisfying the sorter or any visible items if it is zero */
154 truth stack::SortedItems (ccharacter *Viewer, sorter SorterFunction) const {
155 if (Items) {
156 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
157 if ((SorterFunction == 0 || ((*i)->*SorterFunction)(Viewer)) && ((Flags & HIDDEN) || i->CanBeSeenBy(Viewer))) return true;
160 return false;
164 int stack::SortedItemsCount (ccharacter *Viewer, sorter SorterFunction) const {
165 int res = 0;
166 if (Items) {
167 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
168 if ((SorterFunction == 0 || ((*i)->*SorterFunction)(Viewer)) && ((Flags & HIDDEN) || i->CanBeSeenBy(Viewer))) ++res;
171 return res;
175 // 0, 1 or most recent pickup time
176 feuLong stack::SortedItemsRecentTime (ccharacter *Viewer, sorter SorterFunction) const {
177 feuLong highestTime = 0;
178 if (Items) {
179 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
180 if ((SorterFunction == 0 || ((*i)->*SorterFunction)(Viewer)) && ((Flags & HIDDEN) || i->CanBeSeenBy(Viewer))) {
181 feuLong pt = (*i)->pickupTime;
182 if (pt == 0) pt = 1;
183 if (pt > highestTime) highestTime = pt;
187 return highestTime;
191 void stack::BeKicked (character *Kicker, int KickDamage, int Direction) {
192 if (KickDamage) {
193 ReceiveDamage(Kicker, KickDamage, PHYSICAL_DAMAGE, Direction);
194 if (GetItems() && GetLSquareUnder()->IsFlyable()) {///&& SquarePosition == CENTER)
195 item *Item1 = *GetTop();
196 item *Item2 = RAND() & 1 && GetItems() > 1 ? *--GetTop() : 0;
197 Item1->Fly(Kicker, Direction, KickDamage*3);
198 if (Item2) {
199 /*k8: if(!Item2->Exists() || Item2->GetPos() != GetPos()) int esko = esko = 2; */
200 if (!(!Item2->Exists() || Item2->GetPos() != GetPos())) Item2->Fly(Kicker, Direction, KickDamage * 3);
203 } else if (Kicker->IsPlayer() && GetNativeVisibleItems(Kicker)) {
204 ADD_MESSAGE("Your weak kick has no effect.");
209 void stack::Polymorph (character *Polymorpher) {
210 itemvector ItemVector;
211 FillItemVector(ItemVector);
212 int p = 0;
213 for (uInt c = 0; c < ItemVector.size(); ++c) {
214 if (ItemVector[c]->Exists() && ItemVector[c]->Polymorph(Polymorpher, this) && ++p == 5) break;
219 void stack::CheckForStepOnEffect (character *Stepper) {
220 itemvector ItemVector;
221 FillItemVector(ItemVector);
222 for (uInt c = 0; c < ItemVector.size(); ++c) {
223 if (ItemVector[c]->Exists()) {
224 ItemVector[c]->StepOnEffect(Stepper);
225 if (!Stepper->IsEnabled()) return;
231 lsquare *stack::GetLSquareTrulyUnder (int SquarePosition) const {
232 if (SquarePosition == DOWN) {
233 if (GetArea()->IsValidPos(GetPos()+v2(0, 1))) return GetNearLSquare(GetPos() + v2(0, 1));
234 return 0;
236 if (SquarePosition == LEFT) {
237 if (GetArea()->IsValidPos(GetPos()+v2(-1, 0))) return GetNearLSquare(GetPos() + v2(-1, 0));
238 return 0;
240 if (SquarePosition == UP) {
241 if (GetArea()->IsValidPos(GetPos()+v2(0, -1))) return GetNearLSquare(GetPos() + v2(0, -1));
242 return 0;
244 if (SquarePosition == RIGHT) {
245 if (GetArea()->IsValidPos(GetPos()+v2(1, 0))) return GetNearLSquare(GetPos() + v2(1, 0));
246 return 0;
248 return GetLSquareUnder();
252 void stack::ReceiveDamage (character *Damager, int Damage, int Type, int Direction) {
253 itemvector ItemVector;
254 FillItemVector(ItemVector);
255 for (uInt c = 0; c < ItemVector.size(); ++c)
256 if (ItemVector[c]->Exists() && AllowDamage(Direction, ItemVector[c]->GetSquarePosition()))
257 ItemVector[c]->ReceiveDamage(Damager, Damage, Type);
261 void stack::TeleportRandomly (uInt Amount) {
262 itemvector ItemVector;
263 FillItemVector(ItemVector);
264 for (uInt c = 0; c < ItemVector.size() && c < Amount; ++c)
265 if (ItemVector[c]->Exists()) {
266 if (ItemVector[c]->CanBeSeenByPlayer()) ADD_MESSAGE("%s disappears!", ItemVector[c]->GetExtendedDescription().CStr());
267 ItemVector[c]->TeleportRandomly();
272 /* ItemVector receives all items in the stack */
273 void stack::FillItemVector (itemvector &ItemVector) const {
274 for (stackiterator i = GetBottom(); i.HasItem(); ++i) ItemVector.push_back(*i);
278 /* Don't use; this function is only for gum solutions */
279 item *stack::GetItem (int I) const {
280 int c = 0;
281 for (stackiterator i = GetBottom(); i.HasItem(); ++i, ++c) if (c == I) return *i;
282 return 0;
286 /* Don't use; this function is only for gum solutions */
287 int stack::SearchItem (item *ToBeSearched) const {
288 int c = 0;
289 for (stackiterator i = GetBottom(); i.HasItem(); ++i, ++c) if (*i == ToBeSearched) return c;
290 return -1;
294 /* Flags for all DrawContents functions can be found in ivandef.h.
295 Those returning int return 0 on success and a felist error
296 otherwise (see felibdef.h) */
297 item *stack::DrawContents (ccharacter *Viewer, cfestring &Topic, int Flags, sorter SorterFunction, item *hiitem) const {
298 itemvector ReturnVector;
299 DrawContents(ReturnVector, 0, Viewer, Topic, CONST_S(""), CONST_S(""), CONST_S(""), 0, Flags|NO_MULTI_SELECT, SorterFunction, hiitem);
300 return ReturnVector.empty() ? 0 : ReturnVector[0];
304 int stack::DrawContents (itemvector &ReturnVector, ccharacter *Viewer, cfestring &Topic, int Flags, sorter SorterFunction, item *hiitem) const {
305 return DrawContents(ReturnVector, 0, Viewer, Topic, CONST_S(""), CONST_S(""), CONST_S(""), 0, Flags, SorterFunction, hiitem);
309 /* MergeStack is used for showing two stacks together. Like when eating when
310 there are items on the ground and in the character's stack */
311 int stack::DrawContents (itemvector &ReturnVector, stack *MergeStack,
312 ccharacter *Viewer, cfestring &Topic, cfestring &ThisDesc, cfestring &ThatDesc,
313 cfestring &SpecialDesc, col16 SpecialDescColor, int Flags, sorter SorterFunction, item *hiitem) const
315 felist Contents(Topic);
316 lsquare *Square = GetLSquareUnder();
317 stack *AdjacentStack[4] = { 0, 0, 0, 0 };
319 if ((this->Flags&HIDDEN) == 0) for (int c = 0; c < 4; ++c) AdjacentStack[c] = Square->GetStackOfAdjacentSquare(c);
321 if (!SpecialDesc.IsEmpty()) {
322 Contents.AddDescription(CONST_S(""));
323 Contents.AddDescription(SpecialDesc.CapitalizeCopy(), SpecialDescColor);
327 if (!(Flags & NO_SPECIAL_INFO)) {
328 Contents.AddDescription(CONST_S(""));
329 sLong Weight = GetWeight(Viewer, CENTER);
330 if (MergeStack) Weight += MergeStack->GetWeight(Viewer, CENTER);
331 for (c = 0; c < 4; ++c)
332 if (AdjacentStack[c])
333 Weight += AdjacentStack[c]->GetWeight(Viewer, 3 - c);
334 Contents.AddDescription(CONST_S("Overall weight: ") + Weight + " grams");
338 if (Flags&NONE_AS_CHOICE) {
339 int ImageKey = game::AddToItemDrawVector(itemvector());
340 Contents.AddEntry(CONST_S("none"), LIGHT_GRAY, 0, ImageKey);
343 if (MergeStack) MergeStack->AddContentsToList(Contents, Viewer, ThatDesc, Flags, CENTER, SorterFunction, hiitem);
344 AddContentsToList(Contents, Viewer, ThisDesc, Flags, CENTER, SorterFunction, hiitem);
346 static cchar *WallDescription[] = { "western", "southern", "nothern", "eastern" };
347 for (int c = 0; c < 4; ++c) {
348 if (AdjacentStack[c]) AdjacentStack[c]->AddContentsToList(Contents, Viewer, CONST_S("Items on the ")+WallDescription[c] + " wall:", Flags, 3-c, SorterFunction, hiitem);
351 game::SetStandardListAttributes(Contents);
352 Contents.SetPageLength(12);
353 Contents.RemoveFlags(BLIT_AFTERWARDS);
354 Contents.SetEntryDrawer(game::ItemEntryDrawer);
356 if ((Flags&NO_SELECT) == 0) Contents.AddFlags(SELECTABLE);
358 // `Contents.Draw()` will fix invalid selections
359 int selected = 0;
360 if (Flags&REMEMBER_SELECTED) {
361 if ((Flags&NONE_AS_CHOICE) && (Flags&SKIP_FIRST_IF_NO_OLD) && !hiitem && GetSelected() == 0) {
362 selected = 1;
364 } else {
365 if ((Flags&NONE_AS_CHOICE) && (Flags&SKIP_FIRST_IF_NO_OLD) && !hiitem) {
366 selected = 1;
370 if ((Flags&NO_SELECT) == 0 && (Flags&SELECT_MOST_RECENT)) {
371 int cursel = -1;
372 feuLong maxpt = 0;
373 for (uInt c = 0; c < Contents.GetLength(); ++c) {
374 if (!Contents.IsEntrySelectable(c)) continue;
375 ++cursel;
376 if (cursel < selected) continue;
377 feuLong pt = Contents.GetEntryUData(c);
378 if (pt <= 1 || game::GetTick()-pt > 15+4) continue;
379 if (pt < maxpt) continue;
380 maxpt = pt;
381 selected = cursel;
385 if (selected <= 0) selected = GetSelected();
386 Contents.SetSelected(selected);
388 game::DrawEverythingNoBlit(); //doesn't prevent mirage puppies
389 int Chosen = Contents.Draw();
390 game::ClearItemDrawVector();
392 if (Chosen&FELIST_ERROR_BIT) {
393 Selected = 0;
394 return Chosen;
396 Selected = Chosen;
398 int Pos = 0;
400 if (Flags&NONE_AS_CHOICE) {
401 if (!Selected) return 0;
402 ++Pos;
405 if (MergeStack) {
406 Pos = MergeStack->SearchChosen(ReturnVector, Viewer, Pos, Selected, Flags, CENTER, SorterFunction);
407 if (!ReturnVector.empty()) return 0;
410 Pos = SearchChosen(ReturnVector, Viewer, Pos, Selected, Flags, CENTER, SorterFunction);
412 if (!ReturnVector.empty()) return 0;
414 for (int c = 0; c < 4; ++c) {
415 if (AdjacentStack[c]) {
416 AdjacentStack[c]->SearchChosen(ReturnVector, Viewer, Pos, Selected, Flags, 3-c, SorterFunction);
417 if (!ReturnVector.empty()) break;
421 return 0;
425 /* Internal function to fill Contents list */
426 void stack::AddContentsToList (felist &Contents, ccharacter *Viewer, cfestring &Desc, int Flags,
427 int RequiredSquarePosition, sorter SorterFunction, item *hiitem) const
429 itemvectorvector PileVector;
430 Pile(PileVector, Viewer, RequiredSquarePosition, SorterFunction);
431 truth DrawDesc = Desc.GetSize();
432 sLong LastCategory = 0;
433 festring Entry;
434 for (uInt p = 0; p < PileVector.size(); ++p) {
435 if (DrawDesc) {
436 if (!Contents.IsEmpty()) Contents.AddEntry(CONST_S(""), WHITE, 0, NO_IMAGE, false);
437 Contents.AddEntry(Desc, WHITE, 0, NO_IMAGE, false);
438 Contents.AddEntry(CONST_S(""), WHITE, 0, NO_IMAGE, false);
439 DrawDesc = false;
441 item *Item = PileVector[p].back();
442 if (Item->GetCategory() != LastCategory) {
443 LastCategory = Item->GetCategory();
444 Contents.AddEntry(item::GetItemCategoryName(LastCategory), LIGHT_GRAY, 0, NO_IMAGE, false);
446 Entry.Empty();
447 Item->AddInventoryEntry(Viewer, Entry, PileVector[p].size(), !(Flags & NO_SPECIAL_INFO));
448 int ImageKey = game::AddToItemDrawVector(PileVector[p]);
449 Contents.AddEntry(Entry, (Item == hiitem ? WHITE : LIGHT_GRAY), 0, ImageKey, true, Item->pickupTime);
454 /* Internal function which fills ReturnVector according to Chosen,
455 which is given by felist::Draw, and possibly the user's additional
456 input about item amount. */
457 int stack::SearchChosen (itemvector &ReturnVector, ccharacter *Viewer, int Pos, int Chosen, int Flags,
458 int RequiredSquarePosition, sorter SorterFunction) const
460 /* Not really efficient... :( */
461 itemvectorvector PileVector;
462 Pile(PileVector, Viewer, RequiredSquarePosition, SorterFunction);
463 for (uInt p = 0; p < PileVector.size(); ++p) {
464 if (Pos++ == Chosen) {
465 if (Flags & NO_MULTI_SELECT) {
466 int Amount = (Flags & SELECT_PAIR && PileVector[p][0]->HandleInPairs() && PileVector[p].size() >= 2 ? 2 : 1);
467 ReturnVector.assign(PileVector[p].end() - Amount, PileVector[p].end());
468 return -1;
469 } else {
470 int Amount = PileVector[p].size();
471 if (Amount > 1) {
472 Amount = game::ScrollBarQuestion(CONST_S("How many ")+PileVector[p][0]->GetName(PLURAL)+'?',
473 Amount, 1, 0, Amount, 0, WHITE,
474 LIGHT_GRAY, DARK_GRAY);
476 ReturnVector.assign(PileVector[p].end() - Amount, PileVector[p].end());
477 return -1;
481 return Pos;
485 truth stack::RaiseTheDead (character *Summoner) {
486 itemvector ItemVector;
487 FillItemVector(ItemVector);
488 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->RaiseTheDead(Summoner)) return true;
489 return false;
493 /* Returns false if the Applier didn't try to use the key */
494 truth stack::TryKey (item *Key, character *Applier) {
495 if (!Applier->IsPlayer()) return false;
496 item *ToBeOpened = DrawContents(Applier, CONST_S("Where do you wish to use the key?"), 0, &item::HasLock);
497 if (!ToBeOpened) return false;
498 return ToBeOpened->TryKey(Key, Applier);
502 /* Returns false if the Applier didn't try to open anything */
503 truth stack::Open (character *Opener) {
504 if (!Opener->IsPlayer()) return false;
505 item *ToBeOpened = DrawContents(Opener, CONST_S("What do you wish to open?"), 0, &item::IsOpenable);
506 return ToBeOpened ? ToBeOpened->Open(Opener) : false;
510 int stack::GetSideItems (int RequiredSquarePosition) const {
511 int VisibleItems = 0;
512 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
513 if (i->GetSquarePosition() == RequiredSquarePosition) ++VisibleItems;
514 return VisibleItems;
518 int stack::GetVisibleItems (ccharacter *Viewer) const {
519 int VisibleItems = 0;
520 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
521 if (i->GetSquarePosition() == CENTER && i->CanBeSeenBy(Viewer)) ++VisibleItems;
522 lsquare *Square = GetLSquareUnder();
523 for (int c = 0; c < 4; ++c) {
524 stack *Stack = Square->GetStackOfAdjacentSquare(c);
525 if (Stack) VisibleItems += Stack->GetVisibleSideItems(Viewer, 3-c);
527 return VisibleItems;
531 void stack::GetVisibleItemsV (ccharacter *Viewer, std::vector<item *> &vi) {
532 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
533 if (i->GetSquarePosition() == CENTER && i->CanBeSeenBy(Viewer)) vi.push_back(*i);
535 lsquare *Square = GetLSquareUnder();
536 for (int c = 0; c < 4; ++c) {
537 stack *Stack = Square->GetStackOfAdjacentSquare(c);
538 if (Stack) {
539 //VisibleItems += Stack->GetVisibleSideItems(Viewer, 3-c);
540 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
541 if (i->GetSquarePosition() == (3-c) && i->CanBeSeenBy(Viewer)) vi.push_back(*i);
548 truth stack::HasSomethingFunny (ccharacter *Viewer, truth seeCorpses, truth seeUnstepped) {
549 std::vector<item *> vi;
551 GetVisibleItemsV(Viewer, vi);
552 for (unsigned int f = 0; f < vi.size(); f++) {
553 if (!vi[f]->CanBePickedUp()) continue;
554 //fprintf(stderr, "::%s %s\n", vi[f]->IsSteppedOn()?"T":"o", seeUnstepped?"T":"o");
555 if (!seeUnstepped && vi[f]->IsSteppedOn()) continue;
556 if (seeCorpses || (!vi[f]->IsCorpse() && !vi[f]->IsBodyPart())) return true;
558 return false;
562 void stack::SetSteppedOn (truth v) {
563 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
564 if (i->GetSquarePosition() == CENTER) i->SetSteppedOn(v);
566 lsquare *Square = GetLSquareUnder();
567 for (int c = 0; c < 4; ++c) {
568 stack *Stack = Square->GetStackOfAdjacentSquare(c);
569 if (Stack) {
570 //VisibleItems += Stack->GetVisibleSideItems(Viewer, 3-c);
571 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
572 if (i->GetSquarePosition() == (3-c)) i->SetSteppedOn(v);
576 //fprintf(stderr, "STON!\n");
580 int stack::GetNativeVisibleItems (ccharacter *Viewer) const {
581 if (Flags & HIDDEN) return Items;
582 int VisibleItems = 0;
583 for (stackiterator i = GetBottom(); i.HasItem(); ++i) if (i->CanBeSeenBy(Viewer)) ++VisibleItems;
584 return VisibleItems;
588 int stack::GetVisibleSideItems (ccharacter *Viewer, int RequiredSquarePosition) const {
589 int VisibleItems = 0;
590 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
591 if (i->GetSquarePosition() == RequiredSquarePosition && i->CanBeSeenBy(Viewer)) ++VisibleItems;
592 return VisibleItems;
596 item *stack::GetBottomVisibleItem (ccharacter *Viewer) const {
597 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
598 if ((Flags & HIDDEN) || i->CanBeSeenBy(Viewer)) return *i;
599 return 0;
603 void stack::SignalVolumeAndWeightChange () {
604 if (!(Flags & FREEZED)) {
605 CalculateVolumeAndWeight();
606 if (MotherEntity) MotherEntity->SignalVolumeAndWeightChange();
611 void stack::CalculateVolumeAndWeight () {
612 Volume = Weight = 0;
613 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
614 Volume += i->GetVolume();
615 Weight += i->GetWeight();
620 void stack::SignalEmitationIncrease (int ItemSquarePosition, col24 EmitationUpdate) {
621 if (ItemSquarePosition < CENTER) {
622 stack *Stack = GetLSquareUnder()->GetStackOfAdjacentSquare(ItemSquarePosition);
623 if (Stack) Stack->SignalEmitationIncrease(CENTER, EmitationUpdate);
624 return;
626 if (!(Flags & FREEZED) && game::CompareLights(EmitationUpdate, Emitation) > 0) {
627 Emitation = game::CombineConstLights(Emitation, EmitationUpdate);
628 if (MotherEntity) {
629 if (MotherEntity->AllowContentEmitation()) MotherEntity->SignalEmitationIncrease(EmitationUpdate);
630 } else {
631 GetLSquareUnder()->SignalEmitationIncrease(EmitationUpdate);
637 void stack::SignalEmitationDecrease (int ItemSquarePosition, col24 EmitationUpdate) {
638 if (ItemSquarePosition < CENTER) {
639 stack *Stack = GetLSquareUnder()->GetStackOfAdjacentSquare(ItemSquarePosition);
640 if (Stack) Stack->SignalEmitationDecrease(CENTER, EmitationUpdate);
641 return;
643 if (!(Flags & FREEZED) && Emitation && game::CompareLights(EmitationUpdate, Emitation) >= 0) {
644 col24 Backup = Emitation;
645 CalculateEmitation();
646 if (Backup != Emitation) {
647 if (MotherEntity) {
648 if (MotherEntity->AllowContentEmitation()) MotherEntity->SignalEmitationDecrease(EmitationUpdate);
649 } else {
650 GetLSquareUnder()->SignalEmitationDecrease(EmitationUpdate);
657 void stack::CalculateEmitation () {
658 Emitation = GetSideEmitation(CENTER);
662 col24 stack::GetSideEmitation (int RequiredSquarePosition) {
663 col24 Emitation = 0;
664 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
665 if (i->GetSquarePosition() == RequiredSquarePosition) game::CombineLights(Emitation, i->GetEmitation());
666 return Emitation;
670 truth stack::CanBeSeenBy (ccharacter *Viewer, int SquarePosition) const {
671 if (MotherEntity) return MotherEntity->ContentsCanBeSeenBy(Viewer);
672 lsquare *Square = GetLSquareTrulyUnder(SquarePosition);
673 return Viewer->IsOver(Square->GetPos()) || Square->CanBeSeenBy(Viewer);
677 truth stack::IsDangerous (ccharacter *Stepper) const {
678 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
679 if (i->IsDangerous(Stepper) && i->CanBeSeenBy(Stepper)) return true;
680 return false;
684 /* Returns true if something was duplicated.
685 Max is the cap of items to be affected */
686 truth stack::Duplicate (int Max, feuLong Flags) {
687 if (!GetItems()) return false;
688 itemvector ItemVector;
689 FillItemVector(ItemVector);
690 int p = 0;
691 for (uInt c = 0; c < ItemVector.size(); ++c)
692 if (ItemVector[c]->Exists() && ItemVector[c]->DuplicateToStack(this, Flags) && ++p == Max) break;
693 return p > 0;
697 /* Adds the item without any external update requests */
698 void stack::AddElement (item *Item, truth) {
699 ++Items;
700 /* "I love writing illegible code." - Guy who wrote this */
701 (Top = (Bottom ? Top->Next : Bottom) = new stackslot(this, Top))->PutInItem(Item);
705 /* Removes the slot without any external update requests */
706 void stack::RemoveElement (stackslot *Slot) {
707 --Items;
708 (Slot->Last ? Slot->Last->Next : Bottom) = Slot->Next;
709 (Slot->Next ? Slot->Next->Last : Top) = Slot->Last;
710 delete Slot;
714 void stack::MoveItemsTo (stack *Stack) {
715 while (Items) GetBottom()->MoveTo(Stack);
719 void stack::MoveItemsTo (slot *Slot) {
720 while (Items) Slot->AddFriendItem(*GetBottom());
724 item *stack::GetBottomItem (ccharacter *Char, truth ForceIgnoreVisibility) const {
725 if ((Flags & HIDDEN) || ForceIgnoreVisibility) return Bottom ? **Bottom : 0;
726 return GetBottomVisibleItem(Char);
730 item *stack::GetBottomSideItem (ccharacter *Char, int RequiredSquarePosition, truth ForceIgnoreVisibility) const {
731 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
732 if ((i->GetSquarePosition() == RequiredSquarePosition && (Flags & HIDDEN)) ||
733 ForceIgnoreVisibility || i->CanBeSeenBy(Char)) return *i;
735 return 0;
739 truth CategorySorter (const itemvector &V1, const itemvector &V2) {
740 return (*V1.begin())->GetCategory() < (*V2.begin())->GetCategory();
744 /* Slow function which sorts the stack's contents to a vector of piles
745 (itemvectors) of which elements are similiar to each other, for instance
746 4 bananas */
747 void stack::Pile (itemvectorvector &PileVector, ccharacter *Viewer,
748 int RequiredSquarePosition, sorter SorterFunction) const
750 if (!Items) return;
751 std::list<item *> List;
752 for (stackiterator s = GetBottom(); s.HasItem(); ++s)
753 if (s->GetSquarePosition() == RequiredSquarePosition &&
754 (SorterFunction == 0 || ((*s)->*SorterFunction)(Viewer)) &&
755 ((Flags & HIDDEN) || s->CanBeSeenBy(Viewer)))
756 List.push_back(*s);
757 for (std::list<item*>::iterator i = List.begin(); i != List.end(); ++i) {
758 PileVector.resize(PileVector.size()+1);
759 itemvector& Pile = PileVector.back();
760 Pile.push_back(*i);
761 if ((*i)->CanBePiled()) {
762 std::list<item*>::iterator j = i;
763 for (++j; j != List.end(); ) {
764 if ((*j)->CanBePiled() && (*i)->CanBePiledWith(*j, Viewer)) {
765 Pile.push_back(*j);
766 std::list<item *>::iterator Dirt = j++;
767 List.erase(Dirt);
768 } else {
769 ++j;
774 std::stable_sort(PileVector.begin(), PileVector.end(), CategorySorter);
778 /* Total price of the stack */
779 sLong stack::GetTruePrice () const {
780 sLong Price = 0;
781 for (stackiterator i = GetBottom(); i.HasItem(); ++i) Price += i->GetTruePrice();
782 return Price;
786 /* GUI used for instance by chests and bookcases.
787 Returns whether anything was done. */
788 truth stack::TakeSomethingFrom (character *Opener, cfestring &ContainerName) {
789 if (!GetItems()) {
790 ADD_MESSAGE("There is nothing in %s.", ContainerName.CStr());
791 return false;
793 truth Success = false;
794 room *Room = GetLSquareUnder()->GetRoom();
795 SetSelected(0);
796 for (;;) {
797 itemvector ToTake;
798 game::DrawEverythingNoBlit();
799 DrawContents(ToTake, Opener, CONST_S("What do you want to take from ")+ContainerName+'?', REMEMBER_SELECTED);
800 if (ToTake.empty()) break;
801 if (!IsOnGround() || !Room || Room->PickupItem(Opener, ToTake[0], ToTake.size())) {
802 for (uInt c = 0; c < ToTake.size(); ++c) ToTake[c]->MoveTo(Opener->GetStack());
803 ADD_MESSAGE("You take %s from %s.", ToTake[0]->GetName(DEFINITE, ToTake.size()).CStr(), ContainerName.CStr());
804 Success = true;
807 return Success;
811 /* GUI used for instance by chests and bookcases (use ContainerID == 0 if
812 the container isn't an item). Returns whether anything was done. */
813 truth stack::PutSomethingIn (character *Opener, cfestring &ContainerName, sLong StorageVolume, feuLong ContainerID) {
814 if (!Opener->GetStack()->GetItems()) {
815 ADD_MESSAGE("You have nothing to put in %s.", ContainerName.CStr());
816 return false;
818 truth Success = false;
819 room *Room = GetLSquareUnder()->GetRoom();
820 SetSelected(0);
821 for (;;) {
822 itemvector ToPut;
823 game::DrawEverythingNoBlit();
824 Opener->GetStack()->DrawContents(ToPut, Opener, CONST_S("What do you want to put in ")+ContainerName+'?', REMEMBER_SELECTED|SELECT_MOST_RECENT);
825 if (ToPut.empty()) break;
826 if (ToPut[0]->GetID() == ContainerID) {
827 ADD_MESSAGE("You can't put %s inside itself!", ContainerName.CStr());
828 continue;
830 uInt Amount = Min<uInt>((StorageVolume-GetVolume())/ToPut[0]->GetVolume(), ToPut.size());
831 if (!Amount) {
832 if (ToPut.size() == 1) ADD_MESSAGE("%s doesn't fit in %s.", ToPut[0]->CHAR_NAME(DEFINITE), ContainerName.CStr());
833 else ADD_MESSAGE("None of the %d %s fit in %s.", int(ToPut.size()), ToPut[0]->CHAR_NAME(PLURAL), ContainerName.CStr());
834 continue;
836 if (Amount != ToPut.size())
837 ADD_MESSAGE("Only %d of the %d %s fit%s in %s.", Amount, int(ToPut.size()), ToPut[0]->CHAR_NAME(PLURAL), Amount == 1 ? "s" : "", ContainerName.CStr());
838 if (!IsOnGround() || !Room || Room->DropItem(Opener, ToPut[0], Amount)) {
839 for (uInt c = 0; c < Amount; ++c) ToPut[c]->MoveTo(this);
840 ADD_MESSAGE("You put %s in %s.", ToPut[0]->GetName(DEFINITE, Amount).CStr(), ContainerName.CStr());
841 Success = true;
844 return Success;
848 truth stack::IsOnGround () const {
849 return !MotherEntity || MotherEntity->IsOnGround();
853 int stack::GetSpoiledItems () const {
854 int Counter = 0;
855 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
856 Counter += (i->GetSpoilLevel() > 0); // even though this is pretty unclear, it isn't mine but Hex's
857 return Counter;
861 /* Adds all items and recursively their contents
862 which satisfy the sorter to ItemVector */
863 void stack::SortAllItems (const sortdata &SortData) const {
864 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->SortAllItems(SortData);
868 /* Search for traps and other secret items */
869 void stack::Search (ccharacter *Char, int Perception) {
870 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->Search(Char, Perception);
874 /* Used to determine whether the danger symbol should be shown */
875 truth stack::NeedDangerSymbol (ccharacter *Viewer) const {
876 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
877 if (i->NeedDangerSymbol() && i->CanBeSeenBy(Viewer)) return true;
878 return false;
882 void stack::PreProcessForBone () {
883 if (Items) {
884 itemvector ItemVector;
885 FillItemVector(ItemVector);
886 for (uInt c = 0; c < ItemVector.size(); ++c) ItemVector[c]->PreProcessForBone();
891 void stack::PostProcessForBone () {
892 if (Items) {
893 itemvector ItemVector;
894 FillItemVector(ItemVector);
895 for (uInt c = 0; c < ItemVector.size(); ++c) ItemVector[c]->PostProcessForBone();
900 void stack::FinalProcessForBone () {
901 /* Items can't be removed during the final processing stage */
902 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->FinalProcessForBone();
906 /* VolumeModifier increases the spilled liquid's volume.
907 Note that the original liquid isn't placed anywhere nor deleted,
908 but its volume is decreased (possibly to zero). */
909 void stack::SpillFluid (character *Spiller, liquid *Liquid, sLong VolumeModifier) {
910 if (!Items) return;
911 double ChanceMultiplier = 1.0/(300+sqrt(Volume));
912 itemvector ItemVector;
913 FillItemVector(ItemVector);
914 for (int c = ItemVector.size()-1; c >= 0; --c) {
915 if (ItemVector[c]->Exists() && ItemVector[c]->AllowFluids()) {
916 sLong ItemVolume = ItemVector[c]->GetVolume();
917 double Root = sqrt(ItemVolume);
918 if (Root > RAND() % 200 || Root > RAND() % 200) {
919 sLong SpillVolume = sLong(VolumeModifier*Root*ChanceMultiplier);
920 if (SpillVolume) {
921 Liquid->EditVolume(-Max(SpillVolume, Liquid->GetVolume()));
922 ItemVector[c]->SpillFluid(Spiller, Liquid->SpawnMoreLiquid(SpillVolume), ItemVector[c]->GetSquareIndex(GetPos()));
923 if (!Liquid->GetVolume()) return;
931 void stack::AddItems (const itemvector &ItemVector) {
932 for (uInt c = 0; c < ItemVector.size(); ++c) AddItem(ItemVector[c]);
936 void stack::MoveItemsTo (itemvector &ToVector, int RequiredSquarePosition) {
937 itemvector ItemVector;
938 FillItemVector(ItemVector);
939 for (uInt c = 0; c < ItemVector.size(); ++c) {
940 if (ItemVector[c]->GetSquarePosition() == RequiredSquarePosition) {
941 ItemVector[c]->RemoveFromSlot();
942 ToVector.push_back(ItemVector[c]);
948 void stack::DropSideItems () {
949 for (stackiterator i = GetBottom(); i.HasItem(); ++i) {
950 int SquarePosition = i->GetSquarePosition();
951 if (SquarePosition != CENTER) {
952 if (i->IsAnimated()) {
953 lsquare *Square = GetLSquareTrulyUnder(SquarePosition);
954 if (Square) Square->DecStaticAnimatedEntities();
955 GetLSquareUnder()->IncStaticAnimatedEntities();
957 i->SignalSquarePositionChange(CENTER);
958 SignalEmitationDecrease(SquarePosition, i->GetEmitation());
959 SignalEmitationIncrease(CENTER, i->GetEmitation());
965 truth stack::AllowDamage (int Direction, int SquarePosition) {
966 if (SquarePosition == CENTER) return true;
967 switch (Direction) {
968 case 0: return SquarePosition == DOWN || SquarePosition == RIGHT;
969 case 1: return SquarePosition == DOWN;
970 case 2: return SquarePosition == DOWN || SquarePosition == LEFT;
971 case 3: return SquarePosition == RIGHT;
972 case 4: return SquarePosition == LEFT;
973 case 5: return SquarePosition == UP || SquarePosition == RIGHT;
974 case 6: return SquarePosition == UP;
975 case 7: return SquarePosition == UP || SquarePosition == LEFT;
977 return true;
981 sLong stack::GetWeight (ccharacter *Viewer, int SquarePosition) const {
982 sLong Weight = 0;
983 for (stackiterator i = GetBottom(); i.HasItem(); ++i)
984 if (i->GetSquarePosition() == SquarePosition && ((Flags & HIDDEN) || i->CanBeSeenBy(Viewer)))
985 Weight += i->GetWeight();
986 return Weight;
990 truth stack::DetectMaterial (cmaterial *Material) const {
991 for (stackiterator i = GetBottom(); i.HasItem(); ++i) if (i->DetectMaterial(Material)) return true;
992 return false;
996 void stack::SetLifeExpectancy (int Base, int RandPlus) {
997 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->SetLifeExpectancy(Base, RandPlus);
1001 truth stack::Necromancy (character *Necromancer) {
1002 itemvector ItemVector;
1003 FillItemVector(ItemVector);
1004 for (uInt c = 0; c < ItemVector.size(); ++c) if (ItemVector[c]->Necromancy(Necromancer)) return true;
1005 return false;
1009 void stack::CalculateEnchantments () {
1010 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->CalculateEnchantment();
1014 ccharacter *stack::FindCarrier () const {
1015 return MotherEntity ? MotherEntity->FindCarrier() : 0;
1019 void stack::Haste () {
1020 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->Haste();
1024 void stack::Slow () {
1025 for (stackiterator i = GetBottom(); i.HasItem(); ++i) i->Slow();