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 #include <algorithm>
14 #include <cstdarg>
15 #include <string>
16 #include <sstream>
17 #include <iostream>
18 #include <vector>
19 #include <bitset>
20 #include <ctime>
21 #include <pcre.h>
22 
23 #if defined(UNIX) || defined(__DJGPP__)
24 #include <sys/stat.h>
25 #endif
26 
27 #ifdef UNIX
28 #include <time.h>
29 #endif
30 
31 #ifdef WIN32
32 #include <direct.h>
33 #endif
34 
35 #include "allocate.h"
36 #include "action.h"
37 #include "area.h"
38 #include "audio.h"
39 #include "balance.h"
40 #include "bitmap.h"
41 #include "bugworkaround.h"
42 #include "confdef.h"
43 #include "command.h"
44 #include "definesvalidator.h"
45 #include "feio.h"
46 #include "felist.h"
47 #include "fetime.h"
48 #include "game.h"
49 #include "gear.h"
50 #include "god.h"
51 #include "graphics.h"
52 #include "hscore.h"
53 #include "human.h"
54 #include "iconf.h"
55 #include "lterras.h"
56 #include "materias.h"
57 #include "message.h"
58 #include "miscitem.h"
59 #include "namegen.h"
60 #include "nonhuman.h"
61 #include "pool.h"
62 #include "proto.h"
63 #include "rain.h"
64 #include "rawbit.h"
65 #include "room.h"
66 #include "save.h"
67 #include "stack.h"
68 #include "team.h"
69 #include "whandler.h"
70 #include "wsquare.h"
71 
72 #include "dbgmsgproj.h"
73 
74 #define SAVE_FILE_VERSION 136 // Increment this if changes make savefiles incompatible
75 #define BONE_FILE_VERSION 120 // Increment this if changes make bonefiles incompatible
76 
77 #define LOADED 0
78 #define NEW_GAME 1
79 #define BACK 2
80 
81 int game::CurrentLevelIndex;
82 truth game::InWilderness = false;
83 worldmap* game::WorldMap;
84 area* game::AreaInLoad;
85 square* game::SquareInLoad;
86 dungeon** game::Dungeon;
87 int game::CurrentDungeonIndex;
88 ulong game::NextCharacterID = 1;
89 ulong game::NextItemID = 1;
90 ulong game::NextTrapID = 1;
91 team** game::Team;
92 ulong game::LOSTick;
93 v2 game::CursorPos(-1, -1);
94 truth game::Zoom;
95 truth game::Generating = false;
96 double game::AveragePlayerArmStrengthExperience;
97 double game::AveragePlayerLegStrengthExperience;
98 double game::AveragePlayerDexterityExperience;
99 double game::AveragePlayerAgilityExperience;
100 int game::Teams;
101 int game::Dungeons;
102 int game::StoryState;
103 int game::GloomyCaveStoryState;
104 int game::XinrochTombStoryState;
105 int game::FreedomStoryState;
106 int game::AslonaStoryState;
107 int game::RebelStoryState;
108 truth game::PlayerIsChampion;
109 truth game::HasBoat;
110 massacremap game::PlayerMassacreMap;
111 massacremap game::PetMassacreMap;
112 massacremap game::MiscMassacreMap;
113 long game::PlayerMassacreAmount = 0;
114 long game::PetMassacreAmount = 0;
115 long game::MiscMassacreAmount = 0;
116 boneidmap game::BoneItemIDMap;
117 boneidmap game::BoneCharacterIDMap;
118 truth game::TooGreatDangerFoundTruth;
119 itemvectorvector game::ItemDrawVector;
120 charactervector game::CharacterDrawVector;
121 truth game::SumoWrestling;
122 liquid* game::GlobalRainLiquid;
123 v2 game::GlobalRainSpeed;
124 long game::GlobalRainTimeModifier;
125 truth game::PlayerSumoChampion;
126 truth game::TouristHasSpider;
127 ulong game::SquarePartEmitationTick = 0;
128 long game::Turn;
129 truth game::PlayerRunning;
130 character* game::LastPetUnderCursor;
131 charactervector game::PetVector;
132 double game::DangerFound;
133 int game::OldAttribute[ATTRIBUTES];
134 int game::NewAttribute[ATTRIBUTES];
135 int game::LastAttributeChangeTick[ATTRIBUTES];
136 int game::NecroCounter;
137 int game::CursorData;
138 truth game::CausePanicFlag;
139 //int game::iListWidth = 652;
140 
141 truth game::Loading = false;
142 truth game::JumpToPlayerBe = false;
143 truth game::InGetCommand = false;
144 character* game::Petrus = 0;
145 time_t game::TimePlayedBeforeLastLoad;
146 time_t game::LastLoad;
147 time_t game::GameBegan;
148 truth game::PlayerHasReceivedAllGodsKnownBonus;
149 
150 cchar* const game::Alignment[] = { "L++", "L+", "L", "L-", "N+", "N=", "N-", "C+", "C", "C-", "C--" };
151 god** game::God;
152 
153 cint game::MoveNormalCommandKey[] =
154 { KEY_HOME, KEY_UP, KEY_PAGE_UP, KEY_LEFT, KEY_RIGHT, KEY_END, KEY_DOWN, KEY_PAGE_DOWN, '.' };
155 cint game::MoveAbnormalCommandKey[] = { '7', '8', '9', 'u', 'o', 'j', 'k', 'l', '.' };
156 cint game::MoveNetHackCommandKey[] = { 'y', 'k', 'u', 'h', 'l', 'b', 'j', 'n', '.' };
157 int  game::MoveCustomCommandKey[] = { '.', '.', '.', '.', '.', '.', '.', '.', '.' };
158 
159 cv2 game::MoveVector[] =
160 { v2(-1, -1), v2(0, -1), v2(1, -1), v2(-1, 0), v2(1, 0), v2(-1, 1), v2(0, 1), v2(1, 1), v2(0, 0) };
161 cv2 game::ClockwiseMoveVector[] =
162 { v2(-1, -1), v2(0, -1), v2(1, -1), v2(1, 0), v2(1, 1), v2(0, 1), v2(-1, 1), v2(-1, 0), v2(0, 0) };
163 cv2 game::RelativeMoveVector[] =
164 { v2(-1, -1), v2(1, 0), v2(1, 0), v2(-2, 1), v2(2, 0), v2(-2, 1), v2(1, 0), v2(1, 0), v2(-1, -1) };
165 cv2 game::BasicMoveVector[] = { v2(-1, 0), v2(1, 0), v2(0, -1), v2(0, 1) };
166 cv2 game::LargeMoveVector[] =
167 { v2(-1, -1), v2(0, -1), v2(1, -1), v2(2, -1), v2(-1, 0), v2(2, 0), v2(-1, 1), v2(2, 1),
168   v2(-1, 2), v2(0, 2), v2(1, 2), v2(2, 2), v2(0, 0), v2(1, 0), v2(0, 1), v2(1, 1) };
169 cint game::LargeMoveDirection[] = { 0, 1, 1, 2, 3, 4, 3, 4, 5, 6, 6, 7, 8, 8, 8, 8 };
170 
171 truth game::LOSUpdateRequested = false;
172 uchar*** game::LuxTable = 0;
173 truth game::Running;
174 character* game::Player;
175 v2 game::Camera(0, 0);
176 ulong game::Tick;
177 gamescript* game::GameScript = 0;
178 valuemap game::GlobalValueMap;
179 dangermap game::DangerMap;
180 int game::NextDangerIDType;
181 int game::NextDangerIDConfigIndex;
182 characteridmap game::CharacterIDMap;
183 itemidmap game::ItemIDMap;
184 trapidmap game::TrapIDMap;
185 truth game::PlayerHurtByExplosion;
186 area* game::CurrentArea;
187 level* game::CurrentLevel;
188 wsquare*** game::CurrentWSquareMap;
189 lsquare*** game::CurrentLSquareMap;
190 festring game::DefaultPolymorphTo;
191 festring game::DefaultSummonMonster;
192 festring game::DefaultWish;
193 festring game::DefaultChangeMaterial;
194 festring game::DefaultDetectMaterial;
195 truth game::WizardMode;
196 int game::AutoPlayMode=0;
197 int game::SeeWholeMapCheatMode;
198 truth game::GoThroughWallsCheat;
199 int game::QuestMonstersFound;
200 bitmap* game::BusyAnimationCache[32];
201 festring game::PlayerName;
202 festring game::CurrentBaseSaveFileName;
203 ulong game::EquipmentMemory[MAX_EQUIPMENT_SLOTS];
204 olterrain* game::MonsterPortal;
205 std::vector<v2> game::SpecialCursorPos;
206 std::vector<int> game::SpecialCursorData;
207 cbitmap* game::EnterImage;
208 v2 game::EnterTextDisplacement;
209 
210 int iMaxXSize=0;
211 int iMaxYSize=0;
212 int iXSize=0;
213 int iYSize=0;
214 
215 int iRegionAroundXBRZ = -1;
216 int iRegionSilhouette = -1;
217 int iRegionVanillaSilhouette = -1;
218 int iRegionIndexDungeon = -1;
219 int iRegionListItem = -1;
220 int iRegionItemsUnder = -1;
221 
222 blitdata game::bldAroundOnScreenTMP = DEFAULT_BLITDATA;
223 blitdata bldFullDungeonTMP = DEFAULT_BLITDATA;
224 blitdata bldSilhouetteTMP = DEFAULT_BLITDATA;
225 blitdata bldVanillaSilhouetteTMP = DEFAULT_BLITDATA;
226 blitdata bldListItemTMP = DEFAULT_BLITDATA;
227 
228 int iItemsUnderStretch = 2;
229 
230 int iZoomFactor=6;
231 v2 ZoomPos = v2(0,0);
232 v2 silhouettePos = v2(0,0);
233 
234 bool bPositionQuestionMode=false;
235 
236 std::vector<dbgdrawoverlay> game::vDbgDrawOverlayFunctions;
237 
238 int game::iCurrentDungeonTurn=-1;
239 
240 int CurrentSavefileVersion=-1;
241 
242 int game::WorldShape = 0;
243 
244 /**
245  * IMPORTANT!!!
246  * this is intended to be called only from Load() and NEVER on Save()!
247  * TODO OS independent backtrace function call name check?
248  */
GetCurrentSavefileVersion()249 int game::GetCurrentSavefileVersion()
250 {
251   if(CurrentSavefileVersion==-1)
252     ABORT("no savegame loaded yet..."); //just means wrong usage of this method...
253 
254   return CurrentSavefileVersion;
255 }
256 
257 /**
258  * BEWARE!!!
259  * should only be called once at main(), you probably want GetCurrentSavefileVersion() instead!
260  */
GetSaveFileVersionHardcoded()261 int  game::GetSaveFileVersionHardcoded(){return SAVE_FILE_VERSION;}
262 
SetIsRunning(truth What)263 void game::SetIsRunning(truth What) { Running = What; }
264 
GetMaxScreenXSize()265 int game::GetMaxScreenXSize() { //this generally should not be used when the campera position is part of the calculations
266   if(iMaxXSize==0){
267     // 800 provides 42. 800/16=50. 42=50-8. 8 magic number for whatever is drawn to the right of the dungeon.
268     iMaxXSize = ivanconfig::GetStartingWindowWidth()/TILE_SIZE;
269     iMaxXSize-=8;
270   }
271   return iMaxXSize;
272 }
273 
GetMaxScreenYSize()274 int game::GetMaxScreenYSize() { //this generally should not be used when the campera position is part of the calculations
275   if(iMaxYSize==0){
276     // 600 provides 26. 600/16=37. 26=37-11. 11 magic number for whatever is drawn below of the dungeon.
277     iMaxYSize = ivanconfig::GetStartingWindowHeight()/TILE_SIZE;
278     iMaxYSize-=11;
279   }
280   return iMaxYSize;
281 }
282 
GetScreenXSize()283 int game::GetScreenXSize() { //actually dugeon visible width in tiles count
284   if(iXSize==0){
285     iXSize=GetMaxScreenXSize();
286     iXSize/=ivanconfig::GetStartingDungeonGfxScale();DBG2("VisibleDungeonColumns",iXSize); //yes, may lose some columns, no way to fit as scaler is integer and not float
287   }
288   return iXSize;
289 }
290 
GetScreenYSize()291 int game::GetScreenYSize() {  //actually dugeon visible height in tiles count
292   if(iYSize==0){
293     iYSize=GetMaxScreenYSize();
294     iYSize/=ivanconfig::GetStartingDungeonGfxScale();DBG2("VisibleDungeonLines",iYSize); //yes, may lose some lines, no way to fit as scaler is integer and not float
295   }
296   return iYSize;
297 }
298 
AddCharacterID(character * Char,ulong ID)299 void game::AddCharacterID(character* Char, ulong ID)
300 {
301   CharacterIDMap.insert(std::make_pair(ID, Char));
302 }
RemoveCharacterID(ulong ID)303 void game::RemoveCharacterID(ulong ID)
304 {
305   characteridmap::iterator itr = CharacterIDMap.find(ID);
306   if(itr == CharacterIDMap.end() || itr->second == NULL){
307     if(!bugfixdp::IsFixing())
308       ABORT("AlreadyErased:CharacterID %lu",ID);
309   }else
310     CharacterIDMap.erase(itr);
311 }
AddItemID(item * Item,ulong ID)312 void game::AddItemID(item* Item, ulong ID)
313 {
314   ItemIDMap.insert(std::make_pair(ID, Item));
315 }
316 
RemoveItemID(ulong ID)317 void game::RemoveItemID(ulong ID)
318 {
319   if(ID){
320     DBG2("Erasing:ItemID",ID);
321 
322     itemidmap::iterator itr = ItemIDMap.find(ID); //TODO if the search affects performance, make this optional
323     if(itr == ItemIDMap.end() || itr->second == NULL){
324       /**
325        * This happens when the duplicated player bug happens!
326        * so it will try to erase the item 2 times and CRASH on the second,
327        * therefore the abort is appropriate.
328        */
329       if(!bugfixdp::IsFixing())
330         ABORT("AlreadyErased:ItemID %lu, possible dup char bug",ID);
331     }else{
332       ItemIDMap.erase(itr);
333       DBG2("ERASED!:ItemID",ID);
334     }
335   }
336 }
337 
UpdateItemID(item * Item,ulong ID)338 void game::UpdateItemID(item* Item, ulong ID)
339 {
340   ItemIDMap.find(ID)->second = Item;
341 }
AddTrapID(entity * Trap,ulong ID)342 void game::AddTrapID(entity* Trap, ulong ID)
343 {
344   if(ID) TrapIDMap.insert(std::make_pair(ID, Trap));
345 }
RemoveTrapID(ulong ID)346 void game::RemoveTrapID(ulong ID)
347 {
348   if(ID){
349     trapidmap::iterator itr = TrapIDMap.find(ID);
350     if(itr == TrapIDMap.end() || itr->second == NULL){
351       if(!bugfixdp::IsFixing())
352         ABORT("AlreadyErased:TrapID %lu",ID);
353     }else
354       TrapIDMap.erase(itr);
355   }
356 }
UpdateTrapID(entity * Trap,ulong ID)357 void game::UpdateTrapID(entity* Trap, ulong ID)
358 {
359   TrapIDMap.find(ID)->second = Trap;
360 }
GetDangerMap()361 const dangermap& game::GetDangerMap() { return DangerMap; }
ClearItemDrawVector()362 void game::ClearItemDrawVector() { ItemDrawVector.clear(); }
ClearCharacterDrawVector()363 void game::ClearCharacterDrawVector() { CharacterDrawVector.clear(); }
364 
InitScript()365 void game::InitScript()
366 {
367   inputfile ScriptFile(GetDataDir() + "Script/dungeon.dat", &GlobalValueMap);
368   GameScript = new gamescript;
369   GameScript->ReadFrom(ScriptFile);
370   {
371     for (int f = 0; f <= 99; f++) //additional dungeon files
372     {
373       char bnum[32];
374       sprintf(bnum, "Script/dungeon_%02d.dat", f);
375       inputfile ifl(game::GetDataDir()+bnum, &game::GetGlobalValueMap(), false);
376       if (ifl.IsOpen())
377       {
378         GameScript->ReadFrom(ifl);
379         ifl.Close();
380       }
381     }
382   }
383   GameScript->RandomizeLevels();
384 }
385 
IsQuestItem(item * it)386 truth game::IsQuestItem(item* it) //dont protect against null item* it may be a problem outside here.
387 {
388   return it->IsQuestItem();
389 }
390 
PrepareToClearNonVisibleSquaresAround(v2 v2SqrPos)391 void game::PrepareToClearNonVisibleSquaresAround(v2 v2SqrPos) {
392   int i=ivanconfig::GetXBRZSquaresAroundPlayer();
393   if(i==0)return;
394   if(DoZoom())return; //TODO should be able to clear in zoom mode too? the result is still messy, but... is it cool to xBRZ non visible squares in look mode?  if so, no need to clear them...
395 
396   std::vector<v2> vv2ToBeCleared;
397   if(!IsInWilderness()){
398     /***
399      * this will check the squares around player for visibility/CanFeel
400      *
401      * the problem is the dungeon corners, ex.:
402      *  0       1       2
403      *  ####### ######O ######O
404      *  ####### ##P###O ##P###O
405      *  WWW#### WWW###O WWW###O
406      *  ##WP### ##W###O     ##O
407      *  ##W#### ##W###O      #O
408      *  ####### OOOOOOO OOOOOOO
409      *  ####### OOOOOOO OOOOOOO
410      *
411      *  Using xBRZ around player by 3 squares.
412      *  P = player
413      *  # = within user requested "around" distance.
414      *  O = out of user requested "around" distance (are ignored/not considered)
415      *  W = wall
416      *
417      *  0 - player is far from visible dungeon corners (no complexity)
418      *
419      *  1 - player is near top left corner:
420      *    The drawn dungeon visible area will be copied just 2 to the left and 1 to the top away from player position,
421      *    this means the cached bitmap will be smaller than at (*0).
422      *
423      *  2 - The player surroundings will be checked for visibility/CanFeel, if neither, these squares (that I deleted
424      *    on the example above) will be cleared from pixel colors to the mask/transparent color.
425      *    So, the vanilla (non xBRZ) non-visible squares' representation will be kept.
426      */
427 
428   //  lsquare* plsq = Player->GetLSquareUnder();
429   //  v2 v2PlayerPos = plsq->GetPos(); DBG3("PlayerPos",v2PlayerPos.X,v2PlayerPos.Y);
430     v2 v2MaxSqrUpperLeft (v2SqrPos.X-i,v2SqrPos.Y-i); DBGSV2(v2MaxSqrUpperLeft);
431     v2 v2MaxSqrLowerRight(v2SqrPos.X+i,v2SqrPos.Y+i); DBGSV2(v2MaxSqrLowerRight);
432 
433     level* plv = Player->GetLevel();
434     v2 v2ChkSqrPos;
435     lsquare* plsqChk;
436     v2 v2CamSqPos = GetCamera();
437     v2 v2DungeonSqSize = v2(GetScreenXSize(),GetScreenYSize());
438     int iSqLeftSkipX=0;
439     int iSqTopSkipY=0;
440     v2 v2Invalid(-1,-1),v2TopLeft(v2Invalid),v2BottomRight(v2Invalid);
441     // tips: OnScreen(v2Square)
442     for(int iY=v2MaxSqrUpperLeft.Y;iY<=v2MaxSqrLowerRight.Y;iY++){
443       if(iY<0                || iY<  v2CamSqPos.Y                   ){iSqTopSkipY++;continue;}
444       if(iY>=plv->GetYSize() || iY>=(v2CamSqPos.Y+v2DungeonSqSize.Y))break;
445 
446       iSqLeftSkipX=0; //must be reset here
447       for(int iX=v2MaxSqrUpperLeft.X;iX<=v2MaxSqrLowerRight.X;iX++){
448         if(iX<0                || iX<  v2CamSqPos.X                   ){iSqLeftSkipX++;continue;}
449         if(iX>=plv->GetXSize() || iX>=(v2CamSqPos.X+v2DungeonSqSize.X))break;
450 
451         v2ChkSqrPos=v2(iX,iY);
452         if(v2TopLeft==v2Invalid)v2TopLeft=v2ChkSqrPos; //first is top left
453         v2BottomRight=v2ChkSqrPos; //it will keep updating bottom right while it can
454         plsqChk = plv->GetLSquare(v2ChkSqrPos);
455 
456         if(plsqChk->CanBeSeenByPlayer())
457           continue;
458         if(!IsInWilderness()){
459           if(plsqChk->CanBeFeltByPlayer())
460             continue;
461           if(plsqChk->GetCharacter()!=NULL)
462             if(plsqChk->GetCharacter()->CanBeSeenByPlayer())
463               continue;
464         }
465 
466         /********************************************************************************************
467          * Now the final thing is to setup the relative pixel position on the small blitdata->bitmap
468          * (that is a copy of the player surroundings at dungeon area),
469          * that will have the squares cleared after it is cached
470          * and before it is stretched with xBRZ,
471          * so that the non visible squares will be drawn equally to all other far away
472          * non vivible squares.
473          */
474         vv2ToBeCleared.push_back(v2( //TODO CalculateScreenCoordinates(v2Square)
475           (v2ChkSqrPos.X - v2MaxSqrUpperLeft.X - iSqLeftSkipX)*TILE_SIZE,
476           (v2ChkSqrPos.Y - v2MaxSqrUpperLeft.Y - iSqTopSkipY )*TILE_SIZE
477         )); DBGSV2(vv2ToBeCleared[vv2ToBeCleared.size()-1]);
478       }
479     }
480 
481     DBGSV2(v2TopLeft);DBGSV2(v2BottomRight);
482   }
483 
484   graphics::SetSRegionClearSquaresAt(iRegionAroundXBRZ,TILE_V2,vv2ToBeCleared);
485 }
486 
SRegionAroundDisable()487 void game::SRegionAroundDisable(){
488   if(iRegionAroundXBRZ==-1)return;
489 
490   graphics::SetSRegionEnabled(iRegionAroundXBRZ,false);
491 }
492 
493 bool bRegionAroundXBRZAllowed=true;
SRegionAroundAllow()494 void game::SRegionAroundAllow(){
495   bRegionAroundXBRZAllowed=true;
496 }
SRegionAroundDeny()497 void game::SRegionAroundDeny(){
498   bRegionAroundXBRZAllowed=false;
499 }
500 
UpdatePosAroundForXBRZ(v2 v2SqrPos)501 void game::UpdatePosAroundForXBRZ(v2 v2SqrPos){ //TODO join this logic with PrepareToClearNonVisibleSquaresAroundPlayer() as they deal with the same thing.
502   if(iRegionAroundXBRZ==-1)return;
503 
504   bool bOk=true;
505 
506   if(bOk && !bRegionAroundXBRZAllowed)bOk=false;
507 
508   int iSAP=ivanconfig::GetXBRZSquaresAroundPlayer();
509   if(bOk && iSAP==0)bOk=false;
510 
511   if(bOk && Player->IsDead())bOk=false; // this may actually never happen...
512 
513   if(bOk && !OnScreen(v2SqrPos))bOk=false;
514 
515   if(bOk && bPositionQuestionMode){
516     if(!IsInWilderness()){ // always allowed in wilderness (as there is only fully dark squares, not partial as memories)
517       bOk=false;
518       /**
519        * TODO adapt the squares cleaners to work with bPositionQuestionMode too
520        * despite interesting, these are not good...:
521       if(bOk && IsDark(Player->GetLevel()->GetSunLightEmitation()))bOk=false;
522       if(bOk && IsDark(Player->GetLevel()->GetAmbientLuminance ()))bOk=false; //TODO explain: snow/rain?
523        */
524     }
525   }
526 
527   if(!bOk){
528     SRegionAroundDisable();
529     return;
530   }
531 
532   /////////////////// ok ///////////////////////
533 
534   v2 v2ScreenPos = CalculateScreenCoordinates(v2SqrPos);//DBGSV2(v2ScreenPos);
535 
536   graphics::SetSRegionEnabled(iRegionAroundXBRZ,true);
537 
538   bldAroundOnScreenTMP.Src = v2ScreenPos;
539 
540 //  v2 v2SqrPosPlayer = Player->GetPos();
541   v2 v2SqrPosCam = GetCamera();
542   v2 v2DeltaSqr = v2(v2SqrPos.X-v2SqrPosCam.X, v2SqrPos.Y-v2SqrPosCam.Y);
543 
544   v2 deltaSquaresForUpperLeft=v2DeltaSqr;
545   deltaSquaresForUpperLeft.X-=iSAP;
546   deltaSquaresForUpperLeft.Y-=iSAP;
547 
548   v2 v2SrcInSquares(iSAP,iSAP);
549 
550   if(deltaSquaresForUpperLeft.X<0)v2SrcInSquares.X+=deltaSquaresForUpperLeft.X;
551   if(deltaSquaresForUpperLeft.Y<0)v2SrcInSquares.Y+=deltaSquaresForUpperLeft.Y;
552 
553   bldAroundOnScreenTMP.Src.X-=TILE_SIZE*v2SrcInSquares.X;
554   bldAroundOnScreenTMP.Src.Y-=TILE_SIZE*v2SrcInSquares.Y;
555 
556 //  bldPlayerOnScreen.Dest = bldPlayerOnScreen.Src;
557 
558   v2 v2BorderInSquares(iSAP*2,iSAP*2);
559 
560   if(deltaSquaresForUpperLeft.X<0)v2BorderInSquares.X+=deltaSquaresForUpperLeft.X;
561   if(deltaSquaresForUpperLeft.Y<0)v2BorderInSquares.Y+=deltaSquaresForUpperLeft.Y;
562 
563   v2 deltaForLowerRight=v2DeltaSqr;
564   deltaForLowerRight.X=GetScreenXSize()-deltaForLowerRight.X-iSAP-1;
565   deltaForLowerRight.Y=GetScreenYSize()-deltaForLowerRight.Y-iSAP-1;
566 
567   if(deltaForLowerRight.X<0)v2BorderInSquares.X+=deltaForLowerRight.X;
568   if(deltaForLowerRight.Y<0)v2BorderInSquares.Y+=deltaForLowerRight.Y;
569 
570   bldAroundOnScreenTMP.Border.X=TILE_SIZE+(TILE_SIZE*v2BorderInSquares.X);
571   bldAroundOnScreenTMP.Border.Y=TILE_SIZE+(TILE_SIZE*v2BorderInSquares.Y);
572 
573   // this grants positioninig on the upper left player's square corner
574 
575   // relative to full dungeon in source image vanilla position
576   v2 deltaForFullDungeonSrc = v2(bldAroundOnScreenTMP.Src.X-bldFullDungeonTMP.Src.X, bldAroundOnScreenTMP.Src.Y-bldFullDungeonTMP.Src.Y);
577 
578   // relative to full dungeon over it's stretched image position
579   bldAroundOnScreenTMP.Dest.X=bldFullDungeonTMP.Dest.X+(deltaForFullDungeonSrc.X*ivanconfig::GetStartingDungeonGfxScale());
580   bldAroundOnScreenTMP.Dest.Y=bldFullDungeonTMP.Dest.Y+(deltaForFullDungeonSrc.Y*ivanconfig::GetStartingDungeonGfxScale());
581 
582   graphics::SetSRegionBlitdata(iRegionAroundXBRZ,bldAroundOnScreenTMP); DBGBLD(bldAroundOnScreenTMP);
583 
584   PrepareToClearNonVisibleSquaresAround(v2SqrPos);
585 }
586 
RegionListItemEnable(bool b)587 void game::RegionListItemEnable(bool b){
588   game::PrepareStretchRegionsLazy();
589   if(iRegionListItem==-1)return;
590 
591   // src pos is set at felist
592   graphics::SetSRegionBlitdata(iRegionListItem, bldListItemTMP);
593   graphics::SetSRegionEnabled(iRegionListItem, b);
594 }
595 
RegionSilhouetteEnable(bool bEnable)596 void game::RegionSilhouetteEnable(bool bEnable){
597   game::PrepareStretchRegionsLazy();
598   if(iRegionSilhouette==-1)return;
599 
600   if(bEnable && ivanconfig::GetSilhouetteScale()==1)bEnable=false;
601 
602   if(bEnable && !PLAYER->IsHumanoid())bEnable=false; //TODO isHuman() too?
603 
604   if(bEnable){
605     bldSilhouetteTMP.Stretch = ivanconfig::GetSilhouetteScale();
606 
607     bldSilhouetteTMP.Dest = {
608         silhouettePos.X -(bldSilhouetteTMP.Border.X*ivanconfig::GetSilhouetteScale()) -3,
609         silhouettePos.Y};
610 
611     graphics::SetSRegionBlitdata(iRegionSilhouette, bldSilhouetteTMP);
612     graphics::SetSRegionEnabled(iRegionSilhouette, true);
613   }else{
614     graphics::SetSRegionEnabled(iRegionSilhouette, false);
615   }
616 
617 }
618 
UpdateSRegionsXBRZ()619 void game::UpdateSRegionsXBRZ(){
620   UpdateSRegionsXBRZ(ivanconfig::IsXBRZScale());
621 }
UpdateSRegionsXBRZ(bool bIsXBRZScale)622 void game::UpdateSRegionsXBRZ(bool bIsXBRZScale){
623   for(int i=0;i<graphics::GetTotSRegions();i++){
624     if(i==iRegionIndexDungeon){
625       graphics::SetSRegionDrawBeforeFelistPage(iRegionIndexDungeon,true,ivanconfig::IsXBRZScale());
626 
627       if(ivanconfig::GetXBRZSquaresAroundPlayer()>0){
628         graphics::SetSRegionUseXBRZ(iRegionIndexDungeon,false);
629         continue;
630       }
631     }
632 
633     graphics::SetSRegionUseXBRZ(i,bIsXBRZScale);
634   }
635 }
636 
PrepareStretchRegionsLazy()637 void game::PrepareStretchRegionsLazy(){ // the ADD order IS important IF they overlap
638   if(iRegionIndexDungeon==-1){
639     if(ivanconfig::GetStartingDungeonGfxScale()>1){
640       /**
641        * dungeon visible area (Bitmap must be NULL)
642        * workaround: only one line of the border will be stretched, hence src -1 and border +2
643        */
644       v2 topleft = area::getTopLeftCorner();
645       bldFullDungeonTMP.Src = {topleft.X-1,topleft.Y-1}; //the top left corner of the dungeon drawn area INSIDE the dungeon are grey ouline
646       bldFullDungeonTMP.Dest = {topleft.X - area::getOutlineThickness() -1, topleft.Y - area::getOutlineThickness() -1}; //the top left corner of the grey ouline to cover it TODO a new one should be drawn one day
647       bldFullDungeonTMP.Border = {GetScreenXSize()*TILE_SIZE+2, game::GetScreenYSize()*TILE_SIZE+2};
648       bldFullDungeonTMP.Stretch = ivanconfig::GetStartingDungeonGfxScale();
649       iRegionIndexDungeon = graphics::AddStretchRegion(bldFullDungeonTMP,"FullDungeon");
650 
651       /***********************************
652        * AROUND: player or look zoom pos *
653        ***********************************/
654       // (will be above dungeon) around player on screen
655       bldAroundOnScreenTMP.Stretch = ivanconfig::GetStartingDungeonGfxScale();
656       iRegionAroundXBRZ = graphics::AddStretchRegion(bldAroundOnScreenTMP,"AroundXBRZ");
657     }
658   }
659 
660   //TODO player stats etc, text log? at most x2?, set thru one user option bool for all (fast blit only?)
661 
662   if(iRegionSilhouette==-1){     // equiped items and humanoid silhouette region
663     silhouettePos = humanoid::GetSilhouetteWhereDefault();
664     if(!silhouettePos.Is0()){
665       silhouettePos.X -= 15; silhouettePos.Y -= 23; //exact top left corner of all equipped items countour
666       silhouettePos-=v2(1,1); //1 dot b4
667       bldSilhouetteTMP.Src = {silhouettePos.X, silhouettePos.Y};
668       int iEqSize=23;
669       v2 v2EqSqr(iEqSize,iEqSize);
670       bldSilhouetteTMP.Border = SILHOUETTE_SIZE+(v2EqSqr*2); //SILHOUETTE_SIZE + equipped items all around
671       bldSilhouetteTMP.Border+=v2(2,2); //compensate for pos-1 and add +1 after border
672       bldSilhouetteTMP.Stretch = 2; // minimum to allow setup
673       iRegionSilhouette = graphics::AddStretchRegion(bldSilhouetteTMP,"Silhouette");
674       graphics::SetSRegionDrawAfterFelist(iRegionSilhouette,true);
675       graphics::SetSRegionDrawRectangleOutline(iRegionSilhouette,true);
676 
677       // alt vanilla silhouette pos
678       if(graphics::GetScale()==1){
679         bldVanillaSilhouetteTMP.Stretch = 2; // minimum to allow setup
680         bldVanillaSilhouetteTMP.Border = SILHOUETTE_SIZE + v2(TILE_SIZE,2);
681         iRegionVanillaSilhouette = graphics::AddStretchRegion(bldVanillaSilhouetteTMP,"AltPosForVanillaSilhouette");
682         graphics::SetSRegionDrawAlways(iRegionVanillaSilhouette,true);
683         graphics::SetSRegionDrawRectangleOutline(iRegionVanillaSilhouette,true);
684       }
685     }
686   }
687 
688   if(iRegionListItem==-1){
689     bldListItemTMP.Dest = ZoomPos;
690     bldListItemTMP.Border = TILE_V2;
691     bldListItemTMP.Stretch = iZoomFactor;
692     iRegionListItem = graphics::AddStretchRegion(bldListItemTMP,"ListItem");
693     graphics::SetSRegionListItem(iRegionListItem);
694     graphics::SetSRegionDrawRectangleOutline(iRegionListItem,true);
695   }
696 
697   if(iRegionItemsUnder==-1){
698     blitdata B = DEFAULT_BLITDATA;
699     B.Stretch=iItemsUnderStretch;
700     iRegionItemsUnder = graphics::AddStretchRegion(B,"ItemsUnderPosShowAboveHead");
701   }
702 
703   UpdateSRegionsXBRZ();
704 }
705 
FantasyName(festring & rfsName)706 void FantasyName(festring& rfsName){ DBG2(rfsName.CStr(),ivanconfig::GetFantasyNamePattern().CStr());
707   if(ivanconfig::GetFantasyNamePattern().IsEmpty())return;
708 
709   NameGen::Generator gen(ivanconfig::GetFantasyNamePattern().CStr());
710 //  NameGen::Random genR(gen);
711   rfsName << gen.toString().c_str(); DBG1(rfsName.CStr());
712 }
713 
Init(cfestring & loadBaseName)714 truth game::Init(cfestring& loadBaseName)
715 {
716   festring absLoadNameOk;
717 
718   if(!loadBaseName.IsEmpty()){
719     absLoadNameOk = SaveName(loadBaseName,true); //will prepend the path
720   }else{
721     if(ivanconfig::GetDefaultName().IsEmpty())
722     {
723       PlayerName.Empty();
724 
725       FantasyName(PlayerName); DBG1(PlayerName.CStr());
726 
727       if(iosystem::StringQuestion(PlayerName, CONST_S("What is your name? (1-20 letters)"),
728                                   v2(30, 46), WHITE, 1, 20, true, true) == ABORTED
729          || PlayerName.IsEmpty())
730         return false;
731     }
732     else
733       PlayerName = ivanconfig::GetDefaultName();
734 
735     CurrentBaseSaveFileName.Empty(); //this is important to prevent loading another character with the same name that was just played in this current session (w/o restarting the game)
736 
737     absLoadNameOk = SaveName(); //default is to use PlayerName
738   }
739 
740 #ifdef WIN32
741   _mkdir("Save");
742   _mkdir("Bones");
743   _mkdir("Scrshot");
744 #endif
745 
746 #ifdef __DJGPP__
747   mkdir("Save", S_IWUSR);
748   mkdir("Bones", S_IWUSR);
749   mkdir("Scrshot", S_IWUSR);
750 #endif
751 
752 #ifdef UNIX
753   mkdir(GetUserDataDir().CStr(), S_IRWXU|S_IRWXG);
754   mkdir(GetSaveDir().CStr(), S_IRWXU|S_IRWXG);
755   mkdir(GetBoneDir().CStr(), S_IRWXU|S_IRWXG);
756   mkdir(GetScrshotDir().CStr(), S_IRWXU|S_IRWXG);
757 #endif
758 
759   LOSTick = 2;
760   DangerFound = 0;
761   CausePanicFlag = false;
762 
763   bool bSuccess=false; DBG3(PlayerName.CStr(),loadBaseName.CStr(),absLoadNameOk.CStr());
764   switch(Load(absLoadNameOk))
765   {
766    case LOADED:
767     {
768       globalwindowhandler::InstallControlLoop(AnimationController);
769       SetIsRunning(true);
770       SetForceJumpToPlayerBe(true);
771       GetCurrentArea()->SendNewDrawRequest();
772       SendLOSUpdateRequest();
773       ADD_MESSAGE("Game loaded successfully.");
774       bSuccess=true;
775       break;
776     }
777    case NEW_GAME:
778     {
779       CurrentSavefileVersion = SAVE_FILE_VERSION;
780 
781       /* New game music */
782       audio::SetPlaybackStatus(0);
783       audio::ClearMIDIPlaylist();
784       audio::LoadMIDIFile("newgame.mid", 0, 100);
785       audio::SetPlaybackStatus(audio::PLAYING);
786 
787       iosystem::TextScreen(CONST_S("You couldn't possibly have guessed this day would differ from any other.\n"
788                                    "It began just as always. You woke up at dawn and drove off the giant spider\n"
789                                    "resting on your face. On your way to work you had serious trouble avoiding\n"
790                                    "the lions and pythons roaming wild around the village. After getting kicked\n"
791                                    "by colony masters for being late you performed your twelve-hour routine of\n"
792                                    "climbing trees, gathering bananas, climbing trees, gathering bananas, chasing\n"
793                                    "monkeys that stole the first gathered bananas, carrying bananas to the village\n"
794                                    "and trying to look happy when real food was distributed.\n\n"
795                                    "Finally you were about to enjoy your free time by taking a quick dip in the\n"
796                                    "nearby crocodile bay. However, at this point something unusual happened.\n"
797                                    "You were summoned to the mansion of Richel Decos, the viceroy of the\n"
798                                    "colony, and were led directly to him."));
799 
800       iosystem::TextScreen(CONST_S("\"I have a task for you, citizen\", said the viceroy picking his golden\n"
801                                    "teeth, \"The market price of bananas has taken a deep dive and yet the\n"
802                                    "central government is about to raise taxes. I have sent appeals to high\n"
803                                    "priest Petrus but received no response. I fear my enemies in Attnam are\n"
804                                    "plotting against me and intercepting my messages before they reach him!\"\n\n"
805                                    "\"That is why you must travel to Attnam with a letter I'll give you and\n"
806                                    "deliver it to Petrus directly. Alas, you somehow have to cross the sea\n"
807                                    "between. Because it's winter, all Attnamese ships are trapped by ice and\n"
808                                    "I have none. Therefore you must venture through the small underwater tunnel\n"
809                                    "connecting our islands. It is infested with monsters, but since you have\n"
810                                    "stayed alive here so long, the trip will surely cause you no trouble.\"\n\n"
811                                    "You have never been so happy! According to the mansion's traveling\n"
812                                    "brochures, Attnam is a peaceful but bustling world city on a beautiful\n"
813                                    "snowy fell surrounded by frozen lakes glittering in the arctic sun just\n"
814                                    "like the diamonds of the imperial treasury. Not that you would believe a\n"
815                                    "word. The point is that tomorrow you can finally forget your home and\n"
816                                    "face the untold adventures ahead."));
817 
818       globalwindowhandler::InstallControlLoop(AnimationController);
819       SetIsRunning(true);
820       InWilderness = true;
821       iosystem::TextScreen(CONST_S("Generating game...\n\nThis may take some time, please wait."),
822                            ZERO_V2, WHITE, false, true, &BusyAnimation);
823       igraph::CreateBackGround(GRAY_FRACTAL);
824       NextCharacterID = 1;
825       NextItemID = 1;
826       NextTrapID = 1;
827       InitScript();
828       CreateTeams();
829       CreateGods();
830       SetPlayer(playerkind::Spawn());
831       Player->SetAssignedName(PlayerName);
832       Player->SetTeam(GetTeam(0));
833       Player->SetNP(SATIATED_LEVEL);
834 
835       for(int c = 0; c < ATTRIBUTES; ++c)
836       {
837         if(c != ENDURANCE)
838           Player->EditAttribute(c, (RAND() & 1) - (RAND() & 1));
839 
840         Player->EditExperience(c, 500, 1 << 11);
841       }
842 
843       Player->SetMoney(Player->GetMoney() + RAND() % 11);
844       GetTeam(0)->SetLeader(Player);
845       InitDangerMap();
846       Petrus = 0;
847       InitDungeons();
848       v2 NewWorldSize = ivanconfig::GetWorldSizeConfig();
849       SetCurrentArea(WorldMap = new worldmap(NewWorldSize.X, NewWorldSize.Y));
850       CurrentWSquareMap = WorldMap->GetMap();
851       WorldMap->Generate();
852       GetCurrentArea()->SendNewDrawRequest();
853       UpdateCamera();
854       SendLOSUpdateRequest();
855       Tick = 0;
856       Turn = 0;
857       InitPlayerAttributeAverage();
858       StoryState = 0;
859       GloomyCaveStoryState = 0;
860       XinrochTombStoryState = 0;
861       FreedomStoryState = 0;
862       AslonaStoryState = 0;
863       RebelStoryState = 0;
864       PlayerIsChampion = false;
865       HasBoat = false;
866       PlayerMassacreMap.clear();
867       PetMassacreMap.clear();
868       MiscMassacreMap.clear();
869       PlayerMassacreAmount = PetMassacreAmount = MiscMassacreAmount = 0;
870       DefaultPolymorphTo.Empty();
871       DefaultSummonMonster.Empty();
872       DefaultWish.Empty();
873       DefaultChangeMaterial.Empty();
874       DefaultDetectMaterial.Empty();
875       Player->GetStack()->AddItem(encryptedscroll::Spawn());
876 
877       if(!ivanconfig::GetNoPet())
878       {
879         character* Doggie = dog::Spawn();
880         Doggie->SetTeam(GetTeam(0));
881         GetWorldMap()->GetPlayerGroup().push_back(Doggie);
882         Doggie->SetAssignedName(ivanconfig::GetDefaultPetName());
883       }
884       WizardMode = false;
885       SeeWholeMapCheatMode = MAP_HIDDEN;
886       GoThroughWallsCheat = false;
887       SumoWrestling = false;
888       GlobalRainTimeModifier = 2048 - (RAND() & 4095);
889       PlayerSumoChampion = false;
890       TouristHasSpider = false;
891       protosystem::InitCharacterDataBaseFlags();
892       memset(EquipmentMemory, 0, sizeof(EquipmentMemory));
893       PlayerRunning = false;
894       InitAttributeMemory();
895       NecroCounter = 0;
896       GameBegan = time(0);
897       LastLoad = time(0);
898       TimePlayedBeforeLastLoad = time::GetZeroTime();
899       commandsystem::ClearSwapWeapons(); //to clear the memory from possibly previously loaded game
900       craftcore::ClearSuspendedList(); //to clear the memory from possibly previously loaded game
901       bool PlayerHasReceivedAllGodsKnownBonus = false;
902       ADD_MESSAGE("You commence your journey to Attnam. Use direction keys to "
903                   "move, '>' to enter an area and '?' to view other commands.");
904 
905       if(IsXMas())
906       {
907         item* Present = banana::Spawn();
908         Player->GetStack()->AddItem(Present);
909         ADD_MESSAGE("Atavus is happy today! He gives you %s.", Present->CHAR_NAME(INDEFINITE));
910       }
911 
912       /* Set off the worldmap music */
913       audio::SetPlaybackStatus(0);
914       audio::ClearMIDIPlaylist();
915       audio::LoadMIDIFile("world.mid", 0, 100);
916       audio::SetPlaybackStatus(audio::PLAYING);
917 
918       bSuccess=true;
919       break;
920     }
921    default:
922     {
923       bSuccess=false;
924       break;
925     }
926   }
927 
928   if(bSuccess){ // for loaded or new game
929     ZoomPos = {RES.X - 104, RES.Y - 112};
930     PrepareStretchRegionsLazy();
931   }
932 
933   return bSuccess;
934 }
935 
DeInit()936 void game::DeInit()
937 {
938   delete WorldMap;
939   WorldMap = 0;
940   int c;
941 
942   for(c = 1; c < Dungeons; ++c)
943     delete Dungeon[c];
944 
945   delete [] Dungeon;
946 
947   for(c = 1; c <= GODS; ++c)
948     delete God[c]; // sorry, Valpuri!
949 
950   delete [] God;
951   pool::BurnHell();
952 
953   for(c = 0; c < Teams; ++c)
954     delete Team[c];
955 
956   delete [] Team;
957   delete GameScript;
958   msgsystem::Format();
959   DangerMap.clear();
960 }
961 
Run()962 void game::Run()
963 {
964   PrepareStretchRegionsLazy();
965 
966   for(;;)
967   {
968     if(!InWilderness)
969     {
970       /* Temporary places */
971       static int Counter = 0;
972 
973       if(++Counter == 10)
974       {
975         CurrentLevel->GenerateMonsters();
976         Counter = 0;
977       }
978 
979       if(CurrentDungeonIndex == ELPURI_CAVE
980          && CurrentLevelIndex == ZOMBIE_LEVEL
981          && !RAND_N(1000 + NecroCounter))
982       {
983         character* Char = necromancer::Spawn(RAND_N(4) ? APPRENTICE_NECROMANCER : MASTER_NECROMANCER);
984         v2 Pos;
985 
986         for(int c2 = 0; c2 < 30; ++c2)
987         {
988           Pos = GetCurrentLevel()->GetRandomSquare(Char);
989 
990           if(abs(Pos.X - Player->GetPos().X) > 20
991              || abs(Pos.Y - Player->GetPos().Y) > 20)
992             break;
993         }
994 
995         if(Pos != ERROR_V2)
996         {
997           Char->SetTeam(GetTeam(MONSTER_TEAM));
998           Char->PutTo(Pos);
999           Char->SetGenerationDanger(GetCurrentLevel()->GetDifficulty());
1000           Char->SignalGeneration();
1001           Char->SignalNaturalGeneration();
1002           ivantime Time;
1003           GetTime(Time);
1004           int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
1005 
1006           if(Modifier > 0)
1007             Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
1008 
1009           NecroCounter += 50;
1010         }
1011         else
1012           delete Char;
1013       }
1014 
1015       if(!(GetTick() % 1000))
1016         CurrentLevel->CheckSunLight();
1017 
1018       if((CurrentDungeonIndex == NEW_ATTNAM
1019           || CurrentDungeonIndex == ATTNAM)
1020          && CurrentLevelIndex == 0)
1021       {
1022         long OldVolume = GlobalRainLiquid->GetVolume();
1023         long NewVolume = Max(long(sin((Tick + GlobalRainTimeModifier) * 0.0003) * 300 - 150), 0L);
1024 
1025         if(NewVolume && !OldVolume)
1026           CurrentLevel->EnableGlobalRain();
1027         else if(!NewVolume && OldVolume)
1028           CurrentLevel->DisableGlobalRain();
1029 
1030         GlobalRainLiquid->SetVolumeNoSignals(NewVolume);
1031 
1032         /*{
1033           item* Item;
1034 
1035           if(!RAND_N(2))
1036           Item = wand::Spawn(1 + RAND_N(12));
1037           else if(!RAND_N(2))
1038           {
1039           Item = beartrap::Spawn();
1040           Item->SetIsActive(true);
1041           Item->SetTeam(MONSTER_TEAM);
1042           }
1043           else if(!RAND_N(2))
1044           {
1045           Item = mine::Spawn();
1046           Item->SetIsActive(true);
1047           Item->SetTeam(MONSTER_TEAM);
1048           }
1049           else
1050           Item = holybanana::Spawn();
1051 
1052           CurrentLevel->GetLSquare(CurrentLevel->GetRandomSquare())->AddItem(Item);
1053           }
1054 
1055           if(!RAND_N(10))
1056           {
1057           character* Char = protosystem::CreateMonster(0, 1000000);
1058           Char->ChangeTeam(GetTeam(RAND() % Teams));
1059           Char->PutTo(CurrentLevel->GetRandomSquare(Char));
1060           }
1061 
1062           if(!RAND_N(5))
1063           {
1064           character* Char;
1065           if(!RAND_N(5))
1066           Char = spider::Spawn(GIANT);
1067           else if(!RAND_N(5))
1068           Char = darkmage::Spawn(1 + RAND_N(4));
1069           else if(!RAND_N(5))
1070           Char = necromancer::Spawn(1 + RAND_N(2));
1071           else if(!RAND_N(5))
1072           Char = chameleon::Spawn();
1073           else if(!RAND_N(5))
1074           Char = kamikazedwarf::Spawn(1 + RAND_N(GODS));
1075           else if(!RAND_N(5))
1076           Char = mommo::Spawn(1 + RAND_N(2));
1077           else if(!RAND_N(3))
1078           Char = bunny::Spawn(RAND_2 ? ADULT_MALE : ADULT_FEMALE);
1079           else if(!RAND_N(3))
1080           Char = eddy::Spawn();
1081           else if(!RAND_N(3))
1082           Char = magicmushroom::Spawn();
1083           else if(!RAND_N(5))
1084           Char = mushroom::Spawn();
1085           else if(!RAND_N(3))
1086           Char = blinkdog::Spawn();
1087           else if(!RAND_N(5))
1088           Char = tourist::Spawn(1 + RAND_N(3));
1089           else if(!RAND_N(5))
1090           Char = hattifattener::Spawn();
1091           else if(!RAND_N(5))
1092           Char = genetrixvesana::Spawn();
1093           else if(!RAND_N(5))
1094           Char = skunk::Spawn();
1095           else if(!RAND_N(5))
1096           Char = ennerbeast::Spawn();
1097           else if(!RAND_N(5))
1098           Char = werewolfhuman::Spawn();
1099           else if(!RAND_N(5))
1100           Char = unicorn::Spawn(1 + RAND_N(3));
1101           else if(!RAND_N(5))
1102           Char = floatingeye::Spawn();
1103           else if(!RAND_N(5))
1104           Char = zombie::Spawn();
1105           else if(!RAND_N(5))
1106           Char = magpie::Spawn();
1107           else if(!RAND_N(5))
1108           Char = elpuri::Spawn();
1109           else if(!RAND_N(5))
1110           Char = vladimir::Spawn();
1111           else if(!RAND_N(5))
1112           Char = billswill::Spawn();
1113           else if(!RAND_N(5))
1114           Char = dolphin::Spawn();
1115           else if(!RAND_N(5))
1116           Char = cossack::Spawn();
1117           else
1118           Char = invisiblestalker::Spawn();
1119 
1120           Char->SetTeam(GetTeam(RAND() % Teams));
1121           Char->PutTo(CurrentLevel->GetRandomSquare(Char));
1122           }*/
1123       }
1124     }
1125 
1126     try
1127     {
1128       pool::Be();
1129       pool::BurnHell();
1130       IncreaseTick();
1131       ApplyDivineTick();
1132     }
1133     catch(quitrequest)
1134     {
1135       graphics::SetDenyStretchedBlit();
1136       break;
1137     }
1138     catch(areachangerequest)
1139     {
1140     }
1141 
1142 //    PrepareStretchRegions();
1143   }
1144 
1145 }
1146 
InitLuxTable()1147 void game::InitLuxTable()
1148 {
1149   if(!LuxTable)
1150   {
1151     Alloc3D(LuxTable, 256, 33, 33);
1152 
1153     for(int c = 0; c < 0x100; ++c)
1154       for(int x = 0; x < 33; ++x)
1155         for(int y = 0; y < 33; ++y)
1156         {
1157           int X = x - 16, Y = y - 16;
1158           LuxTable[c][x][y] = int(c / (double(X * X + Y * Y) / 128 + 1));
1159         }
1160 
1161     atexit(DeInitLuxTable);
1162   }
1163 }
1164 
DeInitLuxTable()1165 void game::DeInitLuxTable()
1166 {
1167   delete [] LuxTable;
1168   LuxTable = 0;
1169 }
1170 
UpdateCameraX()1171 void game::UpdateCameraX()
1172 {
1173   UpdateCameraX(Player->GetPos().X);
1174 }
1175 
UpdateCameraY()1176 void game::UpdateCameraY()
1177 {
1178   UpdateCameraY(Player->GetPos().Y);
1179 }
1180 
UpdateCameraX(int X)1181 void game::UpdateCameraX(int X)
1182 {
1183   UpdateCameraCoordinate(Camera.X, X, GetCurrentArea()->GetXSize(), GetScreenXSize());
1184 }
1185 
UpdateCameraY(int Y)1186 void game::UpdateCameraY(int Y)
1187 {
1188   UpdateCameraCoordinate(Camera.Y, Y, GetCurrentArea()->GetYSize(), GetScreenYSize());
1189 }
1190 
UpdateCameraCoordinate(int & Coordinate,int Center,int Size,int ScreenSize)1191 void game::UpdateCameraCoordinate(int& Coordinate, int Center, int Size, int ScreenSize)
1192 {
1193   int OldCoordinate = Coordinate;
1194 
1195   if(Size < ScreenSize)
1196     Coordinate = (Size - ScreenSize) >> 1;
1197   else if(Center < ScreenSize >> 1)
1198     Coordinate = 0;
1199   else if(Center > Size - (ScreenSize >> 1))
1200     Coordinate = Size - ScreenSize;
1201   else
1202     Coordinate = Center - (ScreenSize >> 1);
1203 
1204   if(Coordinate != OldCoordinate)
1205     GetCurrentArea()->SendNewDrawRequest();
1206 }
1207 
Insult()1208 cchar* game::Insult()
1209 {
1210   static cchar*const Insult[] =
1211   {
1212     "moron",
1213     "silly",
1214     "idiot",
1215     "airhead",
1216     "jerk",
1217     "dork",
1218     "Mr. Mole",
1219     "navastater",
1220     "potatoes-for-eyes",
1221     "lamer",
1222     "mommo-for-brains",
1223     "pinhead",
1224     "stupid-headed person",
1225     "software abuser",
1226     "loser",
1227     "peaballs",
1228     "person-with-problems",
1229     "unimportant user",
1230     "hugger-mugger"
1231   };
1232 
1233   return Insult[RAND_N(sizeof(Insult) / sizeof(Insult[0]))];
1234 }
1235 
1236 /* DefaultAnswer = REQUIRES_ANSWER the question requires an answer */
1237 
TruthQuestion(cfestring & String,int DefaultAnswer,int OtherKeyForTrue)1238 truth game::TruthQuestion(cfestring& String, int DefaultAnswer, int OtherKeyForTrue)
1239 {
1240   if(DefaultAnswer == NO)
1241     DefaultAnswer = 'n';
1242   else if(DefaultAnswer == YES)
1243     DefaultAnswer = 'y';
1244   else if(DefaultAnswer != REQUIRES_ANSWER)
1245     ABORT("Illegal TruthQuestion DefaultAnswer send!");
1246 
1247   int FromKeyQuestion = KeyQuestion(String, DefaultAnswer, 5, 'y', 'Y', 'n', 'N', OtherKeyForTrue);
1248   return FromKeyQuestion == 'y' || FromKeyQuestion == 'Y' || FromKeyQuestion == OtherKeyForTrue;
1249 }
1250 
DrawEverything()1251 void game::DrawEverything()
1252 {
1253   DrawEverythingNoBlit();
1254   graphics::BlitDBToScreen();
1255 }
1256 
OnScreen(v2 Pos)1257 truth game::OnScreen(v2 Pos)
1258 {
1259   return Pos.X >= 0 && Pos.Y >= 0 && Pos.X >= Camera.X && Pos.Y >= Camera.Y
1260       && Pos.X < GetCamera().X + GetScreenXSize() && Pos.Y < GetCamera().Y + GetScreenYSize();
1261 }
1262 
SetMapNote(lsquare * lsqrN,festring What)1263 void game::SetMapNote(lsquare* lsqrN,festring What)
1264 {
1265   festring finalWhat;
1266   finalWhat << game::MapNoteToken();
1267   finalWhat << What;
1268   lsqrN->Engrave(finalWhat);
1269 }
1270 
1271 bool bDrawMapOverlayEnabled=false;
1272 int iMapOverlayDrawCount=0;
ToggleDrawMapOverlay()1273 bool game::ToggleDrawMapOverlay()
1274 {
1275   SetDrawMapOverlay(!bDrawMapOverlayEnabled);
1276   return bDrawMapOverlayEnabled;
1277 }
1278 
RefreshDrawMapOverlay()1279 void game::RefreshDrawMapOverlay()
1280 {
1281   iMapOverlayDrawCount=0;
1282 }
1283 
SetDrawMapOverlay(bool b)1284 void game::SetDrawMapOverlay(bool b)
1285 {
1286   static bool bDummyInit  = [](){
1287     graphics::AddDrawAboveAll(&DrawMapOverlay     ,1000,"Map"     );
1288     graphics::AddDrawAboveAll(&DrawMapNotesOverlay,1100,"MapNotes"); return true;}();
1289 
1290   bDrawMapOverlayEnabled=b;
1291 
1292   if(bDrawMapOverlayEnabled)RefreshDrawMapOverlay();
1293 }
1294 
finalMapBmp(blitdata & bld,int iStretch,bitmap * bmpFrom,v2 & v2TopLeftFinal,v2 & v2MapScrSizeFinal,v2 v2Center)1295 bitmap* finalMapBmp(blitdata& bld, int iStretch, bitmap* bmpFrom, v2& v2TopLeftFinal, v2& v2MapScrSizeFinal, v2 v2Center){
1296   bld.Stretch = iStretch;
1297   bld.Border = bmpFrom->GetSize();
1298 
1299   v2MapScrSizeFinal = bld.Border * bld.Stretch;
1300   v2TopLeftFinal = v2Center -(v2MapScrSizeFinal/2);
1301 
1302   if(bld.Bitmap==NULL || bld.Bitmap->GetSize()!=v2MapScrSizeFinal){
1303     delete bld.Bitmap;
1304     bld.Bitmap = new bitmap(v2MapScrSizeFinal);
1305   }
1306 
1307   graphics::Stretch(true,bmpFrom,bld,false);
1308 
1309   return bld.Bitmap;
1310 };
1311 
MapNoteToken()1312 char game::MapNoteToken()
1313 {
1314   return '#';
1315 }
1316 
1317 int iMapNotesRotation=0;
RotateMapNotes()1318 int game::RotateMapNotes()
1319 {
1320   iMapNotesRotation++;
1321   if(iMapNotesRotation>3)
1322     iMapNotesRotation=0;
1323 
1324   return iMapNotesRotation;
1325 }
1326 
1327 std::vector<festring> afsAutoPickupMatch;
1328 pcre *reAutoPickup=NULL;
UpdateAutoPickUpMatching()1329 void game::UpdateAutoPickUpMatching() //simple matching syntax
1330 {
1331   afsAutoPickupMatch.clear();
1332 
1333   bool bSimple=false;
1334   if(bSimple){ //TODO just drop the simple code? or start the string with something to let it be used instead of regex? tho is cool to let ppl learn regex :)
1335     if(ivanconfig::GetAutoPickUpMatching().GetSize()==0 || ivanconfig::GetAutoPickUpMatching()[0]=='!')return;
1336 
1337     std::stringstream ss(ivanconfig::GetAutoPickUpMatching().CStr());
1338     std::string match;
1339     while(std::getline(ss,match,'|'))
1340       afsAutoPickupMatch.push_back(festring(match.c_str()));
1341   }else{
1342     //TODO test regex about: ignoring broken lanterns and bottles, ignore sticks on fire but pickup scrolls on fire
1343   //  static bool bDummyInit = [](){reAutoPickup=NULL;return true;}();
1344     const char *errMsg;
1345     int iErrOffset;
1346     if(reAutoPickup)pcre_free(reAutoPickup);
1347     reAutoPickup = pcre_compile(
1348       ivanconfig::GetAutoPickUpMatching().CStr(), //pattern
1349       0, //no options
1350       &errMsg,    &iErrOffset,
1351       0); // default char tables
1352     if (!reAutoPickup){
1353       std::vector<festring> afsFullProblems;
1354       afsFullProblems.push_back(festring(errMsg));
1355       afsFullProblems.push_back(festring()+"offset:"+iErrOffset);
1356       bool bDummy = iosystem::AlertConfirmMsg("regex validation failed, if ignored will just not work at all",afsFullProblems,false);
1357     }
1358   }
1359 }
IsAutoPickupMatch(cfestring fsName)1360 bool game::IsAutoPickupMatch(cfestring fsName) {
1361   return pcre_exec(reAutoPickup, 0, fsName.CStr(), fsName.GetSize(), 0, 0, NULL, 0) >= 0;
1362 }
CheckAutoPickup(square * sqr)1363 int game::CheckAutoPickup(square* sqr)
1364 {
1365   if(sqr==NULL)
1366     sqr = PLAYER->GetSquareUnder();
1367 
1368   if(dynamic_cast<lsquare*>(sqr)==NULL)
1369     return 0;
1370 
1371   lsquare* lsqr = (lsquare*)sqr;
1372 
1373   static bool bDummyInit = [](){UpdateAutoPickUpMatching();return true;}();
1374   itemvector iv;
1375   lsqr->GetStack()->FillItemVector(iv);
1376   int iTot=0;
1377   for(int i=0;i<iv.size();i++){
1378     item* it = iv[i];
1379     if(it->GetRoom() && it->GetRoom()->GetMaster())continue; //not from owned rooms
1380     if(it->GetSpoilLevel()>0)continue;
1381     bool b=false;
1382     if(!b && ivanconfig::IsAutoPickupThrownItems() && it->HasTag('t') )b=true; //was thrown
1383     if(!b && !it->HasTag('d')){
1384       if(reAutoPickup!=NULL){
1385         if(IsAutoPickupMatch(it->GetName(DEFINITE))){
1386           b=true;
1387         }
1388       }
1389     }
1390     if(!b){ //TODO use player's perception, in case of a stack of items, to allow random pickup based on item volume (size) where smaller = harder like tiny rings, to compensate for the easiness of not losing a round having to pick up the item interactively
1391       for(int i=0;i<afsAutoPickupMatch.size();i++){ //each simple match
1392         if(it->GetNameSingular().Find(afsAutoPickupMatch[i].CStr(),0) != festring::NPos){
1393           b=true;
1394           break; //each simple match loop
1395         }
1396       }
1397     }
1398     if(b){
1399       it->MoveTo(PLAYER->GetStack());
1400       ADD_MESSAGE("%s picked up.", it->GetName(INDEFINITE).CStr());
1401       iTot++;
1402     }
1403   }
1404 
1405   return iTot;
1406 }
1407 
CheckAddAutoMapNote(square * sqr)1408 bool game::CheckAddAutoMapNote(square* sqr)
1409 {
1410   if(sqr==NULL)
1411     sqr = PLAYER->GetSquareUnder();
1412 
1413   if(dynamic_cast<lsquare*>(sqr)==NULL)
1414     return false;
1415 
1416   lsquare* lsqr = (lsquare*)sqr;
1417 
1418   if(lsqr->GetEngraved())
1419     return false;
1420 
1421   olterrain* olt = lsqr->GetOLTerrain();
1422   if(!olt)return false;
1423 
1424   festring fs;
1425   if(fs.GetSize()==0 && dynamic_cast<altar*>(olt)!=NULL)
1426     fs<<olt->GetMasterGod()->GetName()<<" altar";
1427   if(fs.GetSize()==0 && dynamic_cast<sign*>(olt)!=NULL)
1428     fs<<"Sign: "<<((sign*)olt)->GetText();
1429 
1430   if(
1431     dynamic_cast<door*>(olt)!=NULL ||
1432     dynamic_cast<christmastree*>(olt)!=NULL ||
1433     dynamic_cast<coffin*>(olt)!=NULL ||
1434     dynamic_cast<fountain*>(olt)!=NULL || //TODO exclude cathedral?
1435     dynamic_cast<monsterportal*>(olt)!=NULL ||
1436     dynamic_cast<stairs*>(olt)!=NULL ||
1437     (dynamic_cast<decoration*>(olt)!=NULL && (
1438       olt->GetConfig() == ANVIL ||
1439       olt->GetConfig() == ARM_CHAIR ||
1440       olt->GetConfig() == BENCH ||
1441       olt->GetConfig() == CHAIR ||
1442       olt->GetConfig() == CHEAP_BED ||
1443       olt->GetConfig() == COUCH ||
1444       olt->GetConfig() == DESK ||
1445       olt->GetConfig() == DOUBLE_BED ||
1446       olt->GetConfig() == EXPENSIVE_BED ||
1447       olt->GetConfig() == FORGE ||
1448       olt->GetConfig() == OVEN ||
1449       olt->GetConfig() == PEDESTAL ||
1450       olt->GetConfig() == PLAIN_BED ||
1451       olt->GetConfig() == STRANGE_TREE ||
1452       olt->GetConfig() == TAILORING_BENCH ||
1453       olt->GetConfig() == WELL ||
1454       olt->GetConfig() == WORK_BENCH
1455     ))
1456   ){
1457     olt->AddName(fs,INDEFINITE);
1458 //    fs<<olt->GetNameSingular();
1459   }
1460 
1461   if(fs.GetSize()>0){
1462     SetMapNote(lsqr,fs);
1463     game::RefreshDrawMapOverlay();
1464     return true;
1465   }
1466 
1467   return false;
1468 }
1469 
1470 bool bShowMapNotes=true;
ToggleShowMapNotes()1471 bool game::ToggleShowMapNotes()
1472 {
1473   bShowMapNotes=!bShowMapNotes;
1474   return bShowMapNotes;
1475 }
1476 
1477 bool bImersiveMapMode=false;
1478 struct mapnote{
1479   lsquare* lsqr;
1480   v2 tinyMapPos;
1481   v2 scrPos;
1482   cchar* note;
1483 
1484   int iNoteLength;
1485   int iNoteWidthInPixels;
1486   v2 v2LineHook;
1487   v2 basePos;
1488 
mapnotemapnote1489   mapnote(lsquare* lsqr_,cchar* note_,v2 tinyMapPos_):lsqr(lsqr_),note(note_),tinyMapPos(tinyMapPos_),iNoteLength(0),
1490     iNoteWidthInPixels(0){}
1491 };
1492 static std::vector<mapnote> vMapNotes;
1493 v2 v2MapTopLeft;
1494 v2 v2MapSize;
1495 col16 colMapNoteBkg;
1496 int iNoteHighlight=-1;
GetHighlightedMapNoteLSquare()1497 lsquare* game::GetHighlightedMapNoteLSquare()
1498 {DBGLN;
1499   if(!bDrawMapOverlayEnabled)return NULL;
1500   if(!bShowMapNotes)return NULL;
1501   if(iNoteHighlight==-1)return NULL;DBGLN;
1502   if(iNoteHighlight>=vMapNotes.size())return NULL;DBGLN;
1503   return vMapNotes[iNoteHighlight].lsqr; //no need to expose mapnote, all info required is at lsqr
1504 }
validateV2(v2 v2Chk,bitmap * buffer=NULL,v2 Border=v2 ())1505 bool validateV2(v2 v2Chk, bitmap* buffer=NULL, v2 Border=v2()){
1506   if(v2Chk.X<0 || v2Chk.Y<0)return false;
1507 
1508   if(buffer!=NULL){
1509     if(v2Chk.X > buffer->GetSize().X || v2Chk.Y > buffer->GetSize().Y)return false;
1510 
1511     if(!Border.Is0()){
1512       v2 ending = v2Chk+Border;
1513       if(ending.X>buffer->GetSize().X || ending.Y>buffer->GetSize().Y)return false;
1514     }
1515   }
1516 
1517   return true;
1518 }
DrawMapNotesOverlay(bitmap * buffer)1519 void game::DrawMapNotesOverlay(bitmap* buffer)
1520 {
1521   if(!bDrawMapOverlayEnabled)return;
1522 
1523   if(!bShowMapNotes)return;
1524 
1525   if(vMapNotes.size()==0)return;
1526 
1527 //  if(!bImersiveMapMode)return; //the problem is space for the auto positioning of notes
1528 
1529   //TODO draw to a bitmap in the 1st call and just fast blit it later (with mask), unless it becomes animated in some way.
1530   int iLineHeightPixels=15; //line height in pixels
1531   int iFontWidth=8; //font width
1532   int iM=3; //margin
1533 
1534   const static int iTotCol=5;
1535   static col16 ac[iTotCol];//={BLACK,DARK_GRAY};
1536   static bool bDummyInit = [](){
1537     int step=20;
1538     for(int i=0;i<iTotCol;i++){
1539       ac[i]=MakeRGB16(i*step,i*step,i*step);
1540     }return true;}();
1541 
1542   int iMaxLineLength=0;
1543   for(int i=0;i<vMapNotes.size();i++){
1544     vMapNotes[i].iNoteLength=strlen(vMapNotes[i].note);
1545     if(vMapNotes[i].iNoteLength>iMaxLineLength)
1546       iMaxLineLength=vMapNotes[i].iNoteLength;
1547   }
1548 
1549   v2 v2MapNotesTopLeft;
1550   bool bHookAtRight=false;
1551   int iMaxW=0;
1552   switch(iMapNotesRotation){
1553   case 0: //right
1554     v2MapNotesTopLeft = v2MapTopLeft+v2(v2MapSize.X+iM,0);
1555     break;
1556   case 1: //below
1557     v2MapNotesTopLeft = v2MapTopLeft+v2(0,v2MapSize.Y+iM);
1558     break;
1559   case 2: //left
1560     iMaxW=(iMaxLineLength+1)*iFontWidth;
1561     v2MapNotesTopLeft = v2MapTopLeft+v2(-(iMaxW+iM),0);
1562     bHookAtRight=true;
1563     break;
1564   case 3: //above
1565     v2MapNotesTopLeft = v2MapTopLeft+v2(0,-((vMapNotes.size()*iLineHeightPixels) + iM));
1566     break;
1567   }
1568 
1569   iNoteHighlight=-1;
1570   for(int i=0;i<vMapNotes.size();i++){
1571     vMapNotes[i].basePos=v2MapNotesTopLeft+v2(iM,i*iLineHeightPixels);
1572 
1573 //    int w=iFontWidth*strlen(vMapNotes[i].note)+iM;
1574     vMapNotes[i].iNoteWidthInPixels=iFontWidth*vMapNotes[i].iNoteLength;
1575     int w=vMapNotes[i].iNoteWidthInPixels+iM;
1576     if(bHookAtRight)vMapNotes[i].basePos.X+=iMaxW-w;
1577 
1578     v2 bkgTL=vMapNotes[i].basePos-v2(iM,iM);
1579     v2 bkgB=v2(w,iLineHeightPixels);
1580 
1581     v2 mouse = globalwindowhandler::GetMouseLocation();
1582     if( iNoteHighlight==-1 &&
1583         mouse.X >= bkgTL.X         && mouse.Y >= bkgTL.Y          &&
1584         mouse.X < (bkgTL.X+bkgB.X) && mouse.Y < (bkgTL.Y+bkgB.Y)
1585     ){
1586       iNoteHighlight=i;
1587     }
1588 
1589 //    col16 colBkg = iNoteHighlight==i ? colBkg=YELLOW : colMapNoteBkg;
1590     if(validateV2(bkgTL,buffer,bkgB)){
1591       col16 colMapNoteBkg2=colMapNoteBkg;
1592       if(festring(vMapNotes[i].note).Find("!!")!=festring::NPos)
1593         colMapNoteBkg2=RED;
1594       else
1595       if(festring(vMapNotes[i].note).Find("!")!=festring::NPos)
1596         colMapNoteBkg2=BLUE;
1597 
1598       buffer->Fill(bkgTL,bkgB,colMapNoteBkg2); //bkg
1599       buffer->DrawRectangle(bkgTL,bkgTL+bkgB,LIGHT_GRAY,iNoteHighlight==i); //bkg
1600     }
1601 
1602     vMapNotes[i].v2LineHook=vMapNotes[i].basePos;
1603     if(bHookAtRight)vMapNotes[i].v2LineHook.X+=w;
1604     switch(iMapNotesRotation){
1605     case 1: //below
1606     case 3: //above
1607       vMapNotes[i].v2LineHook.X+=iFontWidth*i;
1608       break;
1609     }
1610   }
1611 
1612   // line
1613   for(int i=0;i<vMapNotes.size();i++){ DBG7(i,vMapNotes.size(),DBGAV2(vMapNotes[i].scrPos),DBGAV2(vMapNotes[i].v2LineHook),ac[i%iTotCol],iNoteHighlight==i, iMapOverlayDrawCount);
1614     if(validateV2(vMapNotes[i].scrPos,buffer) && validateV2(vMapNotes[i].v2LineHook,buffer)){
1615       bool bNH = iNoteHighlight==i;
1616       buffer->DrawLine(vMapNotes[i].scrPos, vMapNotes[i].v2LineHook, bNH ? WHITE : ac[i%iTotCol], bNH);
1617     }
1618   }
1619 
1620   // note
1621   for(int i=0;i<vMapNotes.size();i++)
1622     if(validateV2(vMapNotes[i].basePos,buffer))
1623       FONT->Printf(buffer, vMapNotes[i].basePos, WHITE, "%s", vMapNotes[i].note);
1624 }
1625 
1626 const char* cHugeMap="Cannot display a map that is as big as the world!";
DrawMapOverlay(bitmap * buffer)1627 void game::DrawMapOverlay(bitmap* buffer)
1628 { DBGLN;
1629   if(!bDrawMapOverlayEnabled)return;
1630 
1631   if(ivanconfig::GetStartingDungeonGfxScale()==1){
1632     ADD_MESSAGE(cHugeMap);
1633     bDrawMapOverlayEnabled=false;
1634     return;
1635   }
1636 
1637   bool bUsexBRZ=false;
1638   int iImersiveMap=0;
1639 
1640   switch(ivanconfig::GetShowMap()){
1641   case 0: //mmm... just not using xBRZ
1642     break;
1643   case 1:
1644     bUsexBRZ=ivanconfig::IsXBRZScale();
1645     break;
1646   case 2:
1647     iImersiveMap=1;
1648     break;
1649   case 3:
1650     iImersiveMap=2;
1651     break;
1652   case 4:
1653     iImersiveMap=3;
1654     break;
1655   }
1656   bImersiveMapMode = iImersiveMap>0;
1657 
1658   //it actually only works (for now) if there is any dungeon stretching going on
1659   if(buffer==NULL)return; //TODO can this happen?
1660 
1661   static bitmap* bmpMapBuffer=NULL;
1662 
1663   int iMapTileSizeMax=32; //TODO this starting is too big if known map is still tiny?
1664   int iMapTileSize=iMapTileSizeMax;
1665   static v2 v2TopLeft(0,0);
1666   static v2 v2Center(0,0);
1667   static v2 v2MapScrSize(0,0);
1668   static v2 v2BmpSize(0,0);
1669   static v2 v2TopLeftFinal(0,0);
1670   static v2 v2MapScrSizeFinal(0,0);
1671   static bitmap* bmpFinal;
1672 
1673   bool bTransparentMap = bPositionQuestionMode && (CursorPos != PLAYER->GetPos()) && ivanconfig::IsTransparentMapLM();
1674 
1675   if(bPositionQuestionMode){
1676     static v2 v2PreviousCursorPos;
1677     if(v2PreviousCursorPos != CursorPos){
1678       v2PreviousCursorPos = CursorPos;
1679       RefreshDrawMapOverlay();
1680     }
1681   }
1682 
1683   if(iMapOverlayDrawCount==0){
1684 //    static level* lvlLast=NULL; //this is just a changed dungeon indicator
1685 //    if(lvlLast == game::GetCurrentLevel())return;
1686 //    lvlLast=game::GetCurrentLevel();
1687 
1688 //      static int iR=0xFF, iG=0xFF*0.80, iB=0xFF*0.60; //old paper like
1689 //      static col16 colorMapBkg=MakeRGB16(iR,iG,iB);
1690 //      static const int iLumTot=5;
1691 //      static col16 bkg[iLumTot];
1692 //      static col24 lum[iLumTot];static bool bDummyInit=[](){
1693 //        for(int i=0;i<iLumTot;i++){
1694 //          float fD   = 0.02;
1695 //          float f    = 1.00 -(fD*iLumTot) + fD*i;
1696 //          lum[i]=MakeRGB24(iR*f   , iG*f   , iB*f   );
1697 //
1698 //          float fBkgD = 0.05;
1699 //          float fBkg = 1.00 -(fBkgD*iLumTot) + fBkgD*i; //col16 has less variations
1700 //          bkg[i]=MakeRGB16(iR*fBkg, iG*fBkg, iB*fBkg);
1701 //
1702 //          DBG5(i,f,fBkg,lum[i],bkg[i]);
1703 //        };return true;}();
1704 
1705     v2 v2KnownDungeonSize;
1706     v2 v2Min(game::GetCurrentLevel()->GetXSize(),game::GetCurrentLevel()->GetYSize());
1707     v2 v2Max(0,0);
1708     for(int iY=0;iY<game::GetCurrentLevel()->GetYSize();iY++){for(int iX=0;iX<game::GetCurrentLevel()->GetXSize();iX++){
1709       static lsquare* lsqr;lsqr=CurrentLevel->GetLSquare(iX,iY);
1710       if(lsqr->HasBeenSeen()){
1711         if(v2Min.X > lsqr->GetPos().X) v2Min.X = lsqr->GetPos().X;
1712         if(v2Min.Y > lsqr->GetPos().Y) v2Min.Y = lsqr->GetPos().Y;
1713         if(v2Max.X < lsqr->GetPos().X) v2Max.X = lsqr->GetPos().X;
1714         if(v2Max.Y < lsqr->GetPos().Y) v2Max.Y = lsqr->GetPos().Y;
1715       }
1716 
1717       v2KnownDungeonSize = (v2Max+v2(1,1)) -v2Min;
1718     }} DBG3(DBGAV2(v2Min),DBGAV2(v2Max),DBGAV2(v2KnownDungeonSize));
1719 
1720 //      v2 v2FullDungeonSize=v2(game::GetCurrentLevel()->GetXSize(),game::GetCurrentLevel()->GetYSize());
1721     while(iMapTileSizeMax*v2KnownDungeonSize.X > RES.X*0.9)iMapTileSizeMax--;
1722     while(iMapTileSizeMax*v2KnownDungeonSize.Y > RES.Y*0.9)iMapTileSizeMax--;
1723     iMapTileSize=iMapTileSizeMax;
1724     if(bImersiveMapMode || bUsexBRZ){
1725       iMapTileSize=1; //1 works best with xBRZ (2 makes it blocky again)
1726 
1727       if(bImersiveMapMode)
1728         iMapTileSizeMax = 3 + iImersiveMap; //forces x2 scale tiny map
1729     }DBG2(iMapTileSizeMax,iMapTileSize);
1730     /********** ONLY USE iMapTileSize BELOW HERE!!! *************/
1731 
1732     v2 v2MapTileSize(iMapTileSize,iMapTileSize);
1733 
1734     v2MapScrSize=v2KnownDungeonSize*iMapTileSize;
1735 
1736     v2 v2DungeonScrSize(GetScreenXSize(),GetScreenYSize()); //the visible dungeon size b4 stretching
1737     v2DungeonScrSize *= TILE_SIZE*ivanconfig::GetStartingDungeonGfxScale(); //the final size in pixels
1738     DBG4(DBGAV2(v2KnownDungeonSize),DBGAV2(area::getTopLeftCorner()),DBGAV2(v2DungeonScrSize),DBGAV2(v2MapScrSize));
1739 //      v2 v2VisibleDungeonScrSize=v2CL*TILE_SIZE;
1740     v2Center = area::getTopLeftCorner() +v2DungeonScrSize/2;
1741     v2TopLeft = v2Center -v2MapScrSize/2;
1742     if(v2TopLeft.X<0)v2TopLeft.X=0;
1743     if(v2TopLeft.Y<0)v2TopLeft.Y=0;
1744 //        v2(
1745 //          RES.X/2 -(v2CL.X * iMapTileSize)/2,
1746 //          RES.Y/2 -(v2CL.Y * iMapTileSize)/2);
1747 
1748     v2BmpSize=v2KnownDungeonSize*iMapTileSize;
1749     if(bmpMapBuffer==NULL || bmpMapBuffer->GetSize()!=v2BmpSize){
1750       delete bmpMapBuffer;
1751       bmpMapBuffer=new bitmap(v2BmpSize);
1752     }
1753 //    bmpMapBuffer->ClearToColor(TRANSPARENT_COLOR);
1754     bmpMapBuffer->ClearToColor(BLACK);
1755 
1756     v2 v2PlayerScrPos(0,0);
1757     v2 v2CursorScrPos(-1,-1);
1758     v2 v2Dest(0,0);
1759     vMapNotes.clear();
1760     std::vector<v2> RouteGoOn = commandsystem::GetRouteGoOnCopy();
1761     for(int iY=v2Min.Y;iY<=v2Max.Y;iY++){
1762 //        B.Dest.Y = v2TopLeft.Y +iY*iMapTileSize;
1763       v2Dest.Y = (iY-v2Min.Y)*iMapTileSize;
1764 
1765       for(int iX=v2Min.X;iX<=v2Max.X;iX++){
1766 //          B.Dest.X = v2TopLeft.X +iX*iMapTileSize;
1767         v2Dest.X = (iX-v2Min.X)*iMapTileSize; DBGSV2(v2Dest);
1768 
1769         static v2 v2SqrPos;v2SqrPos.X=iX;v2SqrPos.Y=iY;
1770 
1771         static lsquare* lsqr;lsqr=CurrentLevel->GetLSquare(v2SqrPos);
1772 
1773 //          static float fStepDelay=3.0;
1774 //          static int iAdd;iAdd = ((int)(clock()/(CLOCKS_PER_SEC*fStepDelay))) % ((iLumTot-1)*2); //moving waves
1775 //          static int iLumIndex;iLumIndex = abs( ((iX+iY+iAdd)%((iLumTot-1)*2)) - (iLumTot-1)); //like a wave from 0 to iLumTot to 0 to iLumTot
1776 //          B.Luminance=lum[iLumIndex]; DBG2(iLumIndex,B.Luminance);
1777 
1778 //          static int iR=0xFF, iG=0xFF*0.80, iB=0xFF*0.60; //old paper like
1779 //          static float fFrom=0.95;
1780 //          static float fStep=0.10;
1781 //
1782 //          static float fW=fFrom;
1783 //          static col16 colorWall  =MakeRGB16(iR*fW,iG*fW,iB*fW);
1784 //
1785 //          static float fF=fFrom-fStep;
1786 //          static col16 colorFloor =MakeRGB16(iR*fF,iG*fF,iB*fF);
1787 //
1788 //          static float fB=fFrom-fStep; //always darker than everything else based on height
1789 //          static col16 colorMapBkg=MakeRGB16(iR*fB,iG*fB,iB*fB);
1790 
1791         static col16 colorNaturalWall,colorBuiltWall,colorFloor,colorMapBkg;
1792         static bool bDummyInit = [](){
1793           int iR=0xFF, iG=0xFF*0.80, iB=0xFF*0.60; //old paper like, well.. should be at least ;)
1794           float fFrom=0.95;
1795           float fStep=0.15;
1796           cint iTot=6; // was 1.0/fStep;
1797           col16 clMap[iTot];
1798           for(int i=0;i<iTot;i++){
1799             float f=fFrom -fStep*i;
1800             clMap[i]=MakeRGB16(iR*f,iG*f,iB*f); DBG1(clMap[i]);
1801           }
1802           int k=0;
1803           colorBuiltWall  =clMap[k++];
1804           colorNaturalWall=clMap[k++];
1805           colorFloor      =clMap[k++];
1806           colorMapBkg     =clMap[k++]; //always darker than everything else based on height
1807           colMapNoteBkg=colorMapBkg;
1808           return true;
1809         }();
1810 
1811         bool bDrawSqr=true;
1812         col16 colorO;
1813         static col16 colorDoor     =MakeRGB16(0xFF*0.66, 0xFF*0.33,        0); //brown
1814         static col16 colorFountain =MakeRGB16(        0,         0,0xFF     ); //blue
1815         static col16 colorUp       =MakeRGB16(        0, 0xFF     ,        0); //green
1816         static col16 colorDown     =MakeRGB16(        0, 0xFF*0.50,        0); //dark green
1817         static col16 colorAltar    =MakeRGB16(0xFF*0.50,         0,0xFF     ); //purple
1818         static col16 colorNote     =MakeRGB16(0xFF*0.90, 0xFF*0.90,0xFF*0.90); //just not white because white is used as look mode indicator on map
1819 //        static col16 colorGoOnRoute=MakeRGB16(0xFF*0.75, 0xFF*0.75,0xFF*0.75); //light gray
1820         static col16 colorGoOnRoute=MakeRGB16(        0, 0xFF*0.75,0xFF     ); //cyan
1821 //            static col16 colorOnGround=MakeRGB16(0xFF*0.80, 0xFF*0.50,0xFF*0.20); //orange
1822         if(lsqr->HasBeenSeen()){
1823           static const int iTotRM=5 +1; //5 is max rest modifier from dat files
1824           static col16 colorOnGroundRM[iTotRM];
1825           static bool bDummyInit2 = [](){
1826             int iR=0xFF, iG=0xFF*0.70, iB=0xFF*0.40; //light orange
1827             float fFrom=1.00;
1828             float fStep=0.05;
1829             for(int i=0;i<iTotRM;i++){
1830               float f=fFrom -fStep*(iTotRM-1 -i);
1831               colorOnGroundRM[i]=MakeRGB16(iR*f,iG*f,iB*f); DBG1(colorOnGroundRM[i]);
1832             }
1833 //              colorOnGround = colorOnGroundRM[0];
1834             return true;
1835           }();
1836 
1837           static olterrain* olt;olt = lsqr->GetOLTerrain();
1838           cchar* note = lsqr->GetEngraved();
1839           if(note!=NULL && note[0]==game::MapNoteToken())//{
1840             vMapNotes.push_back(mapnote(lsqr,note,v2Dest));
1841           //              colorO=colorNote;
1842 //            }else
1843           if(olt){
1844             if(olt->IsDoor()){
1845               colorO=colorDoor;
1846             }else if(olt->IsWall()){
1847               if(dynamic_cast<earth*>(olt)!=NULL)
1848                 colorO=colorNaturalWall;
1849               else
1850                 colorO=colorBuiltWall;
1851             }else if(olt->IsFountainWithWater()){
1852               colorO=colorFountain;
1853 //              }else if(olt->IsUpLink()){
1854             }else if(olt->GetConfig() == STAIRS_UP   || olt->GetConfig() == SUMO_ARENA_EXIT ){
1855               colorO=colorUp;
1856             }else if(olt->GetConfig() == STAIRS_DOWN || olt->GetConfig() == SUMO_ARENA_ENTRY){
1857               colorO=colorDown;
1858             }else if(dynamic_cast<altar*>(olt)!=NULL){
1859               colorO=colorAltar;
1860             }else if(olt->IsOnGround()){ //LAST ONE! as is generic thing
1861 //                if(olt->GetRestModifier()>1)
1862 //                  colorO=colorOnGround;
1863 //                else
1864 //                  colorO=colorOnGround;
1865               colorO=colorOnGroundRM[olt->GetRestModifier()]; //TODO this may break if another RM level is configured at .dat files
1866             }
1867           }else{ //floor
1868             colorO=colorFloor;
1869             if(bTransparentMap)bDrawSqr=false;
1870           }
1871 
1872           if(lsqr->IsMaterialDetected()) //color override
1873             colorO=YELLOW;
1874 
1875         }else{
1876           colorO=colorMapBkg;
1877           if(bTransparentMap)bDrawSqr=false;
1878         }
1879 
1880         if(RouteGoOn.size()>0)
1881           for(auto v2Rt = RouteGoOn.begin(); v2Rt != RouteGoOn.end(); v2Rt++)
1882             if(v2SqrPos == *v2Rt){
1883               colorO=colorGoOnRoute;
1884               bDrawSqr=true;
1885               break;
1886             }
1887 //          for(std::list<v2>::iterator itrRt = RouteGoOn.begin(); itrRt != RouteGoOn.end(); itrRt++)
1888 //            if(itrRt->second == v2SqrPos){
1889 //              colorO=colorGoOnRoute;
1890 //              bDrawSqr=true;
1891 //              break;
1892 //            }
1893 
1894         if(bDrawSqr)
1895           bmpMapBuffer->Fill(v2Dest, v2MapTileSize, colorO);
1896 
1897         if(CursorPos == v2SqrPos)
1898           v2CursorScrPos=v2Dest;
1899 
1900         if(PLAYER->GetPos() == v2SqrPos)
1901           v2PlayerScrPos=v2Dest;
1902       }
1903     }
1904 
1905 //      graphics::DrawRectangleOutlineAround(
1906 //        B.Bitmap, v2TopLeft, v2CL*iMapTileSize, LIGHT_GRAY, true);
1907 
1908     // player location. general override
1909     if(iMapTileSize<3){
1910       if(bPositionQuestionMode && v2CursorScrPos!=v2(-1,-1))
1911         bmpMapBuffer->Fill(v2CursorScrPos, v2MapTileSize, WHITE);
1912 
1913       bmpMapBuffer->Fill(v2PlayerScrPos, v2MapTileSize, RED);
1914     }else{
1915       if(bPositionQuestionMode && v2CursorScrPos!=v2(-1,-1))
1916         graphics::DrawRectangleOutlineAround(
1917           bmpMapBuffer, v2CursorScrPos-v2(1,1), v2MapTileSize+v2(2,2), WHITE, iMapTileSize>12);
1918 
1919       graphics::DrawRectangleOutlineAround(
1920         bmpMapBuffer, v2PlayerScrPos, v2MapTileSize, RED, iMapTileSize>12);
1921     }
1922 
1923     bmpFinal = bmpMapBuffer;
1924     v2TopLeftFinal = v2TopLeft;
1925     v2MapScrSizeFinal = v2MapScrSize;
1926 
1927     DBG3(bmpMapBuffer,iMapOverlayDrawCount,DBGAV2(v2TopLeft));
1928 
1929     int iFinalMapScaling=0;
1930     if(bUsexBRZ || bImersiveMapMode){ //double stretch
1931       /**
1932        * these are "best fit" double stretch values
1933        *
1934        * max 'a' or 'b' is 6, min is 2
1935        *
1936        * 'b' may be ignored
1937        *
1938        * for best xBRZ results, 'a' should be as big as possible.
1939        *
1940        * TODO a smart formulae that allows above 32 too one day? :>
1941        */
1942       int a=-1,b=-1;
1943   //      if(iMapTileSize==1){
1944         if(iMapTileSizeMax>32)ABORT("not supported yet: iMapTileSizeMax=%d",iMapTileSizeMax);
1945         switch(iMapTileSizeMax/iMapTileSize){
1946         case 32:case 31:case 30:
1947           a=6;b=5;break;
1948         case 29:case 28:case 27:case 26:case 25:
1949           a=5;b=5;break;
1950         case 24:
1951           a=6;b=4;break;
1952         case 23:case 22:case 21:case 20:
1953           a=5;b=4;break;
1954         case 19:case 18:
1955           a=6;b=3;break;
1956         case 17:case 16:
1957           a=4;b=4;break;
1958         case 15:
1959           a=5;b=3;break;
1960         case 14:case 13:case 12:
1961           a=6;b=2;break;
1962         case 11:case 10:
1963           a=5;b=2;break;
1964         case 9:
1965           a=3;b=3;break;
1966         case 8:
1967           a=4;b=2;break;
1968         case 7:
1969           a=6;break;
1970         default: // <=6
1971           a=iMapTileSizeMax;
1972         }
1973 
1974       DBG4(a,b,iMapTileSize,iMapTileSizeMax);
1975       if(a<b)ABORT("a=%d should be bigger than b=%d for best initial xBRZ results",a,b);
1976 
1977       if(a>=2){
1978         v2 v2BmpSizeIn=v2BmpSize;
1979         static blitdata bldA=DEFAULT_BLITDATA;
1980         bmpFinal=finalMapBmp(bldA,a,bmpFinal,v2TopLeftFinal,v2MapScrSizeFinal,v2Center);
1981 
1982         if(b>=2){
1983           static blitdata bldB=DEFAULT_BLITDATA;
1984           bmpFinal=finalMapBmp(bldB,b,bmpFinal,v2TopLeftFinal,v2MapScrSizeFinal,v2Center);
1985         }
1986 
1987   //        graphics::DrawRectangleOutlineAround(
1988   //          buffer, bld.Dest, bld.Border*bld.Stretch, LIGHT_GRAY, true);
1989       }
1990 
1991       iFinalMapScaling = (a!=-1?a:1) * (b!=-1?b:1); DBG3(a,b,iFinalMapScaling);
1992 
1993     }
1994 
1995     if(bImersiveMapMode && !bPositionQuestionMode){ // at player hands!
1996       v2TopLeftFinal = area::getTopLeftCorner()
1997         + (CalculateScreenCoordinates(PLAYER->GetPos()) -area::getTopLeftCorner()) * ivanconfig::GetStartingDungeonGfxScale()
1998         + (TILE_V2*ivanconfig::GetStartingDungeonGfxScale())/2 //find center at player tile
1999         + v2(0,TILE_SIZE*ivanconfig::GetStartingDungeonGfxScale()*0.2) //player's hands a bit below center
2000         - v2(bmpFinal->GetSize().X/2,0) //center map top's on player's hands
2001         ;
2002     }
2003 
2004     if((v2TopLeftFinal.X+v2MapScrSizeFinal.X) > RES.X)v2TopLeftFinal.X=RES.X-v2MapScrSizeFinal.X;
2005     if((v2TopLeftFinal.Y+v2MapScrSizeFinal.Y) > RES.Y)v2TopLeftFinal.Y=RES.Y-v2MapScrSizeFinal.Y;
2006     if(v2TopLeftFinal.X<0)v2TopLeftFinal.X=0;
2007     if(v2TopLeftFinal.Y<0)v2TopLeftFinal.Y=0;
2008     DBGSV2(v2TopLeftFinal);
2009 
2010     // prepare notes
2011     int iMult=(iFinalMapScaling>0?iFinalMapScaling:1);
2012     for(int i=0;i<vMapNotes.size();i++){
2013       vMapNotes[i].scrPos = v2TopLeftFinal+
2014         (vMapNotes[i].tinyMapPos*iMult) //pos
2015 //        +v2(2,2);//
2016         +((v2(iMapTileSize,iMapTileSize)*iMult)/2);
2017       DBG7(i,vMapNotes.size(),DBGAV2(vMapNotes[i].scrPos),DBGAV2(v2TopLeftFinal),iFinalMapScaling,iMult,DBGAV2(vMapNotes[i].tinyMapPos));
2018     }
2019 //      v2MapNotesTopLeft = v2TopLeftFinal+v2(v2MapScrSizeFinal.X,0);
2020     v2MapTopLeft = v2TopLeftFinal;
2021     v2MapSize = v2MapScrSizeFinal;
2022   }
2023 
2024   static blitdata BFinal = DEFAULT_BLITDATA;
2025   BFinal.Bitmap = buffer;
2026   BFinal.Dest = v2TopLeftFinal;
2027   BFinal.Border = bmpFinal->GetSize();
2028   BFinal.MaskColor = BLACK;
2029   if(bTransparentMap){
2030     bmpFinal->NormalMaskedBlit(BFinal);
2031   }else
2032     bmpFinal->FastBlit(BFinal.Bitmap, BFinal.Dest );
2033 
2034   if(!bTransparentMap)
2035     graphics::DrawRectangleOutlineAround(buffer, v2TopLeftFinal, v2MapScrSizeFinal, LIGHT_GRAY, true);
2036 
2037   iMapOverlayDrawCount++;
2038 
2039 }
2040 /****************
2041  * Fancy map code have some interesting calculations,
2042  * kept as reference, may be useful to something later,
2043  * if it breaks just comment or remove it...
2044  */
DrawMapOverlayFancy(bitmap * buffer)2045 void DrawMapOverlayFancy(bitmap* buffer)
2046 {
2047   if(!bDrawMapOverlayEnabled)return;
2048 
2049   if(ivanconfig::GetStartingDungeonGfxScale()==1){
2050     ADD_MESSAGE(cHugeMap);
2051   }else{ //it actually work works (for now) if there is any dungeon stretching going on
2052     if(buffer!=NULL){
2053       static float fRGB=0.3;
2054       static int iR=0xFF*fRGB,iG=0xFF*fRGB,iB=0xFF*fRGB;
2055 
2056       static const int iLumTot=5;
2057       static col16 bkg[iLumTot];
2058       static col24 lum[iLumTot];static bool bDummyInit=[](){
2059         for(int i=0;i<iLumTot;i++){
2060           float f    = 1.00 + 0.02*i;
2061           float fBkg = 0.25 + 0.05*i; //col16 has less variations
2062           lum[i]=MakeRGB24(iR*f   , iG*f   , iB*f   );
2063           bkg[i]=MakeRGB16(iR*fBkg, iG*fBkg, iB*fBkg);
2064           DBG5(i,f,fBkg,lum[i],bkg[i]);
2065         };return true;}();
2066 
2067       static v2 v2MapMaxSize(RES/TILE_SIZE);
2068       static v2 v2CL;v2CL=v2(game::GetCurrentLevel()->GetXSize(),game::GetCurrentLevel()->GetYSize());
2069 
2070       static v2 v2MapVisibleSize;
2071       v2MapVisibleSize.X = Min(v2MapMaxSize.X,v2CL.X);
2072       v2MapVisibleSize.Y = Min(v2MapMaxSize.Y,v2CL.Y);
2073 
2074       static v2 v2MapSkipSize;
2075       v2MapSkipSize.X = v2CL.X - v2MapVisibleSize.X;
2076       v2MapSkipSize.Y = v2CL.Y - v2MapVisibleSize.Y;
2077 
2078       float fPercX = PLAYER->GetPos().X/(float)game::GetCurrentLevel()->GetXSize();
2079       float fPercY = PLAYER->GetPos().Y/(float)game::GetCurrentLevel()->GetYSize();
2080 
2081       v2MapSkipSize.X*=fPercX;
2082       v2MapSkipSize.Y*=fPercY;
2083 
2084       static blitdata B = [buffer](){B=DEFAULT_BLITDATA;
2085         B.Bitmap=buffer;
2086         B.Border=TILE_V2;return B;}();
2087 //        B.Luminance=NORMAL_LUMINANCE; return B;}(); //TODO yellowish like an old map? this no good: MakeRGB24(150,150,0);
2088 //        B.Luminance=MakeRGB24(iR,iG,iB); return B;}(); //TODO yellowish like an old map
2089 
2090       v2 v2TopLeft(
2091         RES.X/2 -(v2MapVisibleSize.X * TILE_SIZE)/2,
2092         RES.Y/2 -(v2MapVisibleSize.Y * TILE_SIZE)/2);
2093 
2094       /** arms not ready, still broken, TODO would be cool anyway?
2095         static humanoid* h;h = dynamic_cast<humanoid*>(PLAYER);
2096         static blitdata bldArm = [](){bldArm=DEFAULT_BLITDATA;
2097           bldArm.Bitmap=new bitmap(TILE_V2);
2098           bldArm.Border=TILE_V2; return bldArm;}();
2099         static blitdata bldArmBig = [buffer](){bldArmBig=DEFAULT_BLITDATA;
2100           bldArmBig.Bitmap=buffer;
2101           bldArmBig.Border=TILE_V2; return bldArmBig;}();
2102           bldArmBig.Stretch=6;
2103         if(h->GetLeftArm ())h->GetLeftArm ()->Draw(bldArm);
2104         graphics::Stretch(ivanconfig::IsXBRZScale(), bldArm.Bitmap, bldArmBig, false);
2105         //TODO fix right pos etc
2106         if(h->GetRightArm())h->GetRightArm()->Draw(bldArm);
2107         graphics::Stretch(ivanconfig::IsXBRZScale(), bldArm.Bitmap, bldArmBig, false);
2108       */
2109 
2110       /* the alpha just looks bad, mainly when above non visible areas, kept this comment as reminder...
2111         static blitdata bldTmp = [](){bldTmp=DEFAULT_BLITDATA;
2112           bldTmp.Bitmap=new bitmap(TILE_V2);
2113           bldTmp.Bitmap->CreateAlphaMap(0xFF*0.75);
2114           bldTmp.Border=TILE_V2; return bldTmp;}();
2115       */
2116 
2117       v2 PlayerScrPos(0,0);
2118       for(int iY=0;iY<v2MapVisibleSize.Y;iY++){
2119         B.Dest.Y = v2TopLeft.Y +iY*TILE_SIZE;
2120 
2121         for(int iX=0;iX<v2MapVisibleSize.X;iX++){
2122           B.Dest.X = v2TopLeft.X +iX*TILE_SIZE;
2123 
2124           static v2 v2SqrPos;v2SqrPos=v2(v2MapSkipSize.X+iX, v2MapSkipSize.Y+iY);
2125 
2126           static lsquare* lsqr;lsqr=game::GetCurrentLevel()->GetLSquare(v2SqrPos);
2127 
2128           static float fStepDelay=3.0;
2129           static int iAdd;iAdd = ((int)(clock()/(CLOCKS_PER_SEC*fStepDelay))) % ((iLumTot-1)*2); //moving waves
2130           static int iLumIndex;iLumIndex = abs( ((iX+iY+iAdd)%((iLumTot-1)*2)) - (iLumTot-1)); //like a wave from 0 to iLumTot to 0 to iLumTot
2131           B.Luminance=lum[iLumIndex]; DBG2(iLumIndex,B.Luminance);
2132 //          bldTmp.Luminance=lum[iLumIndex]; DBG2(iLumIndex,B.Luminance);
2133 
2134           if(lsqr->HasBeenSeen()){
2135             lsqr->GetGLTerrain()->Draw(B);
2136 //            lsqr->GetGLTerrain()->Draw(bldTmp);
2137 //            bldTmp.Bitmap->AlphaMaskedBlit(B);
2138 
2139             static olterrain* olt;olt = lsqr->GetOLTerrain();
2140             if(olt){
2141               olt->Draw(B);
2142 //              olt->Draw(bldTmp);
2143 //              bldTmp.Bitmap->AlphaMaskedBlit(B);
2144             }
2145           }else{
2146 //            static col16 bkg = MakeRGB16(iR,iG,iB);
2147             B.Bitmap->Fill(B.Dest, B.Border, bkg[iLumIndex]); DBG2(iLumIndex,bkg[iLumIndex]);
2148           }
2149 
2150           if(PLAYER->GetPos() == v2SqrPos){
2151             static float fP = 0.75;
2152             static col24 colP = MakeRGB24(0xff*fP,0xff*fP,0xff*fP);
2153             B.Luminance = clock()%2==0 ? colP : NORMAL_LUMINANCE; //to call attention like a highlight TODO not working...
2154             PLAYER->Draw(B);
2155 //            static int iHL=0;iHL++;
2156 //            B.Bitmap->DrawRectangle(B.Dest-v2(1,1), B.Border+v2(2,2), iHL%2==0 ? RED : YELLOW, false);
2157 //            graphics::DrawRectangleOutlineAround(
2158 //                B.Bitmap, B.Dest-v2(2,2), B.Border+v2(4,4), RED, true);//iHL%2==0);
2159 //            B.Bitmap, B.Dest-v2(2,2), B.Border+v2(4,4), RED, iHL%2==0);
2160             PlayerScrPos=B.Dest;
2161           }
2162         }
2163       }
2164 
2165       graphics::DrawRectangleOutlineAround(
2166         B.Bitmap, v2TopLeft, v2MapVisibleSize*TILE_SIZE, LIGHT_GRAY, true);
2167 
2168       graphics::DrawRectangleOutlineAround(
2169         B.Bitmap, PlayerScrPos, TILE_V2, RED, false);
2170     }
2171   }
2172 }
2173 
2174 /**
2175  * TODO optimize it: there are some calculations that could be made once per turn and not per frame...
2176  * TODO split this method in many, merely to easy it's understanding by sub-contexts..
2177  */
UpdateAltSilhouette(bool AnimationDraw)2178 void game::UpdateAltSilhouette(bool AnimationDraw){
2179   static const int iStep=2;
2180   static const int iYDiff=TILE_SIZE/3; //has more +- 33% height, after stretching by x3 will be like 3x4 squares of 16x16 dots each
2181   static const int iY4 = TILE_SIZE + iYDiff + 1; //+1 as the top line is to be kept empty
2182   static const v2 v2OverSilhouette = v2(TILE_SIZE, iY4);
2183   static int iAltSilBlitCount=0;
2184   static const int iTotTallStates=3;
2185   static const int iTallFrom=2;
2186   static const int iBreathFrom=3;
2187   static int iTallState=iTotTallStates-1;
2188   static int iPreviousAltSilOpt=0;
2189   static const v2 v2AltSilDispl = v2(8,-2);//v2(10,-2);//v2(24,24);
2190   static v2 v2AltSilPos=v2(0,0);
2191 //  static int iRandTorso=0;
2192 
2193   bool bOk=true;
2194 
2195   //if(bOk && !bAllowed)bOk=false;
2196 
2197   //if(bOk && !graphics::IsSRegionEnabled(iRegionSilhouette))bOk=false; //depends on it
2198 
2199   if(bOk && bPositionQuestionMode)bOk=false;
2200 
2201   if(bOk && ivanconfig::GetAltSilhouette()==0)bOk=false;
2202 
2203   if(bOk && !Player->IsEnabled())bOk=false;
2204 
2205   if(bOk && Player->IsDead())bOk=false; //TODO this works?
2206 
2207   humanoid* h=dynamic_cast<humanoid*>(Player);
2208   if(bOk && h==NULL)bOk=false; //TODO let it work with non humanoid forms
2209 //  if(bOk && Player->IsPolymorphed())bOk=false;
2210 
2211   if(humanoid::GetSilhouetteWhereDefault().Is0()){
2212     bOk=false;
2213   }else{
2214     if(iRegionVanillaSilhouette==-1)game::PrepareStretchRegionsLazy();
2215   }
2216 
2217   if(!bOk){
2218     iTallState=iTotTallStates-1;
2219     iAltSilBlitCount=0;
2220     humanoid::SetSilhouetteWhere(humanoid::GetSilhouetteWhereDefault());
2221     if(iRegionVanillaSilhouette!=-1)
2222       graphics::SetSRegionEnabled(iRegionVanillaSilhouette,false);
2223     return;
2224   }
2225 
2226   /////////////////////////// ok ////////////////////////////
2227 
2228   iPreviousAltSilOpt=ivanconfig::GetAltSilhouette();
2229 
2230 //  humanoid::SetSilhouetteWhere(ZoomPos+v2(10,10));
2231   bool bRolling=false;
2232   bool bHopping=false; DBG1(iRegionVanillaSilhouette);
2233   if(iRegionVanillaSilhouette!=-1 || graphics::GetScale()>1){
2234     bool bOk2=true;
2235 
2236     if(bOk2 && ZoomPos.Is0())bOk2=false;
2237 
2238     if(bOk2 && h==NULL)bOk2=false;
2239 
2240     if(bOk2 && ivanconfig::GetAltListItemPos()==1 && graphics::IsSRegionEnabled(iRegionListItem))bOk2=false; //is same of zoom pos
2241 
2242     if(bOk2){
2243       bRolling = !h->GetRightLeg() && !h->GetLeftLeg();
2244       bHopping = !bRolling && (!h->GetRightLeg() || !h->GetLeftLeg());
2245 
2246       v2 v2Pos=ZoomPos;
2247       if(graphics::GetScale()>1)
2248         v2Pos+=TILE_V2*3; //to avoid as much as possible be over the status texts
2249 
2250       humanoid::SetSilhouetteWhere(v2Pos);DBGSV2(v2Pos);
2251 
2252       if(iAltSilBlitCount==0) //first time
2253         if(h){
2254           h->DrawSilhouette(false);DBGLN;
2255         }
2256 
2257       if(graphics::GetScale()==1){ //TODO make these things optional? but there is no good place to draw it w/o hiding things behind it...
2258         bldVanillaSilhouetteTMP.Src = v2Pos + v2(0,-1);
2259 
2260         v2 v2Dest = v2Pos;
2261         v2 v2Min = RES - (bldVanillaSilhouetteTMP.Border*bldVanillaSilhouetteTMP.Stretch) - v2(5,5);
2262         if(v2Dest.X > v2Min.X)v2Dest.X=v2Min.X;
2263         if(v2Dest.Y > v2Min.Y)v2Dest.Y=v2Min.Y;
2264         bldVanillaSilhouetteTMP.Dest=v2Dest;
2265 
2266         graphics::SetSRegionBlitdata(iRegionVanillaSilhouette,bldVanillaSilhouetteTMP);
2267         //h->DrawSilhouette(AnimationDraw); //TODO necessary?
2268         graphics::SetSRegionEnabled(iRegionVanillaSilhouette,true);
2269       }
2270     }else{
2271       if(iRegionVanillaSilhouette!=-1)
2272         graphics::SetSRegionEnabled(iRegionVanillaSilhouette,false);
2273     }
2274   }
2275 
2276 //  if(v2AltSilPos.Is0())v2AltSilPos = bldSilhouetteTMP.Src + v2AltSilDispl;
2277 //  if(v2AltSilPos.Is0())v2AltSilPos = humanoid::GetSilhouetteWhere() + v2AltSilDispl;
2278   if(v2AltSilPos.Is0())v2AltSilPos = humanoid::GetSilhouetteWhereDefault() + v2AltSilDispl;
2279   static const v2 v2AltSilTopCenterPos = v2AltSilPos + v2(v2OverSilhouette.X/2,0);
2280   static v2 v2AltSilMovingPos=v2AltSilPos;
2281 
2282   static blitdata bldPlayerCopyTMP = [](){bldPlayerCopyTMP = DEFAULT_BLITDATA;
2283     bldPlayerCopyTMP.Bitmap = new bitmap(TILE_V2);
2284     bldPlayerCopyTMP.CustomData |= ALLOW_ANIMATE;
2285     bldPlayerCopyTMP.Luminance = NORMAL_LUMINANCE; return bldPlayerCopyTMP; }(); DBGBLD(bldPlayerCopyTMP);
2286   static int iDarkComp=50;
2287   static col16 darkestThatWontGlitchWithAlpha = MakeRGB16(iDarkComp,iDarkComp,iDarkComp); //still glitches a bit...
2288 //  static col24 darkestThatWontGlitchWithAlphaLum24 = MakeRGB24(iDarkComp,iDarkComp,iDarkComp);
2289   col16 bkgAlignmentColor = TRANSPARENT_COLOR;
2290   col24 bkgAlignmentLum24 = NORMAL_LUMINANCE;
2291   int iPlayerAlignment = GetPlayerAlignment();
2292   switch(ivanconfig::GetAltSilhouettePreventColorGlitch()){
2293   case 0:
2294     // keep default transparent
2295     break;
2296   case 1:
2297     bkgAlignmentColor = darkestThatWontGlitchWithAlpha;
2298     break;
2299   case 2:{
2300     /**
2301      * depicts alignment with background colors
2302      * tho, not more info than what is already written by the side of player's name
2303      * TODO animations? fire chaotic, clouds lawful
2304      */
2305     const static int iColorCompBase=0xff/2; // should start where NORMAL_LUMINANCE does, in the middle of the range
2306     const static int iColorCompMax=225;
2307     const static int iColorStepMult=3;
2308     const static int iMaxColorVariations = (iColorCompMax-iColorCompBase)/iColorStepMult;
2309     static col16 reds[iMaxColorVariations];
2310     static col24 reds24[iMaxColorVariations];
2311     static col16 blues[iMaxColorVariations];
2312     static col24 blues24[iMaxColorVariations];
2313 //    static col16 greys[iMaxColorVariations];
2314     static bool bDummy_Colors = [](){
2315       int iColorVarFinal;
2316       for(int i=0;i<iMaxColorVariations;i++){
2317         /**
2318          * the i*Multiplier: there are not so many colors on col16
2319          */
2320         iColorVarFinal = iColorCompBase + (i+1)*iColorStepMult; //+1 to avoid the extact middle
2321 
2322         reds[i]=MakeRGB16(iColorVarFinal,0,0); // chaotic variation
2323         reds24[i]=MakeRGB24(GetRed16(reds[i]),iColorCompBase,iColorCompBase);
2324 
2325         blues[i]=MakeRGB16(0,0,iColorVarFinal);
2326         blues24[i]=MakeRGB24(iColorCompBase,iColorCompBase,GetBlue16(blues[i]));
2327 
2328 //        iMult=1;iColorVarFinal = (i*iMult)-((totColorVariations*iMult)/2);
2329 //        greys[i]=MakeRGB16(iDarkComp+iColorVarFinal,iDarkComp+iColorVarFinal,iDarkComp+iColorVarFinal);
2330       };return true; }();
2331     const static int totColorVariations=iMaxColorVariations/4; //4 is max lawful or chaotic steps
2332     static int iColorVariationIndex=0;
2333     static int iColorVariationDir=1;
2334     static int iColorVariationDelay=CLOCKS_PER_SEC/totColorVariations;
2335     static int iColorVariationChangeAt=clock()+iColorVariationDelay;
2336     if(clock()>=iColorVariationChangeAt){
2337       iColorVariationIndex+=iColorVariationDir;
2338 
2339       if(iColorVariationIndex<0){
2340         iColorVariationIndex=0;
2341         iColorVariationDir=1;
2342       }
2343 
2344       if(iColorVariationIndex==totColorVariations){
2345         iColorVariationIndex=totColorVariations-1;
2346         iColorVariationDir=-1;
2347       }
2348     }
2349     int iBaseColorVariation=0;
2350     switch(abs(iPlayerAlignment)){
2351       case  4: iBaseColorVariation+=totColorVariations;
2352       case  3: iBaseColorVariation+=totColorVariations;
2353       case  2: iBaseColorVariation+=totColorVariations;
2354       case  1:
2355       case  0:
2356         iColorVariationIndex+=iBaseColorVariation;
2357         if(iColorVariationIndex>=iMaxColorVariations)iColorVariationIndex=iMaxColorVariations-1; //fail safe...
2358     }
2359     DBG5(iColorVariationIndex,iBaseColorVariation,totColorVariations,iMaxColorVariations,iColorVariationDir);
2360     switch(iPlayerAlignment){
2361       case  4:      case  3:      case  2:      case  1:
2362         bkgAlignmentColor=blues[iColorVariationIndex];
2363         bkgAlignmentLum24=blues24[iColorVariationIndex];
2364         break;
2365       case  0:
2366         bkgAlignmentColor=darkestThatWontGlitchWithAlpha; //greys[iColorVariationIndex];
2367         bkgAlignmentLum24=NORMAL_LUMINANCE;// to not darken it //darkestThatWontGlitchWithAlphaLum24;
2368         break;
2369       case -1:      case -2:      case -3:      case -4:
2370         bkgAlignmentColor=reds[iColorVariationIndex];
2371         bkgAlignmentLum24=reds24[iColorVariationIndex];
2372         break;
2373     }
2374 //    igraph::BlitBackGround(bldPlayerCopyTMP.Bitmap,v2(),TILE_V2); //not good...
2375 //    bldPlayerCopyTMP.Bitmap->Fill(0,0,TILE_V2,bkgAlignmentColor);
2376     }break;
2377   default:
2378     ABORT("invalid option GetAltSilhouettePreventColorGlitch %d",ivanconfig::GetAltSilhouettePreventColorGlitch());
2379     break;
2380   }
2381 
2382   bool bFluctuating = Player->IsSwimming() || Player->IsFlying();
2383 
2384   bool bSleeping = false;
2385   if(Player->GetAction()!=NULL)bSleeping=Player->GetAction()->IsUnconsciousness();
2386 
2387   bool bRotate=false;
2388   static blitdata bldRotated = [](){bldRotated = DEFAULT_BLITDATA;
2389     bldRotated.Border=TILE_V2;
2390     bldRotated.Bitmap = new bitmap(TILE_V2); return bldRotated; }(); DBGBLD(bldRotated);
2391 
2392   static int iLastSleepSide=0;
2393 //  if(bSleeping && !bFluctuating){
2394   if(bSleeping){ //TODO ? currently there is no space for breath animation if rotated +-90 degrees, some lines could be lost may be?
2395     if(iLastSleepSide==0)iLastSleepSide=clock()%2==0?1:-1;
2396     bitmap::ConfigureBlitdataRotation(bldRotated,1);
2397 //    bldPlayerCopyTMP.CustomData |= SQUARE_INDEX_MASK;
2398     bRotate=true;
2399   }else{
2400     iLastSleepSide=0;
2401 //    bldPlayerCopyTMP.CustomData &= ~SQUARE_INDEX_MASK;
2402   }
2403 
2404   /**
2405    * This is an attempt to workaround the alpha blit.
2406    *
2407    * The problem (glitch):
2408    *  To blit the player with transparent background we use TRANSPARENT_COLOR.
2409    *  But when the alpha blit from Player->Draw() writes to the bitmap, it will blend with the TRANSPARENT_COLOR.
2410    *
2411    * The workaround:
2412    *  Let it draw over TRANSPARENT_COLOR.
2413    *  Look for the first available (non used) color from darkest possible.
2414    *  Let it draw again over that not found color to prevent the glitch.
2415    *
2416    * TODO The correct solution (may be):
2417    *  Let Player->Draw(), when blitting alpha pixels, optionally use some requested color (like black) just to blend
2418    *  with thes alpha pixels, instead of the existing one (TRANSPARENT_COLOR that is pink).
2419    */
2420   static col16 aColorToClear[256];static bool bDummy_aColorToClear = [](){ //grey levels
2421     for(int i=0;i<256;i++)aColorToClear[i]=MakeRGB16(i,i,i); return true;}();
2422   bldPlayerCopyTMP.Bitmap->Fill(0,0,TILE_V2,TRANSPARENT_COLOR);
2423   Player->Draw(bldPlayerCopyTMP);
2424 
2425   col16 colorNotFound=TRANSPARENT_COLOR; //this may cause one frame glitch from time to time :/ by bleding other color that has alpha with that color
2426 
2427   if(bkgAlignmentColor != TRANSPARENT_COLOR){ // prefers alignment color first for best blending
2428     if(!bldPlayerCopyTMP.Bitmap->HasColor(bkgAlignmentColor))
2429       colorNotFound=bkgAlignmentColor;
2430   }
2431 
2432   if(colorNotFound == TRANSPARENT_COLOR){
2433     for(int i=0;i<256;i++){ //starting from black (0,0,0) gave better visual results than from white or gray.
2434       if(!bldPlayerCopyTMP.Bitmap->HasColor(aColorToClear[i])){
2435         colorNotFound=aColorToClear[i]; DBG2(colorNotFound,i);
2436         break;
2437       }
2438     }
2439   }
2440 
2441   if(colorNotFound != TRANSPARENT_COLOR){
2442     bldPlayerCopyTMP.Bitmap->Fill(0,0,TILE_V2,colorNotFound);
2443     Player->Draw(bldPlayerCopyTMP); // draws again for best blending
2444     bldPlayerCopyTMP.Bitmap->ReplaceColor(colorNotFound, TRANSPARENT_COLOR);
2445   }
2446   /*
2447   static col16 ColorBlendWithAlphaAndToClear = MakeRGB16(255,254,253); //TODO expectedly unused, but just a wild bad guess...
2448   bldPlayerCopyTMP.Bitmap->Fill(0,0,TILE_V2, ColorBlendWithAlphaAndToClear);
2449   Player->Draw(bldPlayerCopyTMP);
2450   bldPlayerCopyTMP.Bitmap->ReplaceColor(ColorBlendWithAlphaAndToClear,TRANSPARENT_COLOR); //TODO this may create holes in the image...
2451   */
2452   /*
2453   static bool bBkgPlayerForceTransparent = true;
2454   bldPlayerCopyTMP.Bitmap->Fill(0,0,TILE_V2, bBkgPlayerForceTransparent ? TRANSPARENT_COLOR : bkgAlignmentColor);
2455   Player->Draw(bldPlayerCopyTMP);
2456  * */
2457 
2458   bitmap* bmpPlayerSrc=bldPlayerCopyTMP.Bitmap; //DBG1(bmpPlayerSrc);
2459 
2460   bool bXbyYis3by4=ivanconfig::GetAltSilhouette()>=iTallFrom; // tall/breathing
2461   if(bXbyYis3by4){
2462     static blitdata bldPlayer3by4TMP = [](){
2463 //      blitdata B = DEFAULT_BLITDATA;
2464       bldPlayer3by4TMP=DEFAULT_BLITDATA;
2465       bldPlayer3by4TMP.Bitmap = new bitmap(v2OverSilhouette);
2466 //      return B;
2467       return bldPlayer3by4TMP;
2468     }();
2469 //    if(bldPlayer3by4TMP.Bitmap==NULL){
2470 //      bldPlayer3by4TMP.Bitmap = new bitmap(v2OverSilhouette);
2471 //    }
2472 
2473     bool bTired = Player->GetTirednessState()==EXHAUSTED || Player->GetTirednessState()==FAINTING;
2474 
2475     static int iFullBreathCount=0;
2476 
2477     static bool bKeepRolling=false;
2478     static int iNextRollAtFullBreath=0;
2479     if(bRolling && !bFluctuating && !bSleeping && iTallState==0){
2480       if(Player->GetBurdenState()!=OVER_LOADED){
2481         if(iFullBreathCount>=iNextRollAtFullBreath){
2482           int iRollFPS = 3 * (PlayerIsRunning()?2:1); // 4 steps is one full roll 360 degrees
2483           int iStepDelay = CLOCKS_PER_SEC/iRollFPS;
2484 
2485           static clock_t nextRollTime=0;
2486           static int iRollDirection=1;
2487           static int iRollingCount=0;
2488           if(clock() >= nextRollTime){
2489             iRollingCount++;
2490 
2491             bitmap::ConfigureBlitdataRotation(bldRotated,iRollingCount*iRollDirection);
2492             DBG5("RollRotate90deg", iRollingCount*iRollDirection, bldRotated.Flags&ROTATE, bldRotated.Flags&FLIP, bldRotated.Flags&MIRROR );
2493 
2494             if(iRollingCount >= 4){
2495               bKeepRolling=false;
2496               iRollDirection = clock()%2==0 ? 1 : -1; //prepare for next roll direction TODO use last if was left or right move (will ignore up/down tho)? would look good
2497               iRollingCount=0;
2498               iNextRollAtFullBreath = iFullBreathCount + 2 + clock()%4; DBG1(iNextRollAtFullBreath);
2499             }else{
2500               bKeepRolling=true;
2501             } DBG2(bKeepRolling,DBGB(bKeepRolling));
2502 
2503             nextRollTime = clock()+iStepDelay;
2504           }
2505 
2506           bRotate=true;
2507         }
2508       }else{
2509         bKeepRolling=false;
2510       }
2511     }
2512 
2513     if(bRotate){
2514       bmpPlayerSrc->NormalBlit(bldRotated); //DBG1(bmpPlayerSrc);DBGLN;
2515       bmpPlayerSrc = bldRotated.Bitmap; //DBG1(bmpPlayerSrc);DBGLN;
2516     }
2517 
2518     /********************************************
2519      * set base position
2520      */
2521     if(bFluctuating){
2522       float fStepsPerSecond=15; //fly turbulence move base speed
2523       int iMoveStep=1;
2524       if(PlayerIsRunning()){
2525         static int iRunMultSPS=2;
2526         fStepsPerSecond *= iRunMultSPS; //this will not work well if the machine is too slow
2527         if(globalwindowhandler::GetFPS(true) < fStepsPerSecond) iMoveStep=iRunMultSPS; //this is like a frame skip
2528       }
2529       if(bTired){
2530         fStepsPerSecond/=2;
2531         iMoveStep=1;
2532       }
2533       long lFlyStepDelay = CLOCKS_PER_SEC/fStepsPerSecond;
2534 
2535       static v2 v2PtoSAmoveTo;
2536       long lTimeNow=clock(); //this is animation based on real time.
2537       bool bDoStepNow=false;
2538       static long lPreviousFlyStepTime=0;
2539       if(lTimeNow-lPreviousFlyStepTime > lFlyStepDelay){
2540         bDoStepNow=true;
2541         lPreviousFlyStepTime=lTimeNow;
2542       }else{DBGSI(lTimeNow);}
2543 
2544       v2 v2Dist = v2PtoSAmoveTo - v2AltSilMovingPos; DBG2(DBGAV2(v2Dist),DBGB(v2Dist<v2(2,2)));
2545 //      if(!v2PtoSAmoveTo.Is0() && v2AltSilMovingPos != v2PtoSAmoveTo){ //slowly move to the new spot
2546       if( !v2PtoSAmoveTo.Is0() && (abs(v2Dist.X)>=iMoveStep || abs(v2Dist.Y)>=iMoveStep) ){ //slowly move to the new spot
2547         if(bDoStepNow){
2548           if(v2AltSilMovingPos.X < v2PtoSAmoveTo.X)v2AltSilMovingPos.X+=iMoveStep;
2549           else
2550           if(v2AltSilMovingPos.X > v2PtoSAmoveTo.X)v2AltSilMovingPos.X-=iMoveStep;
2551 
2552           if(v2AltSilMovingPos.Y < v2PtoSAmoveTo.Y)v2AltSilMovingPos.Y+=iMoveStep;
2553           else
2554           if(v2AltSilMovingPos.Y > v2PtoSAmoveTo.Y)v2AltSilMovingPos.Y-=iMoveStep;
2555         }
2556       }else{ //prepare new target fly spot destination
2557         // a bit more turbulence :)
2558         v2 v2Displ;
2559         int iMaxDisplacementFromCenterLess1=TILE_SIZE/4;//(PlayerIsRunning()?4:8);
2560         v2Displ.X  = clock()%iMaxDisplacementFromCenterLess1;
2561         v2Displ.Y  = clock()%iMaxDisplacementFromCenterLess1;
2562         v2Displ.X *= clock()%2==0 ? 1 : -1;
2563         v2Displ.Y *= clock()%2==0 ? 1 : -1;
2564 
2565         v2PtoSAmoveTo  = v2AltSilTopCenterPos - v2(v2OverSilhouette.X/2,0); //top left
2566         v2PtoSAmoveTo += v2Displ; //variation from top center
2567       }
2568 
2569     }
2570 
2571     if(!bKeepRolling && !bSleeping){
2572       int iYDest=0;
2573       int iBreathStepCount=0;
2574       if(ivanconfig::GetAltSilhouette()>=iBreathFrom){ //breath animation
2575         int nBreathDelay = 20 + 10*(ivanconfig::GetAltSilhouette()-iBreathFrom); //calm breathing
2576         if(PlayerIsRunning())nBreathDelay/=2;
2577         if(Player->GetTirednessState()==EXHAUSTED)nBreathDelay/=2; // OR faiting...
2578         if(Player->GetTirednessState()==FAINTING )nBreathDelay/=4;
2579         if(nBreathDelay<1)nBreathDelay=1;
2580 
2581         iBreathStepCount = iAltSilBlitCount/nBreathDelay;
2582         int iTotTallStatesCurrent=iTotTallStates;
2583         if(bFluctuating)iTotTallStatesCurrent=2;
2584         int iTallStateNew = iBreathStepCount % iTotTallStatesCurrent;
2585   //      if(bFluctuating)iTallStateNew=0;
2586         if(iTallStateNew!=iTallState){
2587           if(iTallStateNew==0)iFullBreathCount++;
2588         }
2589         iTallState=iTallStateNew;
2590       }DBG1(iTallState);
2591       if(TILE_SIZE==16){ // this is like a post processing gfx
2592         //never glue the head on top to prevent (more) stretching distortions, so we have at least one empty line on top
2593         bldPlayer3by4TMP.Bitmap->Fill(0, iYDest++, TILE_SIZE, 1, TRANSPARENT_COLOR);
2594 
2595         bool bLower = (iTallState==0 && bTired) || bFluctuating;
2596 
2597         int iTotBlankLines = iTotTallStates - (iTallState+1);
2598         // 3-(2+1)=0 //nothing
2599         // 3-(1+1)=1 //0
2600         // 3-(0+1)=2 //0 1
2601         if(bLower)iTotBlankLines++; //wont dup pants
2602 
2603         bool bJump=false;
2604         if(bFluctuating){
2605           iTotBlankLines+=1; // for the shorter legs
2606           iTotBlankLines+=2; // for the even smaller torso with -2 lines both at 0 and 1 tall states
2607   //        switch(iTallState){
2608   //        case 0: iTotBlankLines+=2; break; // for the even smaller torso
2609   //        case 1: iTotBlankLines+=1; break;
2610   //        }
2611 
2612   //        // random blank above head to make it oscillate while flying
2613   //        if(iFlyRandom%2==0){
2614   //          bldPlayer3by4TMP.Bitmap->Fill(0, iYDest++, TILE_SIZE, 1, TRANSPARENT_COLOR);
2615   //          iTotBlankLines--;
2616   //        }
2617         }else{
2618           if(iTallState==0 && bHopping && clock()%2==0)bJump=true;
2619 
2620           //blank space above head
2621           if(!bJump)
2622             for(int i=0;i<iTotBlankLines;i++)
2623               bldPlayer3by4TMP.Bitmap->Fill(0, iYDest++, TILE_SIZE, 1, TRANSPARENT_COLOR);
2624         }
2625 
2626         /*************************************
2627          * full body
2628          *************************************/
2629         int iHeadLines=6;
2630         for(int y=0;y<iHeadLines;y++){ //head
2631           bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,y,TILE_SIZE,true);
2632         }
2633 
2634         // torso are lines 6 7 8 9 (lets keep it simple to read...)
2635         switch(iTallState){
2636         case 0:
2637           if(bFluctuating){ // -2L (4 lines)
2638             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2639             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2640             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2641             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2642           }else{ //lowest (6 lines)  for non flying
2643             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2644             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2645             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2646             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2647             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2648             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2649           }
2650           break;
2651         case 1:
2652           if(bFluctuating){ // -2L (5 lines)
2653             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2654             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2655             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2656             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2657             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2658           }else{ // (7 lines)
2659             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2660             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2661             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2662             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2663             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2664             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2665             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2666           }
2667           break;
2668         case 2:
2669           if(bFluctuating){ // -2L (6 lines)
2670             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2671             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2672             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2673             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2674             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2675             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2676           }else{ //tallest (8 lines)
2677             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2678             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2679             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,6,TILE_SIZE,true);
2680             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2681             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,7,TILE_SIZE,true);
2682             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2683             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,8,TILE_SIZE,true);
2684             bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,9,TILE_SIZE,true);
2685           }
2686           break;
2687         default:
2688           ABORT("not supported tall state %d",iTallState); //all the above is for 3 tall states, changing it probably will require updating them all
2689         }
2690 
2691         bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,10,TILE_SIZE,true); //pants
2692         bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,11,TILE_SIZE,true); //weapon handle
2693         bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,12,TILE_SIZE,true); //pants
2694         if(!bLower)bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,12,TILE_SIZE,true); //pants dup
2695         bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,13,TILE_SIZE,true);
2696         if(!bFluctuating)bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,14,TILE_SIZE,true); //shorter legs if flying
2697         bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest++,bldPlayerCopyTMP.Bitmap,15,TILE_SIZE,true); //feet
2698 
2699         ////////////////////////// end of body //////////////////////////
2700 
2701         if(bFluctuating || bJump){
2702           // blank lines below feet
2703           for(int i=0;i<iTotBlankLines;i++)
2704             bldPlayer3by4TMP.Bitmap->Fill(0, iYDest++, TILE_SIZE, 1, TRANSPARENT_COLOR);
2705         }
2706 
2707         if(iYDest!=iY4)ABORT("bad calc iYDest=%d != iY4=%d, jump=%s, fly=%s, swim=%s, lower=%s, TotBlank=%d",iYDest,iY4,
2708           bJump?"T":"F", Player->IsFlying()?"T":"F", Player->IsSwimming()?"T":"F", bLower?"T":"F", iTotBlankLines); //Better never remove this, highly useful!
2709 
2710       }else{
2711         // fall back to simple blit for not supported tile sizes
2712         bool bBreakLoop=false;
2713         for(int y = 0; y < TILE_SIZE; ++y){
2714           if(bBreakLoop)break;
2715           bldPlayer3by4TMP.Bitmap->CopyLineFrom(iYDest,bldPlayerCopyTMP.Bitmap,y,TILE_SIZE,true);
2716           if(++iYDest>=iY4){bBreakLoop=true;continue;}
2717         }
2718       }
2719 
2720       bmpPlayerSrc=bldPlayer3by4TMP.Bitmap;
2721     }
2722 
2723   }
2724 
2725   static blitdata bldPlayerToSilhouetteAreaAtDB = [](){
2726     blitdata B = DEFAULT_BLITDATA;
2727     B.Stretch = 3;
2728     B.Bitmap = DOUBLE_BUFFER;
2729     return B;
2730   }();
2731 //  if(bldPlayerToSilhouetteAreaAtDB.Bitmap==NULL){
2732 //    bldPlayerToSilhouetteAreaAtDB.Stretch = 3;
2733 //    bldPlayerToSilhouetteAreaAtDB.Bitmap = DOUBLE_BUFFER;
2734 //  };
2735   bldPlayerToSilhouetteAreaAtDB.Dest = v2AltSilPos;
2736   if(bXbyYis3by4 && !bSleeping){
2737     bldPlayerToSilhouetteAreaAtDB.Dest = v2AltSilMovingPos; DBGLN;
2738   }else{
2739 //    if(bSleeping && !bFluctuating){
2740 //      bldPlayerToSilhouetteAreaAtDB.Dest.Y += iY4-TILE_SIZE;
2741 //    }else{
2742 //    bldPlayerToSilhouetteAreaAtDB.Dest.Y += TILE_SIZE/2 + (iY4-TILE_SIZE); //at bottom
2743     bldPlayerToSilhouetteAreaAtDB.Dest.Y += (iY4-TILE_SIZE)*bldPlayerToSilhouetteAreaAtDB.Stretch; //at bottom
2744     //    bldPlayerToSilhouetteAreaAtDB.Dest.Y += TILE_SIZE/2; //to center on it TODO wrong?
2745   }
2746   bldPlayerToSilhouetteAreaAtDB.Border = bmpPlayerSrc->GetSize(); DBGBLD(bldPlayerToSilhouetteAreaAtDB);
2747 
2748   /*************************************************
2749    *  blit the base background to DB
2750    */
2751   static const v2 v2StretchedPos = v2AltSilPos+v2(-2,0); DBG2(DBGAV2(v2AltSilPos),DBGAV2(v2StretchedPos));
2752 //  v2 v2StretchedBorder = (v2OverSilhouette+v2(4,2))*bldPlayerToSilhouetteAreaAtDB.Stretch;
2753   static const v2 v2StretchedBorder = (v2OverSilhouette*bldPlayerToSilhouetteAreaAtDB.Stretch)+v2(4,2);
2754 
2755   switch(ivanconfig::GetAltSilhouettePreventColorGlitch()){
2756   case 0:
2757     igraph::BlitBackGround(DOUBLE_BUFFER, v2StretchedPos, v2StretchedBorder);
2758     break;
2759   case 1:
2760     DOUBLE_BUFFER->Fill(v2StretchedPos, v2StretchedBorder, darkestThatWontGlitchWithAlpha);
2761     break;
2762   case 2:
2763     static blitdata bldLum = [](){bldLum=DEFAULT_BLITDATA;
2764       bldLum.Bitmap = DOUBLE_BUFFER;
2765       bldLum.Dest = bldLum.Src = v2StretchedPos;
2766       bldLum.Border = v2StretchedBorder; return bldLum;}();
2767 
2768     static long iNextAlignBkgMove=0;
2769     if(clock()>iNextAlignBkgMove){
2770       static v2 v2DisplTargetNext=v2(0,0); DBGSV2(v2DisplTargetNext);
2771       static v2 v2Displacement=v2(0,0); DBGSV2(v2Displacement);
2772       v2 v2Diff = v2DisplTargetNext-v2Displacement; DBGSV2(v2Diff);
2773       int iAbsPA=(abs(iPlayerAlignment)*2)+1; DBG1(iAbsPA);
2774       if(v2Diff.Is0()){
2775         v2DisplTargetNext=v2( clock()%iAbsPA,  clock()%iAbsPA);
2776       }else{
2777         if(v2Diff.X!=0)v2Displacement.X += v2Diff.X>0 ? 1 : -1;
2778         if(v2Diff.Y!=0)v2Displacement.Y += v2Diff.Y>0 ? 1 : -1;
2779       }
2780       bldLum.Src = v2StretchedPos+v2Displacement; DBGSV2(v2Displacement);
2781       long iDisplDelay = CLOCKS_PER_SEC/iAbsPA;
2782       iNextAlignBkgMove = clock()+iDisplDelay;
2783     }
2784 
2785     bldLum.Luminance = bkgAlignmentLum24;
2786     igraph::GetBackGround()->LuminanceBlit(bldLum); DBGBLD(bldLum);
2787     //DOUBLE_BUFFER->Fill(v2StretchedPos, v2StretchedBorder, bkgAlignmentColor);
2788     break;
2789   }
2790 
2791   bool bAllowOtherLayers=!IsInWilderness(); //TODO let it work in wilderness too)
2792   /*************************************************
2793    *  configure the final blit to DB
2794    */
2795   static blitdata bldToDB = [](){bldToDB=DEFAULT_BLITDATA;
2796     bldToDB.Border=v2StretchedBorder;
2797     bldToDB.Luminance=NORMAL_LUMINANCE;
2798     bldToDB.Bitmap=DOUBLE_BUFFER; return bldToDB;}(); DBGBLD(bldToDB);
2799 
2800   /*************************************************
2801    * collect the graphics from the square
2802    */
2803   static blitdata bldFromSqr = [](){bldFromSqr = DEFAULT_BLITDATA;
2804     bldFromSqr.Border=TILE_V2;
2805     bldFromSqr.Bitmap=new bitmap(bldFromSqr.Border);
2806     bldFromSqr.Luminance=NORMAL_LUMINANCE;
2807     bldFromSqr.CustomData |= ALLOW_ANIMATE; return bldFromSqr; }(); DBGBLD(bldFromSqr);
2808   if(bAllowOtherLayers){
2809   //  if(Player->IsSwimming()){
2810   //    // collect the liquid ground gfx
2811     Player->GetLSquareUnder()->GetGLTerrain()->Draw(bldFromSqr); //only the terrain w/o other stuff dropped on it
2812   //  }else{
2813   //    // collect the whole ground gfx
2814   //    Player->GetLSquareUnder()->DrawStaticContents(bldFromSqr);
2815   //  }
2816   }
2817 
2818   /*************************************************
2819    * prepare the multiplied ground copy for maximum details before stretching
2820    */
2821   static v2 v2CopyWH = [](){
2822     v2CopyWH = v2(3,4); //the tall silhouette
2823     v2CopyWH += v2(1,1); //+1,1 as there is tiny bits around the player allowing fly/swim shaking animations
2824     v2CopyWH += v2(0,1); //+1 if the ground liquid oscilates too much
2825     return v2CopyWH;
2826   }();
2827   static blitdata bldCopy = [](){bldCopy = DEFAULT_BLITDATA;
2828     bldCopy.Border = v2CopyWH*TILE_SIZE;
2829     bldCopy.Bitmap=new bitmap(bldCopy.Border);
2830     bldCopy.Bitmap->CreateAlphaMap(0xFF*0.50); return bldCopy;}(); DBGBLD(bldCopy);
2831   if(bAllowOtherLayers){
2832   //  static blitdata bldStretch = [](){bldStretch = DEFAULT_BLITDATA;
2833   //    bldStretch.Stretch=bldPlayerToSilhouetteAreaAtDB.Stretch+1; //+1 as there is tiny bits around the player allowing fly/swim animations
2834   //    bldStretch.Border = TILE_V2;
2835   //    bldStretch.Bitmap=new bitmap(bldStretch.Border * bldStretch.Stretch);
2836   //    bldStretch.Bitmap->CreateAlphaMap(0xFF*0.50); return bldStretch;}(); DBGBLD(bldStretch);
2837     // stretch that gfx enough to prepare the final blit
2838   //  graphics::Stretch(ivanconfig::IsXBRZScale(),bldFromSqr.Bitmap,bldStretch,true);
2839     for(int iY=0;iY<v2CopyWH.Y;iY++){
2840       for(int iX=0;iX<v2CopyWH.X;iX++){
2841         bldCopy.Dest=v2(iX,iY)*TILE_SIZE;
2842         bldFromSqr.Bitmap->NormalBlit(bldCopy); //to have maximum details before stretching later
2843       }
2844     }
2845   }
2846 
2847   if(bAllowOtherLayers){
2848     /*************************************************
2849      * the moving shrinked ground below player
2850      */
2851     if(!Player->IsSwimming()){ //TODO wilderness: earth below and treetop on top sides, in case walking in trees square
2852       int iWalkFPS = 2 * (PlayerIsRunning()?3:1);
2853       int iStepDelay = CLOCKS_PER_SEC/iWalkFPS;
2854 
2855       static int iWalkStepPrevious=-1;
2856       int iWalkStep = clock()/iStepDelay;
2857       if(iWalkStepPrevious!=iWalkStep){
2858         int iHeight = v2StretchedBorder.Y * (bRolling||bSleeping ? 0.66 : 0.33);
2859         bldToDB.Dest = v2StretchedPos+(v2(0,v2StretchedBorder.Y-iHeight));
2860         bldToDB.Border.Y=iHeight;
2861 
2862         //walking ground effect
2863         static int iGroundSrcY=0;
2864         bool bIsMovingOnFloor = !bSleeping && Player->GetBurdenState()!=OVER_LOADED;
2865         if(bIsMovingOnFloor)
2866           iGroundSrcY++; //moving
2867         if((iGroundSrcY+iHeight)>v2StretchedBorder.Y)
2868           iGroundSrcY=0;
2869         bldToDB.Src = v2(0,iGroundSrcY);
2870 
2871         iWalkStepPrevious=iWalkStep;
2872       }
2873 
2874       // copy from stretched as much as needed, to DB
2875   //    bldStretch.Bitmap->NormalBlit(bldToDB);
2876       bldCopy.Bitmap->NormalBlit(bldToDB);
2877     }
2878   }
2879 
2880   /*************************************************
2881    * this stretch draws the modified player at the tiny silhouette region at DB
2882    **/
2883   graphics::Stretch(ivanconfig::IsXBRZScale(),bmpPlayerSrc,bldPlayerToSilhouetteAreaAtDB,true); DBG1(bmpPlayerSrc);
2884 
2885   if(bAllowOtherLayers){
2886     /*************************************************
2887      * draw transparent ground liquid above player
2888      */
2889     if(Player->IsSwimming()){
2890       static bool bSimple=false;
2891 
2892       static const int iMaxWaveLength=6;
2893       static int iWaveLength=iMaxWaveLength;
2894       static const int iHeight = v2StretchedBorder.Y*0.75;
2895       static const int iLiquidFPS=5;
2896       static const int iStepDelay = CLOCKS_PER_SEC/iLiquidFPS;
2897 
2898       static int iWaveStepPrevious=-1;
2899       int iWaveStep = clock()/iStepDelay;
2900       static int iDegrees;
2901       static int iHeightFinal;
2902       static int iDestDisplY;
2903       static v2 v2Dest;
2904       static int iDegreesStep=30;
2905       if(iWaveStepPrevious!=iWaveStep){
2906         iDegrees+=iDegreesStep;
2907         if(iDegrees>=360){
2908           iDegrees=0;
2909           iDegreesStep=30+clock()%60;
2910           iWaveLength=iMaxWaveLength/(1 + clock()%3);
2911         }
2912         iHeightFinal = iHeight + iWaveLength*sin(iDegrees*3.14159/180);
2913         iDestDisplY = v2StretchedBorder.Y - iHeightFinal;
2914         v2Dest = v2StretchedPos+v2(0,iDestDisplY);
2915         iWaveStepPrevious=iWaveStep;
2916       }
2917 
2918       if(bSimple){
2919         DOUBLE_BUFFER->Fill(v2Dest, v2(v2StretchedBorder.X,iHeightFinal), BLUE);
2920       }else{
2921         // copy from stretched as much as needed, to DB
2922         bldToDB.Dest = v2Dest;
2923         bldToDB.Border.Y=iHeightFinal;
2924         /* TODO oscilating the alpha is being cumulative? leading to full transparency, and this alpha veriation is not working as intended:
2925         float fAlpha = 0.85-(0.03*iWaveStep);
2926         bldStretch.Bitmap->FillAlpha(0xFF*fAlpha);
2927          */
2928         bldCopy.Bitmap->AlphaMaskedBlit(bldToDB); //->AlphaLuminanceBlit(bldToDB);
2929       }
2930     }
2931   }
2932 
2933   if(ivanconfig::GetAltSilhouettePreventColorGlitch()>0)
2934     graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, v2StretchedPos, v2StretchedBorder, DARK_GRAY, false);
2935 
2936   iAltSilBlitCount++;
2937 }
2938 
DrawEverythingNoBlit(truth AnimationDraw)2939 void game::DrawEverythingNoBlit(truth AnimationDraw)
2940 {
2941   bool bXBRZandFelist = ivanconfig::IsXBRZScale() && felist::isAnyFelistCurrentlyDrawn();
2942 
2943   if(LOSUpdateRequested && Player->IsEnabled())
2944   {
2945     if(!IsInWilderness())
2946       GetCurrentLevel()->UpdateLOS();
2947     else
2948       GetWorldMap()->UpdateLOS();
2949   }
2950 
2951   if(OnScreen(CursorPos))
2952   {
2953     if(!IsInWilderness() || CurrentWSquareMap[CursorPos.X][CursorPos.Y]->GetLastSeen() || GetSeeWholeMapCheatMode())
2954       CurrentArea->GetSquare(CursorPos)->SendStrongNewDrawRequest();
2955     else
2956       DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, 0);
2957   }
2958 
2959   if(!bXBRZandFelist)
2960     for(size_t c = 0; c < SpecialCursorPos.size(); ++c)
2961       if(OnScreen(SpecialCursorPos[c]))
2962         CurrentArea->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
2963 
2964   globalwindowhandler::UpdateTick();
2965   if(!bXBRZandFelist){
2966     if(!IsInWilderness())
2967       GetCurrentLevel()->RevealDistantLightsToPlayer();
2968     GetCurrentArea()->Draw(AnimationDraw);
2969   }
2970   Player->DrawPanel(AnimationDraw);
2971 
2972   if(!AnimationDraw)
2973     msgsystem::Draw();
2974 
2975   if(!bXBRZandFelist && OnScreen(CursorPos))
2976   {
2977     v2 ScreenCoord = CalculateScreenCoordinates(CursorPos);
2978     blitdata B = { DOUBLE_BUFFER,
2979                    { 0, 0 },
2980                    { ScreenCoord.X, ScreenCoord.Y },
2981                    { TILE_SIZE, TILE_SIZE },
2982                    { 0 },
2983                    TRANSPARENT_COLOR,
2984                    ALLOW_ANIMATE|ALLOW_ALPHA };
2985 
2986     if(!IsInWilderness() && !GetSeeWholeMapCheatMode())
2987     {
2988       lsquare* Square = CurrentLSquareMap[CursorPos.X][CursorPos.Y];
2989 
2990       if(Square->GetLastSeen() != GetLOSTick())
2991         Square->DrawMemorized(B);
2992     }
2993 
2994     if(DoZoom())
2995     { //TODO could this zoom feature simply be another stretchregion?
2996       B.Src = B.Dest;
2997       B.Dest = ZoomPos;
2998       B.Stretch = iZoomFactor;
2999       graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, ZoomPos, TILE_V2*iZoomFactor, DARK_GRAY, true);
3000       graphics::Stretch(ivanconfig::IsXBRZScale(),DOUBLE_BUFFER,B,false);
3001     }
3002 
3003     igraph::DrawCursor(ScreenCoord, CursorData);
3004   }
3005 
3006   if(!bXBRZandFelist && Player->IsEnabled())
3007   {
3008     if(Player->IsSmall())
3009     {
3010       v2 Pos = Player->GetPos();
3011 
3012       if(OnScreen(Pos))
3013       {
3014         v2 ScreenCoord = CalculateScreenCoordinates(Pos);
3015         igraph::DrawCursor(ScreenCoord, Player->GetCursorData());
3016         if(!DoZoom())UpdatePosAroundForXBRZ(Pos);
3017       }
3018     }
3019     else
3020     {
3021       for(int c = 0; c < Player->GetSquaresUnder(); ++c)
3022       {
3023         v2 Pos = Player->GetPos(c);
3024 
3025         if(OnScreen(Pos))
3026         {
3027           v2 ScreenCoord = CalculateScreenCoordinates(Pos);
3028           igraph::DrawCursor(ScreenCoord, Player->GetCursorData()|CURSOR_BIG, c);
3029           if(!DoZoom())UpdatePosAroundForXBRZ(Pos);
3030         }
3031       }
3032     }
3033   }
3034 
3035   if(!bXBRZandFelist){
3036     for(size_t c = 0; c < SpecialCursorPos.size(); ++c){
3037       if(OnScreen(SpecialCursorPos[c]))
3038       {
3039         v2 ScreenCoord = CalculateScreenCoordinates(SpecialCursorPos[c]);
3040         igraph::DrawCursor(ScreenCoord, SpecialCursorData[c]);
3041         GetCurrentArea()->GetSquare(SpecialCursorPos[c])->SendStrongNewDrawRequest();
3042       }
3043     }
3044   }
3045 
3046   UpdateAltSilhouette(AnimationDraw);
3047 
3048   UpdateShowItemsAtPos(!bXBRZandFelist); //last thing as this is a temp overlay
3049 
3050   #ifdef WIZARD
3051     DBG1(vDbgDrawOverlayFunctions.size());
3052     if(vDbgDrawOverlayFunctions.size()>0){DBGLN; // ULTRA last thing
3053       for(int i=0;i<vDbgDrawOverlayFunctions.size();i++)
3054         vDbgDrawOverlayFunctions[i](); //call it
3055     }
3056   #endif
3057 
3058 }
3059 
ItemUnderCode(int iCycleValue)3060 int game::ItemUnderCode(int iCycleValue){
3061   switch(iCycleValue){
3062     case 0:return 0; //disabled
3063 
3064     case 1:return 1; //above head
3065 
3066     // corners and Horizontal/Vertical
3067     case 2:return  10;
3068     case 3:return  11;
3069 
3070     case 4:return 110;
3071     case 5:return 111;
3072 
3073     case 6:return 210;
3074     case 7:return 211;
3075 
3076     case 8:return 310;
3077     case 9:return 311;
3078 
3079     default:ABORT("invalid ItemUnder cycle value %d",iCycleValue);
3080   }
3081 
3082   return -1; //dummy (never happens, just to cpp do not bother..)
3083 }
ItemUnderCorner(int val)3084 int game::ItemUnderCorner(int val){
3085   return val/100;
3086 }
ItemUnderZoom(int val)3087 int game::ItemUnderZoom(int val){
3088   return (val%100)/10;
3089 }
ItemUnderHV(int val)3090 bool game::ItemUnderHV(int val){
3091   return val%2==0; //odd is vertical
3092 }
3093 
3094 bitmap* bmpTgt = NULL;
PrepareItemsUnder(bool bUseDB,stack * su,int iMax,v2 v2PosIni,int iDirX,int iDirY)3095 bitmap* PrepareItemsUnder(bool bUseDB, stack* su, int iMax, v2 v2PosIni, int iDirX, int iDirY){
3096   int iTot = su->GetItems();
3097   if(iMax>-1)iTot = Min(iMax,iTot);
3098   if(iTot==0)return NULL;
3099 
3100   v2 v2Pos = v2PosIni; DBGSV2(v2PosIni);
3101 
3102   blitdata B = DEFAULT_BLITDATA;
3103   B.CustomData = ALLOW_ANIMATE;
3104   B.Stretch = 1; //ignored? anyway this will work only from/to 16x16...
3105   B.Border = { TILE_SIZE, TILE_SIZE };
3106   B.Luminance = ivanconfig::GetContrastLuminance();
3107 
3108   if(bUseDB){
3109     bmpTgt = DOUBLE_BUFFER;
3110   }else{
3111     v2 v2Size = v2(TILE_SIZE*iTot,TILE_SIZE);
3112     if(bmpTgt==NULL || bmpTgt->GetSize()!=v2Size){
3113       // DO NOT DELETE the old bitmap here. See graphics::SetSRegionSrcBitmapOverride(); //NO NO NO: if(bmpTgt!=NULL)delete bmpTgt;
3114       bmpTgt = new bitmap(v2Size);
3115     }
3116   }
3117 
3118   B.Bitmap=bmpTgt;
3119   B.CustomData |= ALLOW_ANIMATE|ALLOW_ALPHA;
3120 
3121   static bool bLight=false; //TODO make this an user option?
3122   col16 clOutline = bLight ? LIGHT_GRAY : BLACK;//DARK_GRAY;
3123   if(!bLight){ //overall around if tiny
3124     v2 v2BkgIni = v2(0,0);
3125     v2 v2Border = bmpTgt->GetSize();
3126     if(bUseDB){
3127       v2BkgIni = v2PosIni+v2(1,1);
3128       if(iDirX<0)v2BkgIni.X-=((iTot-1)*TILE_SIZE);
3129       if(iDirY<0)v2BkgIni.Y-=((iTot-1)*TILE_SIZE);
3130 
3131       v2Border = B.Border;
3132       if(iDirX!=0)v2Border.X*=iTot;
3133       if(iDirY!=0)v2Border.Y*=iTot;
3134       v2Border-=v2(2,2);
3135     }
3136     igraph::BlitBackGround(bmpTgt, v2BkgIni, v2Border);
3137     graphics::DrawRectangleOutlineAround(bmpTgt, v2BkgIni, v2Border, clOutline, false);
3138   }
3139 //  itemvector vit;su->FillItemVector(vit);
3140   static itemvector vit;vit.clear();su->FillItemVector(vit);
3141   for(int i=0;i<iTot;i++){ // fully work on one square per time
3142     item* it = vit[i];
3143     if(!it->CanBeSeenByPlayer())continue;
3144 
3145     if(bLight){ // each square
3146       bmpTgt->Fill(v2Pos, B.Border, DARK_GRAY);
3147       graphics::DrawRectangleOutlineAround(bmpTgt, v2Pos+v2(1,1), B.Border-v2(2,2), clOutline, false);
3148     }
3149 
3150     B.Dest = v2Pos; DBGBLD(B);
3151     it->Draw(B);
3152 
3153     v2Pos.X+=(TILE_SIZE*iDirX);
3154     v2Pos.Y+=(TILE_SIZE*iDirY);
3155   }
3156 
3157   return bmpTgt;
3158 }
3159 
3160 int iStretchedTileSize = -1;
getDungeonStretchedTileSize()3161 int getDungeonStretchedTileSize(){
3162   if(iStretchedTileSize==-1)iStretchedTileSize = TILE_SIZE * ivanconfig::GetStartingDungeonGfxScale();
3163   return iStretchedTileSize;
3164 }
3165 
3166 /**
3167  * For Stretched buffer:
3168  * The final screen coordinates are relative not to 0,0 but to the top left dungeon corner,
3169  * because the full dungeon stretching happens from that spot,
3170  * therefore when working on the stretched buffer, this does not work, ex.:
3171  *  CalculateScreenCoordinates(Player->GetPos())
3172  */
CalculateStretchedBufferCoordinatesFromDungeonSquarePos(v2 v2SqrPos)3173 v2 game::CalculateStretchedBufferCoordinatesFromDungeonSquarePos(v2 v2SqrPos){
3174   v2 v2SqrRelativePosFromCam = v2SqrPos - GetCamera();
3175   v2 v2StretchedBufferDest=area::getTopLeftCorner();
3176   v2StretchedBufferDest+=(v2SqrRelativePosFromCam*getDungeonStretchedTileSize());
3177   return v2StretchedBufferDest;
3178 }
3179 
UpdateShowItemsAtPos(bool bAllowed,v2 v2AtPos)3180 void game::UpdateShowItemsAtPos(bool bAllowed,v2 v2AtPos){
3181   if(v2AtPos.Is0() && bPositionQuestionMode) v2AtPos = CursorPos;
3182 
3183   bool bOk=true;
3184 
3185   if(bOk && !bAllowed)bOk=false;
3186 
3187 //  if(bOk && !bAllowPosMode && bPositionQuestionMode)bOk=false;
3188 
3189   if(bOk && !Player->IsEnabled())bOk=false;
3190 
3191   if(bOk && Player->IsDead())bOk=false;
3192 
3193   if(bOk && v2AtPos.Is0()){ //after validating player
3194     v2AtPos = Player->GetPos();
3195   }
3196 
3197   if(bOk && !OnScreen(v2AtPos))bOk=false;
3198 
3199   if(bOk && IsInWilderness())bOk=false;
3200 
3201   if(bOk){
3202     if(v2AtPos!=Player->GetPos())
3203       if(!GetCurrentLevel()->GetLSquare(v2AtPos)->CanBeSeenByPlayer())
3204         bOk=false;
3205   }
3206 
3207   bool bDynamic=false;
3208   bool bDynamicItems=false;
3209   if(ivanconfig::GetShowItemsAtPlayerSquare()>=10){
3210     bDynamic=true;
3211     if(ivanconfig::GetShowItemsAtPlayerSquare()==11)bDynamicItems=true;
3212   }
3213   int iCode = 1;
3214   if(!bDynamic)iCode=ItemUnderCode(ivanconfig::GetShowItemsAtPlayerSquare());
3215   bool bEnabled = iCode>0;
3216   bool bAboveHead = iCode==1;
3217 
3218   if(bOk && !bEnabled)bOk=false;
3219 
3220   stack* su = NULL;
3221   if(bOk){
3222     //    su=Player->GetStackUnder(); //try{su=Player->GetStackUnder();}catch(std::exception& e){bOk=false;} TODO is this catch too generic/permissive?
3223     su=GetCurrentLevel()->GetLSquare(v2AtPos)->GetStack();
3224     if(bOk && su==NULL)bOk=false; //TODO can this happen?
3225     if(bOk && su->GetItems()==0)bOk=false;
3226     if(bOk){
3227 //      itemvector vit;su->FillItemVector(vit);
3228       static itemvector vit;vit.clear();su->FillItemVector(vit);
3229       for(int i=0;i<vit.size();i++){
3230         if(vit[i]->CanBeSeenByPlayer())break;
3231         if(i==(vit.size()-1))bOk=false; // nothing there can be seen
3232       }
3233     }
3234   }
3235 
3236   if(!bOk){ // reaching here is IMPORTANT as a disabler to the region!
3237     graphics::SetSRegionEnabled(iRegionItemsUnder,false);
3238     return;
3239   }
3240 
3241   /////////////////////// ok ////////////////////////
3242   const v2 v2AbsLevelSqrPos = v2AtPos;
3243 
3244   bool bNearEC=false;
3245   int iNearEC=3; //near edges/corners to avoid hiding player/NPCs that can be in combat TODO use player view distance?
3246   int iCycleCodeFallBack=4; //top right horiz if not near corners/edges
3247   if(v2AbsLevelSqrPos.X<=iNearEC){ //left edge
3248     bNearEC=true;iCycleCodeFallBack=8; //bottom right vert
3249   }else if(v2AbsLevelSqrPos.Y<=iNearEC){ //top edge
3250     bNearEC=true;iCycleCodeFallBack=6; //bottom left horiz
3251   }else if(v2AbsLevelSqrPos.X >= (GetCurrentArea()->GetXSize() - iNearEC)){ //right edge
3252     bNearEC=true;iCycleCodeFallBack=7; //bottom left vert
3253   }else if(v2AbsLevelSqrPos.Y >= (GetCurrentArea()->GetYSize() - iNearEC)){ //bottom edge
3254     bNearEC=true;iCycleCodeFallBack=4; //top right horiz
3255   }
3256   if(bNearEC && iCode==1)
3257     bAboveHead=true;
3258 
3259   if(bDynamic && bAboveHead && !bPositionQuestionMode){ // will not be above head in bPositionQuestionMode
3260     v2 v2Chk; //(v2AbsLevelSqrPos.X,v2AbsLevelSqrPos.Y-1);
3261     bool bCharAboveNear=false;
3262     bool bItemAboveNear=false;
3263     for(int i=-1;i<=1;i++){
3264       v2Chk = v2AbsLevelSqrPos+v2(0+i,-1);
3265       if(GetCurrentLevel()->IsValidPos(v2Chk)){
3266         if(GetCurrentLevel()->GetSquare(v2Chk)->GetCharacter()){
3267           bCharAboveNear=true;
3268           break;
3269         }
3270 
3271         if(bDynamicItems){
3272           if(GetCurrentLevel()->GetLSquare(v2Chk)->GetStack()->GetItems()>0){
3273             bItemAboveNear=true;
3274             break;
3275           }
3276         }
3277       }
3278     }
3279 
3280     if(bCharAboveNear || bItemAboveNear){
3281       iCode = ItemUnderCode(iCycleCodeFallBack);
3282       bAboveHead=false;
3283     }
3284   }
3285 
3286   int iTot = su->GetItems();
3287   if(iTot>game::GetScreenXSize())
3288     iTot=game::GetScreenXSize();
3289   if(iTot>GetCurrentArea()->GetXSize())
3290     iTot=GetCurrentArea()->GetXSize();
3291 
3292   //////////////////////////////////////////////////////////////////////////////////////
3293   // above head with x1 dungeon scale will fall back to "Dungeon square overwrite mode"
3294   if(bAboveHead && ivanconfig::GetStartingDungeonGfxScale()>=2){ //use xBRZ stretch region
3295     // TODO ? Some possible tips if look mode is used later: GetCurrentArea()->, Player->GetArea()->get, game::GetCurrentDungeon()->
3296     bitmap* bmp = PrepareItemsUnder(false, su, iTot, v2(0,0), 1, 0);
3297 
3298     int iStretch=iItemsUnderStretch;
3299     if(su->GetItems()==1)iStretch++;
3300 
3301     v2 v2StretchedBufferDest = CalculateStretchedBufferCoordinatesFromDungeonSquarePos(v2AbsLevelSqrPos);
3302     v2StretchedBufferDest.X+=getDungeonStretchedTileSize()/2; //center of player's head
3303     v2StretchedBufferDest.X-=(bmp->GetSize().X*iStretch)/2;
3304     v2StretchedBufferDest.Y-= bmp->GetSize().Y*iStretch; // above player's head
3305     v2StretchedBufferDest.Y-=2; //just to look better
3306 
3307     if(v2StretchedBufferDest.X<area::getTopLeftCorner().X)
3308       v2StretchedBufferDest.X=area::getTopLeftCorner().X;
3309 
3310     if(v2StretchedBufferDest.Y<area::getTopLeftCorner().Y)
3311       v2StretchedBufferDest.Y=area::getTopLeftCorner().Y;
3312 
3313     graphics::SetSRegionSrcBitmapOverride(iRegionItemsUnder,bmp,iStretch,v2StretchedBufferDest);
3314     graphics::SetSRegionEnabled(iRegionItemsUnder,true);
3315     return;
3316   }
3317 
3318   ////////////////////////////////////////////////////////////////////////////////////////
3319   ////////////////////////////// Dungeon square overwrite mode ///////////////////////////
3320   ////////////////////////////// CORNERS WORK DIRECTLY ON DoubleBuffer ///////////////////
3321   ////////////////////////////////////////////////////////////////////////////////////////
3322 
3323   graphics::SetSRegionEnabled(iRegionItemsUnder,false); //disable above head region
3324 
3325   //this overwrites over dungeon squares pixels and is faster as it will go within the full dungeon stretch!
3326   int iDirX=1,iDirY=0;
3327 //  v2 v2ScrPosIni(0,0);
3328   v2 v2SqrPosIni(0,0);
3329 
3330   if(bAboveHead){ //only for x1 dungeon scale
3331     v2SqrPosIni=v2AtPos;
3332 
3333     v2SqrPosIni.X-=iTot/2;
3334     v2SqrPosIni.Y--;
3335 
3336     // the dungeon area may be smaller than the dungeon MAX area (boundings outline)
3337     if(v2SqrPosIni.X<0)v2SqrPosIni.X=0;
3338     if(v2SqrPosIni.Y<0)v2SqrPosIni.Y=0;
3339 
3340 //    v2ScrPosIni=CalculateScreenCoordinates(v2SqrPosIni);
3341 //  }else{
3342 //    v2ScrPosIni = area::getTopLeftCorner();DBGSV2(v2ScrPosIni);DBGSV2(CalculateScreenCoordinates(Camera));
3343   }
3344 
3345 //  v2ScrPosIni=CalculateScreenCoordinates(v2SqrPosIni); DBG2(DBGAV2(v2SqrPosIni),DBGAV2(v2ScrPosIni));
3346 //
3347 //  // the dungeon area may be smaller than the dungeon MAX area (boundings outline)
3348 //  v2 v2Sqr00ScrPos=CalculateScreenCoordinates(GetCurrentLevel()->GetLSquare(v2(0,0))->GetPos());DBGSV2(v2Sqr00ScrPos);
3349 //  if(v2ScrPosIni.X<v2Sqr00ScrPos.X)v2ScrPosIni.X=v2Sqr00ScrPos.X; //TODO can this conflict with or miss v2SqrPosIni position ?
3350 //  if(v2ScrPosIni.Y<v2Sqr00ScrPos.Y)v2ScrPosIni.Y=v2Sqr00ScrPos.Y;
3351 //  DBGSV2(v2SqrPosIni);
3352 
3353   if(!bAboveHead){
3354     int iCorner = ItemUnderCorner(iCode);
3355     bool bHorizontal = ItemUnderHV(iCode);
3356 
3357     // min top left dungeon sqr coords
3358     v2SqrPosIni=Camera;DBGSV2(v2SqrPosIni);
3359     if(v2SqrPosIni.X<0)v2SqrPosIni.X=0;
3360     if(v2SqrPosIni.Y<0)v2SqrPosIni.Y=0;
3361 
3362     // max bottom right dungeon sqr coords
3363     v2 v2SqrSize(
3364       Min(game::GetScreenXSize(), GetCurrentLevel()->GetXSize()),
3365       Min(game::GetScreenYSize(), GetCurrentLevel()->GetYSize()) );
3366     int iSqrMaxX=v2SqrSize.X-1;
3367     int iSqrMaxY=v2SqrSize.Y-1;
3368 
3369     switch(iCorner){
3370       case 0: iDirX=bHorizontal? 1:0; iDirY=bHorizontal?0: 1;
3371         break;
3372       case 1: iDirX=bHorizontal?-1:0; iDirY=bHorizontal?0: 1;
3373         v2SqrPosIni.X+=iSqrMaxX;
3374 //        v2ScrPosIni.X+=iSqrMaxX*TILE_SIZE;
3375         break;
3376       case 2: iDirX=bHorizontal? 1:0; iDirY=bHorizontal?0:-1;
3377         v2SqrPosIni.Y+=iSqrMaxY;
3378 //        v2ScrPosIni.Y+=iSqrMaxY*TILE_SIZE;
3379         break;
3380       case 3: iDirX=bHorizontal?-1:0; iDirY=bHorizontal?0:-1;
3381         v2SqrPosIni.X+=iSqrMaxX;
3382 //        v2ScrPosIni.X+=iSqrMaxX*TILE_SIZE;
3383         v2SqrPosIni.Y+=iSqrMaxY;
3384 //        v2ScrPosIni.Y+=iSqrMaxY*TILE_SIZE;
3385         break;
3386     }
3387 
3388     if(!bHorizontal){
3389       if(iTot>game::GetScreenYSize())
3390         iTot=game::GetScreenYSize();
3391 
3392       if(iTot>GetCurrentArea()->GetYSize())
3393         iTot=GetCurrentArea()->GetYSize();
3394     }
3395 
3396 //
3397 //      }break;
3398 //
3399 //      case 2:
3400 //      case 3:
3401 //      case 4:
3402 //      case 5:
3403 //      case 6:
3404 //        //TODO xBRZ ?
3405 //        //TODO graphics::DrawRectangleOutlineAround(DOUBLE_BUFFER, area::getTopLeftCorner(), {TILE_SIZE*iTot,TILE_SIZE}, LIGHT_GRAY, false);
3406 //        break;
3407 
3408   }
3409 
3410   v2 v2ScrPosIni=CalculateScreenCoordinates(v2SqrPosIni); DBG2(DBGAV2(v2SqrPosIni),DBGAV2(v2ScrPosIni));
3411 
3412   PrepareItemsUnder(true,su,iTot,v2ScrPosIni,iDirX,iDirY);
3413 
3414   /**
3415    * this grants updating the squares used to show the items.
3416    * this also provides a cleanup after player moves or gets things from the floor.
3417    */
3418   for(int i=0;i<iTot;i++){DBGSV2(v2SqrPosIni);
3419     if(GetCurrentArea()->IsValidPos(v2SqrPosIni)){
3420       GetCurrentArea()->GetSquare(v2SqrPosIni)->SendStrongNewDrawRequest();
3421     }
3422     v2SqrPosIni.X+=iDirX;
3423     v2SqrPosIni.Y+=iDirY;
3424   }
3425 }
3426 
Save(cfestring & SaveName)3427 truth game::Save(cfestring& SaveName)
3428 {
3429   outputfile SaveFile(SaveName + ".sav");
3430   SaveFile << SAVE_FILE_VERSION;
3431   SaveFile << GameScript << CurrentDungeonIndex << CurrentLevelIndex << Camera;
3432   SaveFile << WizardMode << SeeWholeMapCheatMode << GoThroughWallsCheat;
3433   SaveFile << Tick << Turn << InWilderness << NextCharacterID << NextItemID << NextTrapID << NecroCounter;
3434   SaveFile << SumoWrestling << PlayerSumoChampion << TouristHasSpider << GlobalRainTimeModifier;
3435   long Seed = RAND();
3436   femath::SetSeed(Seed);
3437   SaveFile << Seed;
3438   SaveFile << AveragePlayerArmStrengthExperience;
3439   SaveFile << AveragePlayerLegStrengthExperience;
3440   SaveFile << AveragePlayerDexterityExperience;
3441   SaveFile << AveragePlayerAgilityExperience;
3442   SaveFile << Teams << Dungeons << StoryState << GloomyCaveStoryState;
3443   SaveFile << XinrochTombStoryState << FreedomStoryState << AslonaStoryState << RebelStoryState;
3444   SaveFile << PlayerIsChampion << HasBoat << PlayerRunning;
3445   SaveFile << PlayerMassacreMap << PetMassacreMap << MiscMassacreMap;
3446   SaveFile << PlayerMassacreAmount << PetMassacreAmount << MiscMassacreAmount;
3447   SaveArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
3448   int c;
3449 
3450   for(c = 0; c < ATTRIBUTES; ++c)
3451     SaveFile << OldAttribute[c] << NewAttribute[c] << LastAttributeChangeTick[c];
3452 
3453   for(c = 1; c < Dungeons; ++c)
3454     SaveFile << Dungeon[c];
3455 
3456   for(c = 1; c <= GODS; ++c)
3457     SaveFile << God[c];
3458 
3459   for(c = 0; c < Teams; ++c)
3460     SaveFile << Team[c];
3461 
3462   if(InWilderness)
3463     SaveWorldMap(SaveName, false);
3464   else
3465     GetCurrentDungeon()->SaveLevel(SaveName, CurrentLevelIndex, false);
3466 
3467   SaveFile << Player->GetPos() << PlayerName;
3468   msgsystem::Save(SaveFile);
3469   SaveFile << DangerMap << NextDangerIDType << NextDangerIDConfigIndex;
3470   SaveFile << DefaultPolymorphTo << DefaultSummonMonster;
3471   SaveFile << DefaultWish << DefaultChangeMaterial << DefaultDetectMaterial;
3472   SaveFile << GetTimeSpent();
3473   /* or in more readable format: time() - LastLoad + TimeAtLastLoad */
3474 
3475   SaveFile << PlayerHasReceivedAllGodsKnownBonus;
3476   SaveFile << WorldShape;
3477   protosystem::SaveCharacterDataBaseFlags(SaveFile);
3478 
3479   commandsystem::SaveSwapWeapons(SaveFile); DBGLN;
3480   craftcore::Save(SaveFile);
3481 
3482   return true;
3483 }
3484 
Load(cfestring & saveName)3485 int game::Load(cfestring& saveName)
3486 {DBGLN;
3487   inputfile SaveFile(saveName + ".sav", 0, false);
3488 
3489   if(!SaveFile.IsOpen())
3490     return NEW_GAME;
3491 
3492   SaveFile >> CurrentSavefileVersion;
3493 
3494   if(ivanconfig::IsAllowImportOldSavegame()){
3495     if(CurrentSavefileVersion > SAVE_FILE_VERSION){
3496       iosystem::Menu(std::vector<bitmap*>(), v2(RES.X >> 1, RES.Y >> 1),
3497         CONST_S("Sorry, this save can't be imported by this game version.\r"),
3498         CONST_S("Hit a key to go back...\r"), LIGHT_GRAY);
3499       return BACK;
3500     }
3501   }else{
3502     if(CurrentSavefileVersion != SAVE_FILE_VERSION)
3503     {
3504       if(!iosystem::Menu(std::vector<bitmap*>(), v2(RES.X >> 1, RES.Y >> 1),
3505                          CONST_S("Sorry, this save is incompatible with the new version.\rStart new game?\r"),
3506                          CONST_S("Yes\rNo\r"), LIGHT_GRAY))
3507         return NEW_GAME;
3508       else
3509         return BACK;
3510     }
3511   }
3512 
3513   SaveFile >> GameScript >> CurrentDungeonIndex >> CurrentLevelIndex >> Camera;
3514   SaveFile >> WizardMode >> SeeWholeMapCheatMode >> GoThroughWallsCheat;
3515   SaveFile >> Tick >> Turn >> InWilderness >> NextCharacterID >> NextItemID >> NextTrapID >> NecroCounter;
3516   SaveFile >> SumoWrestling >> PlayerSumoChampion >> TouristHasSpider >> GlobalRainTimeModifier;
3517   femath::SetSeed(ReadType<long>(SaveFile));
3518   SaveFile >> AveragePlayerArmStrengthExperience;
3519   SaveFile >> AveragePlayerLegStrengthExperience;
3520   SaveFile >> AveragePlayerDexterityExperience;
3521   SaveFile >> AveragePlayerAgilityExperience;
3522   SaveFile >> Teams >> Dungeons >> StoryState >> GloomyCaveStoryState;
3523   SaveFile >> XinrochTombStoryState >> FreedomStoryState >> AslonaStoryState >> RebelStoryState;
3524   SaveFile >> PlayerIsChampion >> HasBoat >> PlayerRunning;
3525   SaveFile >> PlayerMassacreMap >> PetMassacreMap >> MiscMassacreMap;
3526   SaveFile >> PlayerMassacreAmount >> PetMassacreAmount >> MiscMassacreAmount;
3527   LoadArray(SaveFile, EquipmentMemory, MAX_EQUIPMENT_SLOTS);
3528   int c;
3529 
3530   for(c = 0; c < ATTRIBUTES; ++c)
3531     SaveFile >> OldAttribute[c] >> NewAttribute[c] >> LastAttributeChangeTick[c];
3532 
3533   Dungeon = new dungeon*[Dungeons];
3534   Dungeon[0] = 0;
3535 
3536   for(c = 1; c < Dungeons; ++c)
3537     SaveFile >> Dungeon[c];
3538 
3539   God = new god*[GODS + 1];
3540   God[0] = 0;
3541 
3542   for(c = 1; c <= GODS; ++c)
3543     SaveFile >> God[c];
3544 
3545   Team = new team*[Teams];
3546 
3547   for(c = 0; c < Teams; ++c)
3548     SaveFile >> Team[c];
3549 
3550   if(InWilderness)
3551   {
3552     SetCurrentArea(LoadWorldMap(saveName));
3553     CurrentWSquareMap = WorldMap->GetMap();
3554     igraph::CreateBackGround(GRAY_FRACTAL);
3555   }
3556   else
3557   {
3558     SetCurrentArea(CurrentLevel = GetCurrentDungeon()->LoadLevel(saveName, CurrentLevelIndex));
3559     CurrentLSquareMap = CurrentLevel->GetMap();
3560     igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
3561   }
3562 
3563   v2 Pos;
3564   SaveFile >> Pos >> PlayerName;
3565   SetPlayer(bugfixdp::ValidatePlayerAt(GetCurrentArea()->GetSquare(Pos)));
3566   msgsystem::Load(SaveFile);
3567   SaveFile >> DangerMap >> NextDangerIDType >> NextDangerIDConfigIndex;
3568   SaveFile >> DefaultPolymorphTo >> DefaultSummonMonster;
3569   SaveFile >> DefaultWish >> DefaultChangeMaterial >> DefaultDetectMaterial;
3570   SaveFile >> TimePlayedBeforeLastLoad;
3571   SaveFile >> PlayerHasReceivedAllGodsKnownBonus;
3572   SaveFile >> WorldShape;
3573   LastLoad = time(0);
3574   protosystem::LoadCharacterDataBaseFlags(SaveFile);
3575 
3576   commandsystem::LoadSwapWeapons(SaveFile);
3577   craftcore::Load(SaveFile);
3578 
3579   ///////////////// loading ended ////////////////
3580 
3581   UpdateCamera();
3582 
3583   return LOADED;
3584 }
3585 
3586 /**
3587  * this prevents all possibly troublesome characters in all OSs
3588  */
fixChars(festring & fs)3589 void fixChars(festring& fs)
3590 {
3591   for(festring::sizetype i = 0; i < fs.GetSize(); ++i)
3592   {
3593     if(fs[i]>='A' && fs[i]<='Z')continue;
3594     if(fs[i]>='a' && fs[i]<='z')continue;
3595     if(fs[i]>='0' && fs[i]<='9')continue;
3596 
3597     fs[i] = '_';
3598   }
3599 }
3600 
chkAutoSaveSuffix(festring & fs,bool bAlsoFixIt=false)3601 bool chkAutoSaveSuffix(festring& fs,bool bAlsoFixIt=false){DBG1(fs.CStr());
3602   std::string strChk;
3603   strChk = fs.CStr();
3604   int i = strChk.find(AUTOSAVE_SUFFIX);
3605   if(i!=std::string::npos){
3606     if(bAlsoFixIt){
3607       fs.Empty();
3608       fs<<strChk.substr(0,i).c_str();DBG1(fs.CStr());
3609     }
3610     return true;
3611   }
3612 
3613   return false;
3614 }
3615 
SaveName(cfestring & Base,bool bLoadingFromAnAutosave)3616 festring game::SaveName(cfestring& Base,bool bLoadingFromAnAutosave)
3617 {
3618   festring PathAndBaseSaveName = GetSaveDir();
3619 
3620   /**
3621    * Base must come OK, will just prepend directory,
3622    * the problem on modifying it is that as it is read from the filesystem
3623    * it will not be found if it gets changed...
3624    */
3625   DBG3(PlayerName.CStr(), Base.CStr(), CurrentBaseSaveFileName.CStr());
3626 
3627   if(Base.GetSize() > 0)
3628   {
3629     CurrentBaseSaveFileName.Empty();
3630     CurrentBaseSaveFileName << Base;
3631     chkAutoSaveSuffix(CurrentBaseSaveFileName,true);
3632 
3633     PathAndBaseSaveName << Base;
3634   }
3635   else
3636   {
3637     // this is important in case player name changes like when using the fantasy name generator
3638     festring fsPN; fsPN<<PlayerName; fixChars(fsPN);
3639     std::string strASFN; strASFN = CurrentBaseSaveFileName.CStr();
3640     if(strASFN.substr(0,fsPN.GetSize()) != fsPN.CStr())
3641       CurrentBaseSaveFileName.Empty();
3642 
3643     if(CurrentBaseSaveFileName.GetSize() == 0)
3644     {
3645       cint iTmSz=100; char cTime[iTmSz]; time_t now = time(0);
3646       strftime(cTime,iTmSz,"%Y%m%d_%H%M%S",localtime(&now)); //pretty DtTm
3647 
3648       CurrentBaseSaveFileName << PlayerName << '_' << cTime;
3649       fixChars(CurrentBaseSaveFileName);
3650     }
3651 
3652     PathAndBaseSaveName << CurrentBaseSaveFileName;
3653   }
3654 
3655   DBG4(PlayerName.CStr(), PathAndBaseSaveName.CStr(), Base.CStr(), CurrentBaseSaveFileName.CStr());
3656 
3657 #if defined(__DJGPP__)
3658   if(PathAndBaseSaveName.GetSize() > 13)
3659     PathAndBaseSaveName.Resize(13);
3660 #endif
3661 
3662   if(!bLoadingFromAnAutosave){ //very specific use case
3663     if(chkAutoSaveSuffix(PathAndBaseSaveName)){
3664       /**
3665        * this ABORT is important to prevent the troubling autosave suffix duplicity,
3666        * it's consistency is kept using only: GetAutoSaveFileName()
3667        */
3668       ABORT("The base savegame filename '%s' must not contain '%s'",PathAndBaseSaveName.CStr(),AUTOSAVE_SUFFIX);
3669     }
3670   }
3671 
3672   return PathAndBaseSaveName;
3673 }
3674 
GetMoveCommandKeyBetweenPoints(v2 A,v2 B)3675 int game::GetMoveCommandKeyBetweenPoints(v2 A, v2 B)
3676 {
3677   for(int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c)
3678     if((A + GetMoveVector(c)) == B)
3679       return GetMoveCommandKey(c);
3680 
3681   return DIR_ERROR;
3682 }
3683 
ApplyDivineTick()3684 void game::ApplyDivineTick()
3685 {
3686   for(int c = 1; c <= GODS; ++c)
3687     GetGod(c)->ApplyDivineTick();
3688 }
3689 
ApplyDivineAlignmentBonuses(god * CompareTarget,int Multiplier,truth Good)3690 void game::ApplyDivineAlignmentBonuses(god* CompareTarget, int Multiplier, truth Good)
3691 {
3692   for(int c = 1; c <= GODS; ++c)
3693     if(GetGod(c) != CompareTarget)
3694       GetGod(c)->AdjustRelation(CompareTarget, Multiplier, Good);
3695 }
3696 
GetDirectionVectorForKey(int Key)3697 v2 game::GetDirectionVectorForKey(int Key)
3698 {
3699   if(Key == KEY_NUMPAD_5)
3700     return ZERO_V2;
3701 
3702   for(int c = 0; c < EXTENDED_DIRECTION_COMMAND_KEYS; ++c)
3703     if(Key == GetMoveCommandKey(c))
3704       return GetMoveVector(c);
3705 
3706   return ERROR_V2;
3707 }
3708 
GetMinDifficulty()3709 double game::GetMinDifficulty()
3710 {
3711   double Base = CurrentLevel->GetDifficulty() * 0.2;
3712   long MultiplierExponent = 0;
3713   ivantime Time;
3714   GetTime(Time);
3715   int Modifier = Time.Day - DANGER_PLUS_DAY_MIN;
3716 
3717   if(Modifier > 0)
3718     Base += DANGER_PLUS_MULTIPLIER * Modifier;
3719 
3720   for(;;)
3721   {
3722     int Dice = RAND() % 25;
3723 
3724     if(Dice < 5 && MultiplierExponent > -3)
3725     {
3726       Base /= 3;
3727       --MultiplierExponent;
3728       continue;
3729     }
3730 
3731     if(Dice >= 20 && MultiplierExponent < 3)
3732     {
3733       Base *= 3;
3734       ++MultiplierExponent;
3735       continue;
3736     }
3737 
3738     return Base;
3739   }
3740 }
3741 
ShowLevelMessage()3742 void game::ShowLevelMessage()
3743 {
3744   if(CurrentLevel->GetLevelMessage().GetSize())
3745     ADD_MESSAGE(CurrentLevel->GetLevelMessage().CStr());
3746 
3747   CurrentLevel->SetLevelMessage("");
3748 }
3749 
DirectionQuestion(cfestring & Topic,truth RequireAnswer,truth AcceptYourself,int keyChoseDefaultDir,int defaultDir)3750 int game::DirectionQuestion(cfestring& Topic, truth RequireAnswer, truth AcceptYourself, int keyChoseDefaultDir, int defaultDir)
3751 {
3752   for(;;)
3753   {
3754     int Key = AskForKeyPress(Topic); DBG3(Key,keyChoseDefaultDir,defaultDir);
3755 
3756     if(AcceptYourself && Key == '.')
3757       return YOURSELF;
3758 
3759     for(int c = 0; c < DIRECTION_COMMAND_KEYS; ++c)
3760       if(Key == GetMoveCommandKey(c))
3761         return c;
3762 
3763     if(Key==keyChoseDefaultDir)
3764       return defaultDir;
3765 
3766     if(!RequireAnswer)
3767       return DIR_ERROR;
3768   }
3769 }
3770 
RemoveSaves(truth RealSavesAlso,truth onlyBackups)3771 void game::RemoveSaves(truth RealSavesAlso,truth onlyBackups)
3772 { DBG2(RealSavesAlso,onlyBackups);
3773   cchar* bkp = ".bkp";
3774 
3775   if(RealSavesAlso)
3776   {
3777     remove(festring(SaveName() + ".sav" + (onlyBackups?bkp:"")).CStr());
3778     remove(festring(SaveName() + ".wm"  + (onlyBackups?bkp:"")).CStr());
3779   }
3780 
3781   remove(festring(GetAutoSaveFileName() + ".sav" + (onlyBackups?bkp:"") ).CStr());
3782   remove(festring(GetAutoSaveFileName() + ".wm"  + (onlyBackups?bkp:"") ).CStr());
3783   festring File;
3784 
3785   for(int i = 1; i < Dungeons; ++i)
3786     for(int c = 0; c < GetDungeon(i)->GetLevels(); ++c)
3787     { DBG2(i,c);
3788       /* This looks very odd. And it is very odd.
3789        * Indeed, gcc is very odd to not compile this correctly with -O3
3790        * if it is written in a less odd way. */
3791 
3792       File = SaveName() + '.' + i;
3793       File << c; DBG1(File.CStr());
3794 
3795       if(RealSavesAlso)
3796         remove(festring(File + (onlyBackups?bkp:"")).CStr());
3797 
3798       File = GetAutoSaveFileName() + '.' + i;
3799       File << c; DBG1(File.CStr());
3800 
3801       remove(festring(File + (onlyBackups?bkp:"")).CStr()); DBGLN;
3802     }
3803 
3804   DBGLN; //DBGSTK;
3805 }
3806 
SetPlayer(character * NP)3807 void game::SetPlayer(character* NP)
3808 {
3809   Player = NP;
3810 
3811   if(Player)
3812     Player->AddFlags(C_PLAYER);
3813 }
3814 
InitDungeons()3815 void game::InitDungeons()
3816 {
3817   Dungeons = *GetGameScript()->GetDungeons() + 1;
3818   Dungeon = new dungeon *[Dungeons];
3819   Dungeon[0] = 0;
3820 
3821   for(int c = 1; c < Dungeons; ++c)
3822   {
3823     Dungeon[c] = new dungeon(c);
3824     Dungeon[c]->SetIndex(c);
3825   }
3826 }
3827 
DoEvilDeed(int Amount)3828 void game::DoEvilDeed(int Amount)
3829 {
3830   if(!Amount)
3831     return;
3832 
3833   for(int c = 1; c <= GODS; ++c)
3834   {
3835     int Change = Amount - Amount * GetGod(c)->GetAlignment() / 5;
3836 
3837     if(!IsInWilderness() && Player->GetLSquareUnder()->GetDivineMaster() == c)
3838       if(GetGod(c)->GetRelation() - (Change << 1) < -750)
3839       {
3840         if(GetGod(c)->GetRelation() > -750)
3841           GetGod(c)->SetRelation(-750);
3842       }
3843       else if(GetGod(c)->GetRelation() - (Change << 1) > 750)
3844       {
3845         if(GetGod(c)->GetRelation() < 750)
3846           GetGod(c)->SetRelation(750);
3847       }
3848       else
3849         GetGod(c)->SetRelation(GetGod(c)->GetRelation() - (Change << 1));
3850     else
3851       if(GetGod(c)->GetRelation() - Change < -500)
3852       {
3853         if(GetGod(c)->GetRelation() > -500)
3854           GetGod(c)->SetRelation(-500);
3855       }
3856       else if(GetGod(c)->GetRelation() - Change > 500)
3857       {
3858         if(GetGod(c)->GetRelation() < 500)
3859           GetGod(c)->SetRelation(500);
3860       }
3861       else
3862         GetGod(c)->SetRelation(GetGod(c)->GetRelation() - Change);
3863   }
3864 }
3865 
SaveWorldMap(cfestring & SaveName,truth DeleteAfterwards)3866 void game::SaveWorldMap(cfestring& SaveName, truth DeleteAfterwards)
3867 {
3868   outputfile SaveFile(SaveName + ".wm");
3869   SaveFile << WorldMap;
3870 
3871   if(DeleteAfterwards)
3872   {
3873     delete WorldMap;
3874     WorldMap = 0;
3875   }
3876 }
3877 
LoadWorldMap(cfestring & SaveName)3878 worldmap* game::LoadWorldMap(cfestring& SaveName)
3879 {
3880   inputfile SaveFile(SaveName + ".wm");
3881   SaveFile >> WorldMap;
3882   return WorldMap;
3883 }
3884 
Hostility(team * Attacker,team * Defender)3885 void game::Hostility(team* Attacker, team* Defender)
3886 {
3887   for(int c = 0; c < Teams; ++c)
3888     if(GetTeam(c) != Attacker && GetTeam(c) != Defender
3889        && GetTeam(c)->GetRelation(Defender) == FRIEND
3890        && c != NEW_ATTNAM_TEAM
3891        && c != TOURIST_GUIDE_TEAM) // gum solution
3892       GetTeam(c)->SetRelation(Attacker, HOSTILE);
3893 }
3894 
CreateTeams()3895 void game::CreateTeams()
3896 {
3897   Teams = *GetGameScript()->GetTeams();
3898   Team = new team*[Teams];
3899   int c;
3900 
3901   for(c = 0; c < Teams; ++c)
3902   {
3903     Team[c] = new team(c);
3904 
3905     for(int i = 0; i < c; ++i)
3906       Team[i]->SetRelation(Team[c], UNCARING);
3907   }
3908 
3909   for(c = 0; c < Teams; ++c)
3910     if(c != 1)
3911       Team[1]->SetRelation(Team[c], HOSTILE);
3912 
3913   const std::list<std::pair<int, teamscript>>& TeamScript = GetGameScript()->GetTeam();
3914 
3915   for(const std::pair<int, teamscript>& p : TeamScript)
3916   {
3917     for(uint c = 0; c < p.second.GetRelation().size(); ++c)
3918       GetTeam(p.second.GetRelation()[c].first)->SetRelation(GetTeam(p.first), p.second.GetRelation()[c].second);
3919 
3920     cint* KillEvilness = p.second.GetKillEvilness();
3921 
3922     if(KillEvilness)
3923       GetTeam(p.first)->SetKillEvilness(*KillEvilness);
3924   }
3925 }
3926 
3927 /* v2 Pos should be removed from xxxQuestion()s? */
3928 
3929 /* If AllowExit is true the user can abort with the esc-key. The function returns ABORTED
3930    (when user aborts with esc) or NORMAL_EXIT. */
3931 
StringQuestion(festring & Answer,cfestring & Topic,col16 Color,festring::sizetype MinLetters,festring::sizetype MaxLetters,truth AllowExit,stringkeyhandler KeyHandler)3932 int game::StringQuestion(festring& Answer, cfestring& Topic, col16 Color,
3933                          festring::sizetype MinLetters, festring::sizetype MaxLetters,
3934                          truth AllowExit, stringkeyhandler KeyHandler)
3935 {
3936   DrawEverythingNoBlit();
3937   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23)); // pos may be incorrect!
3938   SRegionAroundDeny();
3939   int Return = iosystem::StringQuestion(Answer, Topic, v2(16, 6), Color, MinLetters, MaxLetters, false, AllowExit, KeyHandler);
3940   SRegionAroundAllow();
3941   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
3942   return Return;
3943 }
3944 
NumberQuestion(cfestring & Topic,col16 Color,truth ReturnZeroOnEsc)3945 long game::NumberQuestion(cfestring& Topic, col16 Color, truth ReturnZeroOnEsc)
3946 {
3947   DrawEverythingNoBlit();
3948   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
3949   SRegionAroundDeny();
3950   long Return = iosystem::NumberQuestion(Topic, v2(16, 6), Color, false, ReturnZeroOnEsc);
3951   SRegionAroundAllow();
3952   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
3953   return Return;
3954 }
3955 
ScrollBarQuestion(cfestring & Topic,long BeginValue,long Step,long Min,long Max,long AbortValue,col16 TopicColor,col16 Color1,col16 Color2,void (* Handler)(long))3956 long game::ScrollBarQuestion(cfestring& Topic, long BeginValue, long Step, long Min, long Max, long AbortValue,
3957                              col16 TopicColor, col16 Color1, col16 Color2, void (*Handler)(long))
3958 {
3959   DrawEverythingNoBlit();
3960   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
3961   SRegionAroundDeny();
3962   long Return = iosystem::ScrollBarQuestion(Topic, v2(16, 6), BeginValue, Step, Min, Max, AbortValue,
3963                                             TopicColor, Color1, Color2, GetMoveCommandKey(KEY_LEFT_INDEX),
3964                                             GetMoveCommandKey(KEY_RIGHT_INDEX), false, Handler);
3965   SRegionAroundAllow();
3966   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
3967   return Return;
3968 }
3969 
IncreaseLOSTick()3970 ulong game::IncreaseLOSTick()
3971 {
3972   if(LOSTick != 0xFE)
3973     return LOSTick += 2;
3974   else
3975   {
3976     CurrentLevel->InitLastSeen();
3977     return LOSTick = 4;
3978   }
3979 }
3980 
UpdateCamera()3981 void game::UpdateCamera()
3982 {
3983   UpdateCameraX();
3984   UpdateCameraY();
3985 }
3986 
HandleQuitMessage()3987 truth game::HandleQuitMessage()
3988 {
3989 #ifdef USE_SDL
3990 
3991   if(IsRunning())
3992   {
3993     if(IsInGetCommand())
3994     {
3995       switch(Menu(std::vector<bitmap*>(), v2(RES.X >> 1, RES.Y >> 1),
3996                   CONST_S("Do you want to save your game before quitting?\r"),
3997                   CONST_S("Yes\rNo\rCancel\r"), LIGHT_GRAY))
3998       {
3999        case 0:
4000         Save();
4001         RemoveSaves(false); //keep backups during autosaves
4002         break;
4003        case 2:
4004         GetCurrentArea()->SendNewDrawRequest();
4005         DrawEverything();
4006         return false;
4007        default:
4008         festring Msg = CONST_S("cowardly quit the game");
4009         Player->AddScoreEntry(Msg, 0.75);
4010         End(Msg, true, false);
4011         break;
4012       }
4013     }
4014     else
4015       if(!Menu(std::vector<bitmap*>(), v2(RES.X >> 1, RES.Y >> 1),
4016                CONST_S("You can't save at this point. Are you sure you still want to do this?\r"),
4017                CONST_S("Yes\rNo\r"), LIGHT_GRAY)){
4018         RemoveSaves(); //will remove the real saves too
4019         RemoveSaves(true,true); //will remove only the backups now
4020       }
4021       else
4022       {
4023         GetCurrentArea()->SendNewDrawRequest();
4024         DrawEverything();
4025         return false;
4026       }
4027   }
4028 
4029 #endif /* USE_SDL */
4030 
4031   return true;
4032 }
4033 
GetDirectionForVector(v2 Vector)4034 int game::GetDirectionForVector(v2 Vector)
4035 {
4036   for(int c = 0; c < DIRECTION_COMMAND_KEYS; ++c)
4037     if(Vector == GetMoveVector(c))
4038       return c;
4039 
4040   return DIR_ERROR;
4041 }
4042 
GetPlayerAlignmentSum()4043 int GetPlayerAlignmentSum(){
4044   long Sum = 0;
4045 
4046   for(int c = 1; c <= GODS; ++c)
4047   {
4048     if(game::GetGod(c)->GetRelation() > 0)
4049       Sum += game::GetGod(c)->GetRelation() * (5 - game::GetGod(c)->GetAlignment());
4050   }
4051 
4052   return Sum;
4053 }
4054 
GetPlayerAlignment()4055 int game::GetPlayerAlignment()
4056 {
4057   long Sum = GetPlayerAlignmentSum();
4058 
4059   if(Sum >  15000)return  4;
4060   if(Sum >  10000)return  3;
4061   if(Sum >   5000)return  2;
4062   if(Sum >   1000)return  1;
4063   if(Sum >  -1000)return  0;
4064   if(Sum >  -5000)return -1;
4065   if(Sum > -10000)return -2;
4066   if(Sum > -15000)return -3;
4067 
4068   return -4;
4069 }
4070 
GetVerbalPlayerAlignment()4071 cchar* game::GetVerbalPlayerAlignment()
4072 {
4073   switch(GetPlayerAlignment()){
4074   case  4:return "extremely lawful";
4075   case  3:return "very lawful";
4076   case  2:return "lawful";
4077   case  1:return "mildly lawful";
4078   case  0:return "neutral";
4079   case -1:return "mildly chaotic";
4080   case -2:return "chaotic";
4081   case -3:return "very chaotic";
4082   case -4:return "extremely chaotic";
4083   }
4084 
4085   ABORT("unsupported alignment %d",GetPlayerAlignment());
4086   return NULL; //return just to let it compile
4087 
4088   /* //kept just in case something changes...
4089   long Sum = GetPlayerAlignment();
4090   if(Sum >  15000)return "extremely lawful";
4091   if(Sum >  10000)return "very lawful";
4092   if(Sum >   5000)return "lawful";
4093   if(Sum >   1000)return "mildly lawful";
4094   if(Sum >  -1000)return "neutral";
4095   if(Sum >  -5000)return "mildly chaotic";
4096   if(Sum > -10000)return "chaotic";
4097   if(Sum > -15000)return "very chaotic";
4098   return "extremely chaotic";
4099   */
4100 }
4101 
CreateGods()4102 void game::CreateGods()
4103 {
4104   God = new god*[GODS + 1];
4105   God[0] = 0;
4106 
4107   for(int c = 1; c < protocontainer<god>::GetSize(); ++c)
4108     God[c] = protocontainer<god>::GetProto(c)->Spawn();
4109 }
4110 
BusyAnimation()4111 void game::BusyAnimation()
4112 {
4113   BusyAnimation(DOUBLE_BUFFER, false);
4114 }
4115 
BusyAnimation(bitmap * Buffer,truth ForceDraw)4116 void game::BusyAnimation(bitmap* Buffer, truth ForceDraw)
4117 {
4118   static clock_t LastTime = 0;
4119   static int Frame = 0;
4120   static blitdata B1 = { 0,
4121                          { 0, 0 },
4122                          { 0, 0 },
4123                          { RES.X, RES.Y },
4124                          { 0 },
4125                          0,
4126                          0 };
4127   static blitdata B2 = { 0,
4128                          { 0, 0 },
4129                          { (RES.X >> 1) - 100, (RES.Y << 1) / 3 - 100 },
4130                          { 200, 200 },
4131                          { 0 },
4132                          0,
4133                          0 };
4134 
4135   if(ForceDraw || clock() - LastTime > CLOCKS_PER_SEC / 25)
4136   {
4137     B2.Bitmap = Buffer;
4138     B2.Dest.X = (RES.X >> 1) - 100 + EnterTextDisplacement.X;
4139     B2.Dest.Y = (RES.Y << 1) / 3 - 100 + EnterTextDisplacement.Y;
4140 
4141     if(EnterImage)
4142     {
4143       B1.Bitmap = Buffer;
4144       EnterImage->NormalMaskedBlit(B1);
4145     }
4146 
4147     BusyAnimationCache[Frame]->NormalBlit(B2);
4148 
4149     if(Buffer == DOUBLE_BUFFER)
4150       graphics::BlitDBToScreen();
4151 
4152     if(++Frame == 32)
4153       Frame = 0;
4154 
4155     LastTime = clock();
4156   }
4157 }
4158 
CreateBusyAnimationCache()4159 void game::CreateBusyAnimationCache()
4160 {
4161   bitmap Elpuri(TILE_V2, TRANSPARENT_COLOR);
4162   Elpuri.ActivateFastFlag();
4163   packcol16 Color = MakeRGB16(60, 60, 60);
4164   igraph::GetCharacterRawGraphic()->MaskedBlit(&Elpuri, v2(64, 0), ZERO_V2, TILE_V2, &Color);
4165   bitmap Circle(v2(200, 200), TRANSPARENT_COLOR);
4166   Circle.ActivateFastFlag();
4167 
4168   for(int x = 0; x < 4; ++x)
4169     Circle.DrawPolygon(100, 100, 95 + x, 50, MakeRGB16(255 - 12 * x, 0, 0));
4170 
4171   blitdata B1 = { 0,
4172                   { 0, 0 },
4173                   { 92, 92 },
4174                   { TILE_SIZE, TILE_SIZE },
4175                   { 0 },
4176                   TRANSPARENT_COLOR,
4177                   0 };
4178 
4179   blitdata B2 = { 0,
4180                   { 0, 0 },
4181                   { 0, 0 },
4182                   { 200, 200 },
4183                   { 0 },
4184                   TRANSPARENT_COLOR,
4185                   0 };
4186 
4187   for(int c = 0; c < 32; ++c)
4188   {
4189     B1.Bitmap = B2.Bitmap = BusyAnimationCache[c] = new bitmap(v2(200, 200), 0);
4190     B1.Bitmap->ActivateFastFlag();
4191     Elpuri.NormalMaskedBlit(B1);
4192     double Rotation = 0.3 + c * FPI / 80;
4193 
4194     for(int x = 0; x < 10; ++x)
4195       B1.Bitmap->DrawPolygon(100, 100, 95, 5, MakeRGB16(5 + 25 * x, 0, 0), false, true, Rotation + double(x) / 50);
4196 
4197     Circle.NormalMaskedBlit(B2);
4198   }
4199 }
4200 
4201 bool bQuestionMode=false;
IsQuestionMode()4202 bool game::IsQuestionMode()
4203 {
4204   return bQuestionMode || bPositionQuestionMode;
4205 }
4206 
AskForKeyPress(cfestring & Topic)4207 int game::AskForKeyPress(cfestring& Topic)
4208 {
4209   bQuestionMode=true;
4210 
4211   DrawEverythingNoBlit();
4212   FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CapitalizeCopy().CStr());
4213   graphics::BlitDBToScreen();
4214 
4215   int Key = GET_KEY();
4216   #ifdef FELIST_WAITKEYUP //not actually felist here but is the waitkeyup event
4217   if(game::GetAutoPlayMode()==0)
4218     for(;;){if(WAIT_FOR_KEY_UP())break;};
4219   #endif
4220 
4221   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
4222 
4223   bQuestionMode=false;
4224   return Key;
4225 }
4226 
4227 /* Handler is called when the key has been identified as a movement key
4228  * KeyHandler is called when the key has NOT been identified as a movement key
4229  * Both can be deactivated by passing 0 as parameter */
4230 
PositionQuestion(cfestring & Topic,v2 CursorPos,void (* Handler)(v2),positionkeyhandler KeyHandler,truth Zoom)4231 v2 game::PositionQuestion(cfestring& Topic, v2 CursorPos, void (*Handler)(v2),
4232                           positionkeyhandler KeyHandler, truth Zoom)
4233 {
4234   int Key = 0;
4235   SetDoZoom(Zoom);
4236   v2 Return;
4237   CursorData = RED_CURSOR;
4238 
4239   if(Handler)
4240     Handler(CursorPos);
4241 
4242   bool bMapNotesMode = bDrawMapOverlayEnabled && bShowMapNotes;
4243 
4244   /**
4245    * using the min millis value grants mouse will be updated most often possible
4246    * default key -1 just to be ignored
4247    */
4248   if(bMapNotesMode)
4249     globalwindowhandler::SetKeyTimeout(100,-1);
4250 
4251   bPositionQuestionMode=true;
4252   v2 v2PreviousClick=v2(0,0);
4253   for(;;)
4254   {
4255     square* Square = GetCurrentArea()->GetSquare(CursorPos);
4256 
4257     if(bMapNotesMode){
4258       lsquare* lsqrMapNote = GetHighlightedMapNoteLSquare();
4259       if(lsqrMapNote){
4260         mouseclick mc = globalwindowhandler::ConsumeMouseEvent();
4261         if(mc.btn==1){
4262           CursorPos = lsqrMapNote->GetPos();
4263           if(v2PreviousClick == CursorPos){ //the 2nd click on same pos will accept as expected TODO fast double click detection, just reset v2PreviousClick after 0.5s ?
4264             Return =  CursorPos;
4265             break;
4266           }
4267           v2PreviousClick = CursorPos;
4268         }
4269       }
4270 
4271       CheckAddAutoMapNote(Square);
4272     }
4273 
4274     if(!Square->HasBeenSeen()
4275        && (!Square->GetCharacter() || !Square->GetCharacter()->CanBeSeenByPlayer())
4276        && !GetSeeWholeMapCheatMode())
4277       DOUBLE_BUFFER->Fill(CalculateScreenCoordinates(CursorPos), TILE_V2, BLACK);
4278     else
4279       GetCurrentArea()->GetSquare(CursorPos)->SendStrongNewDrawRequest();
4280 
4281     if(Key == ' ' || Key == '.')
4282     {
4283       Return = CursorPos;
4284       break;
4285     }
4286 
4287     if(Key == KEY_ESC)
4288     {
4289       Return = ERROR_V2;
4290       break;
4291     }
4292 
4293     v2 DirectionVector = GetDirectionVectorForKey(Key);
4294 
4295     if(DirectionVector != ERROR_V2)
4296     {
4297       CursorPos += DirectionVector;
4298 
4299       if(CursorPos.X > GetCurrentArea()->GetXSize() - 1) CursorPos.X = 0;
4300       if(CursorPos.X < 0) CursorPos.X = GetCurrentArea()->GetXSize() - 1;
4301       if(CursorPos.Y > GetCurrentArea()->GetYSize() - 1) CursorPos.Y = 0;
4302       if(CursorPos.Y < 0) CursorPos.Y = GetCurrentArea()->GetYSize() - 1;
4303 
4304       if(Handler)
4305         Handler(CursorPos);
4306     }
4307     else if(KeyHandler)
4308     {
4309       CursorPos = KeyHandler(CursorPos, Key);
4310 
4311       if(CursorPos == ERROR_V2 || CursorPos == ABORT_V2)
4312       {
4313         Return = CursorPos;
4314         break;
4315       }
4316     }
4317 
4318     if(CursorPos.X < GetCamera().X + 3 || CursorPos.X >= GetCamera().X + GetScreenXSize() - 3)
4319       UpdateCameraX(CursorPos.X);
4320 
4321     if(CursorPos.Y < GetCamera().Y + 3 || CursorPos.Y >= GetCamera().Y + GetScreenYSize() - 3)
4322       UpdateCameraY(CursorPos.Y);
4323 
4324     FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Topic.CStr());
4325     SetCursorPos(CursorPos);
4326     UpdatePosAroundForXBRZ(CursorPos);
4327     DrawEverything();
4328     Key = GET_KEY();
4329   }
4330   bPositionQuestionMode=false;
4331 
4332   // for text
4333   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
4334 
4335   // for zoom
4336   igraph::BlitBackGround(ZoomPos, TILE_V2*iZoomFactor);
4337   SetDoZoom(false);
4338 
4339   SetCursorPos(v2(-1, -1));
4340 
4341   if(ivanconfig::IsCenterOnPlayerAfterLook()){
4342     v2 ppos = Player->GetPosSafely();
4343     if(!ppos.Is0()){
4344       UpdateCameraX(ppos.X);
4345       UpdateCameraY(ppos.Y);
4346     }
4347   }
4348 
4349   if(bMapNotesMode)
4350     globalwindowhandler::ResetKeyTimeout();
4351 
4352   return Return;
4353 }
4354 
LookHandler(v2 CursorPos)4355 void game::LookHandler(v2 CursorPos)
4356 {
4357   square* Square = GetCurrentArea()->GetSquare(CursorPos);
4358   festring OldMemory;
4359 
4360   if(GetSeeWholeMapCheatMode())
4361   {
4362     OldMemory = Square->GetMemorizedDescription();
4363 
4364     if(IsInWilderness())
4365       GetWorldMap()->GetWSquare(CursorPos)->UpdateMemorizedDescription(true);
4366     else
4367       GetCurrentLevel()->GetLSquare(CursorPos)->UpdateMemorizedDescription(true);
4368   }
4369 
4370   festring Msg;
4371 
4372   if(WizardModeIsActive())
4373     Msg<<"["<<CursorPos.X<<","<<CursorPos.Y<<"]";
4374 
4375   if(Square->HasBeenSeen() || GetSeeWholeMapCheatMode())
4376   {
4377     if(
4378         !IsInWilderness()
4379         && !Square->CanBeSeenByPlayer()
4380         && Player->IsEnabled() && Player->GetSquareUnderSafely() // important to block this on death
4381         && GetCurrentLevel()->GetLSquare(CursorPos)->CanBeFeltByPlayer()
4382     ){
4383       Msg << CONST_S("You feel here ");
4384     }else if(Square->CanBeSeenByPlayer(true) || GetSeeWholeMapCheatMode()){
4385       Msg << CONST_S("You see here ");
4386     }else
4387       Msg << CONST_S("You remember here ");
4388 
4389     Msg << Square->GetMemorizedDescription() << '.';
4390 
4391     if(!IsInWilderness() && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode()))
4392     {
4393       lsquare* LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
4394       LSquare->DisplaySmokeInfo(Msg);
4395 
4396       if(LSquare->HasEngravings() && LSquare->IsTransparent())
4397       {
4398         if(LSquare->EngravingsCanBeReadByPlayer() || GetSeeWholeMapCheatMode())
4399           LSquare->DisplayEngravedInfo(Msg);
4400         else
4401           Msg << " Something has been engraved here.";
4402       }
4403     }
4404   }
4405   else
4406     Msg = CONST_S("You have never been here.");
4407 
4408   character* Character = Square->GetCharacter();
4409 
4410   if(Character && (Character->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode()))
4411     Character->DisplayInfo(Msg);
4412 
4413   if(!(RAND() % 10000) && (Square->CanBeSeenByPlayer() || GetSeeWholeMapCheatMode()))
4414     Msg << " You see here a frog eating a magnolia."; //TODO this should trigger some special event and also play a sfx :)
4415 
4416   ADD_MESSAGE("%s", Msg.CStr());
4417 
4418   if(GetSeeWholeMapCheatMode())
4419     Square->SetMemorizedDescription(OldMemory);
4420 }
4421 
AnimationController()4422 truth game::AnimationController()
4423 {
4424   DrawEverythingNoBlit(true);
4425   return true;
4426 }
4427 
InitGlobalValueMap()4428 void game::InitGlobalValueMap()
4429 {
4430   inputfile SaveFile(GetDataDir() + "Script/define.dat", &GlobalValueMap);
4431   festring Word;
4432 
4433   for(SaveFile.ReadWord(Word, false); !SaveFile.Eof(); SaveFile.ReadWord(Word, false))
4434   {
4435     if(Word != "#" || SaveFile.ReadWord() != "define")
4436       ABORT("Illegal datafile define on line %ld!", SaveFile.TellLine());
4437 
4438     SaveFile.ReadWord(Word);DBG1(Word.CStr());
4439 
4440     long value = SaveFile.ReadNumber();DBG1(value);
4441     GlobalValueMap.insert(std::make_pair(Word, value));
4442   }
4443 
4444 }
4445 
TextScreen(cfestring & Text,v2 Displacement,col16 Color,truth GKey,truth Fade,bitmapeditor BitmapEditor)4446 void game::TextScreen(cfestring& Text, v2 Displacement, col16 Color,
4447                       truth GKey, truth Fade, bitmapeditor BitmapEditor)
4448 {
4449   graphics::SetDenyStretchedBlit();
4450   globalwindowhandler::DisableControlLoops();
4451   iosystem::TextScreen(Text, Displacement, Color, GKey, Fade, BitmapEditor);
4452   globalwindowhandler::EnableControlLoops();
4453   //TODO need?  graphics::SetAllowStretchedBlit();
4454   //TODO useful or messy?  graphics::BlitDBToScreen();
4455 }
4456 
4457 /* ... all the keys that are acceptable
4458    DefaultAnswer = REQUIRES_ANSWER if this question requires an answer
4459    Not surprisingly KeyNumber is the number of keys at ...
4460 */
4461 
KeyQuestion(cfestring & Message,int DefaultAnswer,int KeyNumber,...)4462 int game::KeyQuestion(cfestring& Message, int DefaultAnswer, int KeyNumber, ...)
4463 {
4464   bQuestionMode=true;
4465 
4466   int* Key = new int[KeyNumber];
4467   va_list Arguments;
4468   va_start(Arguments, KeyNumber);
4469 
4470   for(int c = 0; c < KeyNumber; ++c)
4471     Key[c] = va_arg(Arguments, int);
4472 
4473   va_end(Arguments);
4474   DrawEverythingNoBlit();
4475   FONT->Printf(DOUBLE_BUFFER, v2(16, 8), WHITE, "%s", Message.CStr());
4476   graphics::BlitDBToScreen();
4477   int Return = 0;
4478 
4479   while(!Return)
4480   {
4481     int k = GET_KEY();
4482 
4483     for(int c = 0; c < KeyNumber; ++c)
4484       if(Key[c] == k)
4485       {
4486         Return = k;
4487         break;
4488       }
4489 
4490     if(!Return && DefaultAnswer != REQUIRES_ANSWER)
4491       Return = DefaultAnswer;
4492   }
4493 
4494   delete [] Key;
4495   igraph::BlitBackGround(v2(16, 6), v2(GetMaxScreenXSize() << 4, 23));
4496 
4497   bQuestionMode=false;
4498   return Return;
4499 }
4500 
LookKeyHandler(v2 CursorPos,int Key)4501 v2 game::LookKeyHandler(v2 CursorPos, int Key)
4502 {
4503   square* Square = GetCurrentArea()->GetSquare(CursorPos);
4504 
4505   switch(Key)
4506   {
4507    case 'i':
4508     if(!IsInWilderness())
4509     {
4510       if(Square->CanBeSeenByPlayer() || CursorPos == Player->GetPosSafely() || GetSeeWholeMapCheatMode()){
4511         lsquare* LSquare = GetCurrentLevel()->GetLSquare(CursorPos);
4512         stack* Stack = LSquare->GetStack();
4513 
4514         if(LSquare->IsTransparent() && Stack->GetVisibleItems(Player))
4515           Stack->DrawContents(Player, "Items here", NO_SELECT|(GetSeeWholeMapCheatMode() ? 0 : NO_SPECIAL_INFO));
4516         else
4517           ADD_MESSAGE("You see no items here.");
4518       }
4519       else
4520         ADD_MESSAGE("You should perhaps move a bit closer.");
4521     }
4522 
4523     break;
4524    case 'c':
4525     if(Square->CanBeSeenByPlayer() || CursorPos == Player->GetPos() || GetSeeWholeMapCheatMode())
4526     {
4527       character* Char = Square->GetCharacter();
4528 
4529       if(Char && (Char->CanBeSeenByPlayer() || Char->IsPlayer() || GetSeeWholeMapCheatMode())){
4530         Char->PrintInfo();
4531       }else
4532         ADD_MESSAGE("You see no one here.");
4533     }
4534     else
4535       ADD_MESSAGE("You should perhaps move a bit closer.");
4536 
4537     break;
4538   }
4539 
4540   return CursorPos;
4541 }
4542 
NameKeyHandler(v2 CursorPos,int Key)4543 v2 game::NameKeyHandler(v2 CursorPos, int Key)
4544 {
4545   if(SelectPet(Key))
4546     return LastPetUnderCursor->GetPos();
4547 
4548   if(Key == 'n' || Key == 'N')
4549   {
4550     character* Char = GetCurrentArea()->GetSquare(CursorPos)->GetCharacter();
4551 
4552     if(Char && Char->CanBeSeenByPlayer())
4553       Char->TryToName();
4554     else
4555       ADD_MESSAGE("You don't see anyone here to name.");
4556   }
4557 
4558   return CursorPos;
4559 }
4560 
End(festring DeathMessage,truth Permanently,truth AndGoToMenu)4561 void game::End(festring DeathMessage, truth Permanently, truth AndGoToMenu)
4562 {
4563   game::SRegionAroundDisable();
4564 
4565   if(!Permanently)
4566     game::Save();
4567 
4568   game::RemoveSaves(true,true); DBGLN; // ONLY THE BACKUPS: after fully saving successfully, is a safe moment to remove them.
4569 
4570   globalwindowhandler::DeInstallControlLoop(AnimationController); DBGLN;
4571   SetIsRunning(false); DBGLN;
4572 
4573   if(Permanently || !WizardModeIsReallyActive())
4574     RemoveSaves(Permanently);
4575 
4576   if(Permanently && !WizardModeIsReallyActive())
4577   {
4578     highscore HScore(GetUserDataDir() + HIGH_SCORE_FILENAME);
4579 
4580     if(HScore.LastAddFailed())
4581     { DBGLN;
4582       iosystem::TextScreen(CONST_S("You didn't manage to get onto the high score list.\n\n\n\n")
4583                            + GetPlayerName() + ", " + DeathMessage + "\nRIP");
4584     }
4585     else
4586       HScore.Draw();
4587   }
4588 
4589   if(AndGoToMenu)
4590   { DBGLN;
4591     /* This prevents monster movement etc. after death. */
4592 
4593     /* Set off the main menu music */
4594     audio::SetPlaybackStatus(0); DBGLN;
4595     audio::ClearMIDIPlaylist(); DBGLN;
4596     audio::LoadMIDIFile("mainmenu.mid", 0, 100); DBGLN;
4597     audio::SetPlaybackStatus(audio::PLAYING); DBGLN;
4598 
4599     throw quitrequest();
4600   }
4601 
4602   DBGLN;
4603 }
4604 
PlayVictoryMusic()4605 void game::PlayVictoryMusic()
4606 {
4607   audio::SetPlaybackStatus(0);
4608   audio::ClearMIDIPlaylist();
4609   audio::LoadMIDIFile("victory.mid", 0, 100);
4610   audio::SetPlaybackStatus(audio::PLAYING);
4611 }
4612 
PlayDefeatMusic()4613 void game::PlayDefeatMusic()
4614 {
4615   audio::SetPlaybackStatus(0);
4616   audio::ClearMIDIPlaylist();
4617   audio::LoadMIDIFile("defeat.mid", 0, 100);
4618   audio::SetPlaybackStatus(audio::PLAYING);
4619 }
4620 
CalculateRoughDirection(v2 Vector)4621 int game::CalculateRoughDirection(v2 Vector)
4622 {
4623   if(!Vector.X && !Vector.Y)
4624     return YOURSELF;
4625 
4626   double Angle = femath::CalculateAngle(Vector);
4627 
4628   if(Angle < FPI / 8)
4629     return EAST;
4630   else if(Angle < 3 * FPI / 8)
4631     return SOUTHEAST;
4632   else if(Angle < 5 * FPI / 8)
4633     return SOUTH;
4634   else if(Angle < 7 * FPI / 8)
4635     return SOUTHWEST;
4636   else if(Angle < 9 * FPI / 8)
4637     return WEST;
4638   else if(Angle < 11 * FPI / 8)
4639     return NORTHWEST;
4640   else if(Angle < 13 * FPI / 8)
4641     return NORTH;
4642   else if(Angle < 15 * FPI / 8)
4643     return NORTHEAST;
4644   else
4645     return EAST;
4646 }
4647 
Menu(std::vector<bitmap * > vBackGround,v2 Pos,cfestring & Topic,cfestring & sMS,col16 Color,cfestring & SmallText1,cfestring & SmallText2)4648 int game::Menu(std::vector<bitmap*> vBackGround, v2 Pos, cfestring& Topic, cfestring& sMS,
4649                col16 Color, cfestring& SmallText1, cfestring& SmallText2)
4650 {
4651   globalwindowhandler::DisableControlLoops();
4652   int Return = iosystem::Menu(vBackGround, Pos, Topic, sMS, Color, SmallText1, SmallText2,
4653                               ivanconfig::GetExtraMenuGraphics());
4654   globalwindowhandler::EnableControlLoops();
4655   return Return;
4656 }
4657 
InitDangerMap()4658 void game::InitDangerMap()
4659 {
4660   truth First = true;
4661 
4662   for(int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1)
4663   {
4664     BusyAnimation();
4665     const character::prototype* Proto = protocontainer<character>::GetProto(c1);
4666     const character::database*const* ConfigData = Proto->GetConfigData();
4667     int ConfigSize = Proto->GetConfigSize();
4668 
4669     for(int c2 = 0; c2 < ConfigSize; ++c2)
4670       if(!ConfigData[c2]->IsAbstract)
4671       {
4672         int Config = ConfigData[c2]->Config;
4673 
4674         if(First)
4675         {
4676           NextDangerIDType = c1;
4677           NextDangerIDConfigIndex = c2;
4678           First = false;
4679         }
4680 
4681         character* Char = Proto->Spawn(Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE|NO_SEVERED_LIMBS);
4682         double NakedDanger = Char->GetRelativeDanger(Player, true);
4683         delete Char;
4684         Char = Proto->Spawn(Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE|NO_SEVERED_LIMBS);
4685         double EquippedDanger = Char->GetRelativeDanger(Player, true);
4686         delete Char;
4687         DangerMap[configid(c1, Config)] = dangerid(NakedDanger, EquippedDanger);
4688       }
4689   }
4690 }
4691 
CalculateNextDanger()4692 void game::CalculateNextDanger()
4693 {
4694   if(IsInWilderness() || !*CurrentLevel->GetLevelScript()->GenerateMonsters())
4695     return;
4696 
4697   const character::prototype* Proto = protocontainer<character>::GetProto(NextDangerIDType);
4698   const character::database*const* ConfigData = Proto->GetConfigData();
4699   const character::database* DataBase = ConfigData[NextDangerIDConfigIndex];
4700   dangermap::iterator DangerIterator = DangerMap.find(configid(NextDangerIDType, DataBase->Config));
4701   team* Team = GetTeam(PLAYER_TEAM);
4702 
4703   if(DataBase && DangerIterator != DangerMap.end())
4704   {
4705     character* Char = Proto->Spawn(DataBase->Config, NO_EQUIPMENT|NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
4706     double DangerSum = Player->GetRelativeDanger(Char, true);
4707 
4708     for(character* p : Team->GetMember())
4709       if(p->IsEnabled() && !p->IsTemporary() && !RAND_N(10))
4710         DangerSum += p->GetRelativeDanger(Char, true) / 4;
4711 
4712     double CurrentDanger = 1 / DangerSum;
4713     double NakedDanger = DangerIterator->second.NakedDanger;
4714     delete Char;
4715 
4716     if(NakedDanger > CurrentDanger)
4717       DangerIterator->second.NakedDanger = (NakedDanger * 9 + CurrentDanger) / 10;
4718 
4719     Char = Proto->Spawn(DataBase->Config, NO_PIC_UPDATE|NO_EQUIPMENT_PIC_UPDATE);
4720     DangerSum = Player->GetRelativeDanger(Char, true);
4721 
4722     for(character* p : Team->GetMember())
4723       if(p->IsEnabled() && !p->IsTemporary() && !RAND_N(10))
4724         DangerSum += p->GetRelativeDanger(Char, true) / 4;
4725 
4726     CurrentDanger = 1 / DangerSum;
4727     double EquippedDanger = DangerIterator->second.EquippedDanger;
4728     delete Char;
4729 
4730     if(EquippedDanger > CurrentDanger)
4731       DangerIterator->second.EquippedDanger = (EquippedDanger * 9 + CurrentDanger) / 10;
4732 
4733     if(++NextDangerIDConfigIndex < Proto->GetConfigSize())
4734       return;
4735 
4736     for(;;)
4737     {
4738       if(++NextDangerIDType >= protocontainer<character>::GetSize())
4739         NextDangerIDType = 1;
4740 
4741       Proto = protocontainer<character>::GetProto(NextDangerIDType);
4742       ConfigData = Proto->GetConfigData();
4743       int ConfigSize = Proto->GetConfigSize();
4744 
4745       for(int c = 0; c < ConfigSize; ++c)
4746         if(!ConfigData[c]->IsAbstract)
4747         {
4748           NextDangerIDConfigIndex = c;
4749           return;
4750         }
4751     }
4752   }
4753   else
4754     ABORT("It is dangerous to go ice fishing in the summer.");
4755 }
4756 
TryTravel(int Dungeon,int Area,int EntryIndex,truth AllowHostiles,truth AlliesFollow)4757 truth game::TryTravel(int Dungeon, int Area, int EntryIndex, truth AllowHostiles, truth AlliesFollow)
4758 {
4759   charactervector Group;
4760 
4761   if(LeaveArea(Group, AllowHostiles, AlliesFollow))
4762   {
4763     CurrentDungeonIndex = Dungeon;
4764     EnterArea(Group, Area, EntryIndex);
4765     return true;
4766   }
4767   else
4768     return false;
4769 }
4770 
LeaveArea(charactervector & Group,truth AllowHostiles,truth AlliesFollow)4771 truth game::LeaveArea(charactervector& Group, truth AllowHostiles, truth AlliesFollow)
4772 {
4773   if(!IsInWilderness())
4774   {
4775     if(AlliesFollow && !GetCurrentLevel()->CollectCreatures(Group, Player, AllowHostiles))
4776       return false;
4777 
4778     Player->Remove();
4779     GetCurrentDungeon()->SaveLevel(SaveName(), CurrentLevelIndex);
4780   }
4781   else
4782   {
4783     Player->Remove();
4784     GetWorldMap()->GetPlayerGroup().swap(Group);
4785     SaveWorldMap();
4786   }
4787 
4788   return true;
4789 }
4790 
4791 /* Used always when the player enters an area. */
4792 
EnterArea(charactervector & Group,int Area,int EntryIndex)4793 void game::EnterArea(charactervector& Group, int Area, int EntryIndex)
4794 {
4795   if(Area != WORLD_MAP)
4796   {
4797     Generating = true;
4798     SetIsInWilderness(false);
4799     CurrentLevelIndex = Area;
4800     truth New = !PrepareRandomBone(Area) && !GetCurrentDungeon()->PrepareLevel(Area);
4801     igraph::CreateBackGround(*CurrentLevel->GetLevelScript()->GetBackGroundType());
4802     GetCurrentArea()->SendNewDrawRequest();
4803     v2 Pos = GetCurrentLevel()->GetEntryPos(Player, EntryIndex);
4804     GetCurrentDungeon()->PrepareMusic(Area);
4805 
4806     if(Player)
4807     {
4808       lsquare* lsqr = GetCurrentLevel()->GetLSquare(Pos);
4809       character* NPC = lsqr->GetCharacter();
4810 
4811       bool bMoveAway=true;
4812       /**
4813        * Genetrix Vesana goal is to protect the passage (or not?) TODO tho coming from above could grant a huge damage strike to help to kill it, what could be a tactical manouver
4814        * using now largecreature check because of this crash stack:
4815           area::GetSquare(v2) const //HERE V2 had invalid huge negative values for X and Y
4816           largecreature::PutTo(v2)
4817           character::PutNear(v2) //TODO some complexer code could be implemented at this method
4818           lsquare::KickAnyoneStandingHereAway()
4819           game::EnterArea(std::vector<character*, std::allocator<character*> >&, int, int)+0x164)
4820        */
4821       if(bMoveAway && dynamic_cast<largecreature*>(NPC)!=NULL)bMoveAway=false;
4822 
4823       if(bMoveAway)
4824         lsqr->KickAnyoneStandingHereAway();
4825 
4826       Player->PutToOrNear(Pos);
4827 
4828       game::CheckAddAutoMapNote();
4829       game::CheckAutoPickup();
4830     }
4831     else
4832     {
4833       SetPlayer(bugfixdp::ValidatePlayerAt(GetCurrentLevel()->GetLSquare(Pos)));
4834     }
4835 
4836     uint c;
4837 
4838     for(c = 0; c < Group.size(); ++c)
4839     {
4840       v2 NPCPos = GetCurrentLevel()->GetNearestFreeSquare(Group[c], Pos);
4841 
4842       if(NPCPos == ERROR_V2)
4843         NPCPos = GetCurrentLevel()->GetRandomSquare(Group[c]);
4844 
4845       Group[c]->PutTo(NPCPos);
4846     }
4847 
4848     GetCurrentLevel()->FiatLux();
4849     ctruth* AutoReveal = GetCurrentLevel()->GetLevelScript()->AutoReveal();
4850 
4851     if(New && AutoReveal && *AutoReveal)
4852       GetCurrentLevel()->Reveal();
4853 
4854     ShowLevelMessage();
4855     SendLOSUpdateRequest();
4856     UpdateCamera();
4857 
4858     /* Gum solution! */
4859 
4860     if(New && GetCurrentLevel()->IsOnGround() &&
4861        CurrentDungeonIndex == ATTNAM)
4862     {
4863       GlobalRainLiquid = powder::Spawn(SNOW);
4864       GlobalRainSpeed = v2(-64, 128);
4865       CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
4866     }
4867 
4868     if(New && GetCurrentLevel()->IsOnGround() && CurrentDungeonIndex == XINROCH_TOMB)
4869     {
4870       GlobalRainLiquid = powder::Spawn(SOOT);
4871       GlobalRainSpeed = v2(-64, 128);
4872       CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
4873     }
4874 
4875     if(New && GetCurrentLevel()->IsOnGround() &&
4876        (CurrentDungeonIndex == NEW_ATTNAM || CurrentDungeonIndex == ASLONA_CASTLE ||
4877         CurrentDungeonIndex == REBEL_CAMP || CurrentDungeonIndex == MONDEDR ||
4878         CurrentDungeonIndex == DARK_FOREST || CurrentDungeonIndex == IRINOX))
4879     {
4880       GlobalRainLiquid = liquid::Spawn(WATER);
4881       GlobalRainSpeed = v2(256, 512);
4882       CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
4883     }
4884 
4885     if(New && CurrentDungeonIndex == ELPURI_CAVE && Area == OREE_LAIR)
4886     {
4887       GlobalRainLiquid = liquid::Spawn(BLOOD);
4888       GlobalRainSpeed = v2(256, 512);
4889       CurrentLevel->CreateGlobalRain(GlobalRainLiquid, GlobalRainSpeed);
4890       GlobalRainLiquid->SetVolumeNoSignals(200);
4891       CurrentLevel->EnableGlobalRain();
4892     }
4893 
4894     Generating = false;
4895     GetCurrentLevel()->UpdateLOS();
4896     Player->SignalStepFrom(0);
4897 
4898     for(c = 0; c < Group.size(); ++c)
4899       Group[c]->SignalStepFrom(0);
4900 
4901     if(ivanconfig::GetAutoSaveInterval())
4902       Save(GetAutoSaveFileName().CStr());
4903   }
4904   else
4905   {
4906     igraph::CreateBackGround(GRAY_FRACTAL);
4907     SetIsInWilderness(true);
4908     LoadWorldMap();
4909     SetCurrentArea(WorldMap);
4910     CurrentWSquareMap = WorldMap->GetMap();
4911     GetWorldMap()->GetPlayerGroup().swap(Group);
4912     Player->PutTo(GetWorldMap()->GetEntryPos(Player, EntryIndex));
4913     SendLOSUpdateRequest();
4914     UpdateCamera();
4915     GetWorldMap()->UpdateLOS();
4916 
4917     audio::SetPlaybackStatus(0);
4918     audio::ClearMIDIPlaylist();
4919     audio::LoadMIDIFile("world.mid", 0, 100);
4920     audio::SetPlaybackStatus(audio::PLAYING);
4921 
4922     if(ivanconfig::GetAutoSaveInterval())
4923       Save(GetAutoSaveFileName().CStr());
4924   }
4925 
4926   iCurrentDungeonTurn=-1; //-1 as it will be the turn index and be inc before checking
4927 }
4928 
CompareLightToInt(col24 L,col24 Int)4929 int game::CompareLightToInt(col24 L, col24 Int)
4930 {
4931   if((L & 0xFF0000) > Int || (L & 0xFF00) > Int || (L & 0xFF) > Int)
4932     return 1;
4933   else if((L & 0xFF0000) == Int || (L & 0xFF00) == Int || (L & 0xFF) == Int)
4934     return 0;
4935   else
4936     return -1;
4937 }
4938 
prepareList(felist & rList,v2 & v2TopLeft,int & iW)4939 void prepareList(felist& rList, v2& v2TopLeft, int& iW){
4940   bool bAltItemPos = ivanconfig::GetAltListItemPos()==2;
4941   if(iRegionListItem==-1){ //not initialized
4942     bAltItemPos=false;
4943   }else{
4944     if(ivanconfig::GetAltListItemPos()==0){
4945       graphics::SetSRegionEnabled(iRegionListItem,false); //user option override
4946     }
4947 
4948     if(!graphics::IsSRegionEnabled(iRegionListItem)){ //can be by user option or dependent on what command the user is using like 'drop'
4949       bAltItemPos=false;
4950     }
4951   }
4952 //  bool bAltItemPos = graphics::IsSRegionEnabled(iRegionListItem) && ivanconfig::GetAltListItemPos()==2;
4953 
4954   rList.SetOriginalPos(v2TopLeft);
4955 
4956   int iX=v2TopLeft.X+10,iY=v2TopLeft.Y+10;
4957   if(ivanconfig::GetStartingDungeonGfxScale()>=2){
4958     //mainly to be drawn above the small dungeon (that gets scaled up)
4959     iX=v2TopLeft.X-3;
4960     iY=v2TopLeft.Y-3;
4961   }
4962 
4963   int iItemW = bldListItemTMP.Border.X * bldListItemTMP.Stretch;
4964   if(bAltItemPos){
4965     iX += area::getOutlineThickness()*2; //to leave some space to alt item outline
4966     iX += iItemW;
4967   }else{
4968     bldListItemTMP.Dest = ZoomPos;
4969   }
4970 
4971   if(graphics::IsSRegionEnabled(iRegionSilhouette)){
4972     iW=ivanconfig::GetAltListItemWidth();
4973     //cant be so automatic... or user wants alt or default position... //if(bAltItemPos){iW+=iItemW;}
4974   }
4975 
4976   v2TopLeft=v2(iX,iY); DBGSV2(v2TopLeft);
4977 
4978   graphics::SetSpecialListItemAltPos(bAltItemPos);
4979   if(bAltItemPos)
4980     felist::SetListItemAltPosMinY(area::getTopLeftCorner().Y);
4981 }
4982 
prepareListWidth(int iW)4983 int prepareListWidth(int iW){
4984   return iW;
4985 }
4986 
SetStandardListAttributes(felist & List)4987 void game::SetStandardListAttributes(felist& List)
4988 {
4989   v2 v2TopLeft = area::getTopLeftCorner();
4990   int iW = iListWidth;
4991 
4992   prepareList(List, v2TopLeft, iW);
4993 
4994   List.SetPos(v2TopLeft);
4995   List.SetWidth(iW);
4996   List.SetFlags(DRAW_BACKGROUND_AFTERWARDS);
4997   List.SetUpKey(GetMoveCommandKey(KEY_UP_INDEX));
4998   List.SetDownKey(GetMoveCommandKey(KEY_DOWN_INDEX));
4999 }
5000 
InitPlayerAttributeAverage()5001 void game::InitPlayerAttributeAverage()
5002 {
5003   AveragePlayerArmStrengthExperience
5004     = AveragePlayerLegStrengthExperience
5005     = AveragePlayerDexterityExperience
5006     = AveragePlayerAgilityExperience
5007     = 0;
5008 
5009   if(!Player->IsHumanoid())
5010     return;
5011 
5012   humanoid* Player = static_cast<humanoid*>(GetPlayer());
5013   int Arms = 0;
5014   int Legs = 0;
5015   arm* RightArm = Player->GetRightArm();
5016 
5017   if(RightArm && !RightArm->UseMaterialAttributes())
5018   {
5019     AveragePlayerArmStrengthExperience += RightArm->GetStrengthExperience();
5020     AveragePlayerDexterityExperience += RightArm->GetDexterityExperience();
5021     ++Arms;
5022   }
5023 
5024   arm* LeftArm = Player->GetLeftArm();
5025 
5026   if(LeftArm && !LeftArm->UseMaterialAttributes())
5027   {
5028     AveragePlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
5029     AveragePlayerDexterityExperience += LeftArm->GetDexterityExperience();
5030     ++Arms;
5031   }
5032 
5033   leg* RightLeg = Player->GetRightLeg();
5034 
5035   if(RightLeg && !RightLeg->UseMaterialAttributes())
5036   {
5037     AveragePlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
5038     AveragePlayerAgilityExperience += RightLeg->GetAgilityExperience();
5039     ++Legs;
5040   }
5041 
5042   leg* LeftLeg = Player->GetLeftLeg();
5043 
5044   if(LeftLeg && !LeftLeg->UseMaterialAttributes())
5045   {
5046     AveragePlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
5047     AveragePlayerAgilityExperience += LeftLeg->GetAgilityExperience();
5048     ++Legs;
5049   }
5050 
5051   if(Arms)
5052   {
5053     AveragePlayerArmStrengthExperience /= Arms;
5054     AveragePlayerDexterityExperience /= Arms;
5055   }
5056 
5057   if(Legs)
5058   {
5059     AveragePlayerLegStrengthExperience /= Legs;
5060     AveragePlayerAgilityExperience /= Legs;
5061   }
5062 }
5063 
UpdatePlayerAttributeAverage()5064 void game::UpdatePlayerAttributeAverage()
5065 {
5066   if(!Player->IsHumanoid())
5067     return;
5068 
5069   humanoid* Player = static_cast<humanoid*>(GetPlayer());
5070   double PlayerArmStrengthExperience = 0;
5071   double PlayerLegStrengthExperience = 0;
5072   double PlayerDexterityExperience = 0;
5073   double PlayerAgilityExperience = 0;
5074   int Arms = 0;
5075   int Legs = 0;
5076   arm* RightArm = Player->GetRightArm();
5077 
5078   if(RightArm && !RightArm->UseMaterialAttributes())
5079   {
5080     PlayerArmStrengthExperience += RightArm->GetStrengthExperience();
5081     PlayerDexterityExperience += RightArm->GetDexterityExperience();
5082     ++Arms;
5083   }
5084 
5085   arm* LeftArm = Player->GetLeftArm();
5086 
5087   if(LeftArm && !LeftArm->UseMaterialAttributes())
5088   {
5089     PlayerArmStrengthExperience += LeftArm->GetStrengthExperience();
5090     PlayerDexterityExperience += LeftArm->GetDexterityExperience();
5091     ++Arms;
5092   }
5093 
5094   leg* RightLeg = Player->GetRightLeg();
5095 
5096   if(RightLeg && !RightLeg->UseMaterialAttributes())
5097   {
5098     PlayerLegStrengthExperience += RightLeg->GetStrengthExperience();
5099     PlayerAgilityExperience += RightLeg->GetAgilityExperience();
5100     ++Legs;
5101   }
5102 
5103   leg* LeftLeg = Player->GetLeftLeg();
5104 
5105   if(LeftLeg && !LeftLeg->UseMaterialAttributes())
5106   {
5107     PlayerLegStrengthExperience += LeftLeg->GetStrengthExperience();
5108     PlayerAgilityExperience += LeftLeg->GetAgilityExperience();
5109     ++Legs;
5110   }
5111 
5112   if(Arms)
5113   {
5114     AveragePlayerArmStrengthExperience = (49 * AveragePlayerArmStrengthExperience
5115                                           + PlayerArmStrengthExperience / Arms) / 50;
5116     AveragePlayerDexterityExperience = (49 * AveragePlayerDexterityExperience
5117                                         + PlayerDexterityExperience / Arms) / 50;
5118   }
5119 
5120   if(Legs)
5121   {
5122     AveragePlayerLegStrengthExperience = (49 * AveragePlayerLegStrengthExperience
5123                                           + PlayerLegStrengthExperience / Legs) / 50;
5124     AveragePlayerAgilityExperience = (49 * AveragePlayerAgilityExperience
5125                                       + PlayerAgilityExperience / Legs) / 50;
5126   }
5127 }
5128 
CallForAttention(v2 Pos,int RangeSquare)5129 void game::CallForAttention(v2 Pos, int RangeSquare)
5130 {
5131   for(int c = 0; c < GetTeams(); ++c)
5132   {
5133     if(GetTeam(c)->HasEnemy())
5134       for(character* p : GetTeam(c)->GetMember())
5135         if(p->IsEnabled())
5136         {
5137           long ThisDistance = HypotSquare(long(p->GetPos().X) - Pos.X, long(p->GetPos().Y) - Pos.Y);
5138 
5139           if(ThisDistance <= RangeSquare && !p->IsGoingSomeWhere())
5140             p->SetGoingTo(Pos);
5141         }
5142   }
5143 }
5144 
operator <<(outputfile & SaveFile,const homedata * HomeData)5145 outputfile& operator<<(outputfile& SaveFile, const homedata* HomeData)
5146 {
5147   if(HomeData)
5148   {
5149     SaveFile.Put(1);
5150     SaveFile << HomeData->Pos << HomeData->Dungeon << HomeData->Level << HomeData->Room;
5151   }
5152   else
5153     SaveFile.Put(0);
5154 
5155   return SaveFile;
5156 }
5157 
operator >>(inputfile & SaveFile,homedata * & HomeData)5158 inputfile& operator>>(inputfile& SaveFile, homedata*& HomeData)
5159 {
5160   if(SaveFile.Get())
5161   {
5162     HomeData = new homedata;
5163     SaveFile >> HomeData->Pos >> HomeData->Dungeon >> HomeData->Level >> HomeData->Room;
5164   }
5165 
5166   return SaveFile;
5167 }
5168 
CreateNewCharacterID(character * NewChar)5169 ulong game::CreateNewCharacterID(character* NewChar)
5170 {
5171   ulong ID = NextCharacterID++;
5172   CharacterIDMap.insert(std::make_pair(ID, NewChar));
5173   return ID;
5174 }
5175 
CreateNewItemID(item * NewItem)5176 ulong game::CreateNewItemID(item* NewItem)
5177 {
5178   ulong ID = NextItemID++;
5179 
5180   if(NewItem)
5181     ItemIDMap.insert(std::make_pair(ID, NewItem));
5182 
5183   return ID;
5184 }
5185 
CreateNewTrapID(entity * NewTrap)5186 ulong game::CreateNewTrapID(entity* NewTrap)
5187 {
5188   ulong ID = NextTrapID++;
5189 
5190   if(NewTrap)
5191     TrapIDMap.insert(std::make_pair(ID, NewTrap));
5192 
5193   return ID;
5194 }
5195 
SearchCharacter(ulong ID)5196 character* game::SearchCharacter(ulong ID)
5197 {
5198   characteridmap::iterator Iterator = CharacterIDMap.find(ID);
5199   return Iterator != CharacterIDMap.end() ? Iterator->second : 0;
5200 }
5201 
GetAllCharacters()5202 std::vector<character*> game::GetAllCharacters()
5203 {
5204   std::vector<character*> vc;
5205   for(int i=0;i<CharacterIDMap.size();i++){
5206     if(CharacterIDMap[i]!=NULL)
5207       vc.push_back(CharacterIDMap[i]);
5208   }
5209   return vc;
5210 }
5211 
GetCharacterIDMapCopy()5212 characteridmap game::GetCharacterIDMapCopy()
5213 {
5214   return CharacterIDMap;
5215 }
5216 
GetAllItems()5217 std::vector<item*> game::GetAllItems()
5218 {
5219   std::vector<item*> vc;
5220   for(int i=0;i<ItemIDMap.size();i++){
5221     if(ItemIDMap[i]!=NULL)
5222       vc.push_back(ItemIDMap[i]);
5223   }
5224   return vc;
5225 }
GetItemIDMapCopy()5226 itemidmap game::GetItemIDMapCopy()
5227 {
5228   return ItemIDMap;
5229 }
5230 
SearchItem(ulong ID)5231 item* game::SearchItem(ulong ID)
5232 {
5233   itemidmap::iterator Iterator = ItemIDMap.find(ID);
5234   return Iterator != ItemIDMap.end() ? Iterator->second : 0;
5235 }
5236 
SearchTrap(ulong ID)5237 entity* game::SearchTrap(ulong ID)
5238 {
5239   trapidmap::iterator Iterator = TrapIDMap.find(ID);
5240   return Iterator != TrapIDMap.end() ? Iterator->second : 0;
5241 }
5242 
operator <<(outputfile & SaveFile,const configid & Value)5243 outputfile& operator<<(outputfile& SaveFile, const configid& Value)
5244 {
5245   SaveFile.Write(reinterpret_cast<cchar*>(&Value), sizeof(Value));
5246   return SaveFile;
5247 }
5248 
operator >>(inputfile & SaveFile,configid & Value)5249 inputfile& operator>>(inputfile& SaveFile, configid& Value)
5250 {
5251   SaveFile.Read(reinterpret_cast<char*>(&Value), sizeof(Value));
5252   return SaveFile;
5253 }
5254 
operator <<(outputfile & SaveFile,const dangerid & Value)5255 outputfile& operator<<(outputfile& SaveFile, const dangerid& Value)
5256 {
5257   SaveFile << Value.NakedDanger << Value.EquippedDanger;
5258   return SaveFile;
5259 }
5260 
operator >>(inputfile & SaveFile,dangerid & Value)5261 inputfile& operator>>(inputfile& SaveFile, dangerid& Value)
5262 {
5263   SaveFile >> Value.NakedDanger >> Value.EquippedDanger;
5264   return SaveFile;
5265 }
5266 
5267 /* The program can only create directories to the deepness of one, no more... */
5268 
GetDataDir()5269 festring game::GetDataDir()
5270 {
5271 #ifdef UNIX
5272 #ifdef MAC_APP
5273   return "../Resources/data/";
5274 #else
5275   return DATADIR "/ivan/";
5276 #endif
5277 #endif
5278 
5279 #if defined(WIN32) || defined(__DJGPP__)
5280   return GetUserDataDir();
5281 #endif
5282 }
5283 
GetSaveDir()5284 festring game::GetSaveDir()
5285 {
5286   return GetUserDataDir() + "Save/";
5287 }
5288 
GetScrshotDir()5289 festring game::GetScrshotDir()
5290 {
5291   return GetUserDataDir() + "Scrshot/";
5292 }
5293 
GetBoneDir()5294 festring game::GetBoneDir()
5295 {
5296   return GetUserDataDir() + "Bones/";
5297 }
5298 
GetMusicDir()5299 festring game::GetMusicDir()
5300 {
5301   return GetDataDir() + "Music/";
5302 }
5303 
GetLevel(int I)5304 level* game::GetLevel(int I)
5305 {
5306   return GetCurrentDungeon()->GetLevel(I);
5307 }
5308 
GetLevels()5309 int game::GetLevels()
5310 {
5311   return GetCurrentDungeon()->GetLevels();
5312 }
5313 
SignalDeath(ccharacter * Ghost,ccharacter * Murderer,festring DeathMsg)5314 void game::SignalDeath(ccharacter* Ghost, ccharacter* Murderer, festring DeathMsg)
5315 {
5316   if(InWilderness)
5317     DeathMsg << " in the wilderness";
5318   else
5319     DeathMsg << " in " << GetCurrentDungeon()->GetLevelDescription(CurrentLevelIndex);
5320 
5321   massacremap* MassacreMap;
5322 
5323   if(!Murderer)
5324   {
5325     ++MiscMassacreAmount;
5326     MassacreMap = &MiscMassacreMap;
5327   }
5328   else if(Murderer->IsPlayer())
5329   {
5330     ++PlayerMassacreAmount;
5331     MassacreMap = &PlayerMassacreMap;
5332   }
5333   else if(Murderer->IsPet())
5334   {
5335     ++PetMassacreAmount;
5336     MassacreMap = &PetMassacreMap;
5337   }
5338   else
5339   {
5340     ++MiscMassacreAmount;
5341     MassacreMap = &MiscMassacreMap;
5342   }
5343 
5344   massacreid MI(Ghost->GetType(), Ghost->GetConfig(), Ghost->GetAssignedName());
5345   massacremap::iterator i = MassacreMap->find(MI);
5346 
5347   if(i == MassacreMap->end())
5348   {
5349     i = MassacreMap->insert(std::make_pair(MI, killdata(1, Ghost->GetGenerationDanger()))).first;
5350     i->second.Reason.push_back(killreason(DeathMsg, 1));
5351   }
5352   else
5353   {
5354     ++i->second.Amount;
5355     i->second.DangerSum += Ghost->GetGenerationDanger();
5356     std::vector<killreason>& Reason = i->second.Reason;
5357     uint c;
5358 
5359     for(c = 0; c < Reason.size(); ++c)
5360       if(Reason[c].String == DeathMsg)
5361       {
5362         ++Reason[c].Amount;
5363         break;
5364       }
5365 
5366     if(c == Reason.size())
5367       Reason.push_back(killreason(DeathMsg, 1));
5368   }
5369 }
5370 
DisplayMassacreLists()5371 void game::DisplayMassacreLists()
5372 {
5373   game::RegionListItemEnable(true);
5374 
5375   DisplayMassacreList(PlayerMassacreMap, "directly by you.", PlayerMassacreAmount);
5376   DisplayMassacreList(PetMassacreMap, "by your allies.", PetMassacreAmount);
5377   DisplayMassacreList(MiscMassacreMap, "by some other reason.", MiscMassacreAmount);
5378 
5379   game::RegionListItemEnable(false);
5380 }
5381 
5382 struct massacresetentry
5383 {
operator <massacresetentry5384   bool operator<(const massacresetentry& MSE) const
5385   {
5386     return festring::IgnoreCaseCompare(Key, MSE.Key);
5387   }
5388   festring Key;
5389   festring String;
5390   std::vector<festring> Details;
5391   int ImageKey;
5392 };
5393 
DisplayMassacreList(const massacremap & MassacreMap,cchar * Reason,long Amount)5394 void game::DisplayMassacreList(const massacremap& MassacreMap, cchar* Reason, long Amount)
5395 {
5396   std::set<massacresetentry> MassacreSet;
5397   festring FirstPronoun;
5398   truth First = true;
5399   charactervector GraveYard;
5400 
5401   for(const massacremap::value_type& p : MassacreMap)
5402   {
5403     character* Victim = protocontainer<character>::GetProto(p.first.Type)->Spawn(p.first.Config);
5404     Victim->SetAssignedName(p.first.Name);
5405     massacresetentry Entry;
5406     GraveYard.push_back(Victim);
5407     Entry.ImageKey = AddToCharacterDrawVector(Victim);
5408 
5409     if(p.second.Amount == 1)
5410     {
5411       Victim->AddName(Entry.Key, UNARTICLED);
5412       Victim->AddName(Entry.String, INDEFINITE);
5413     }
5414     else
5415     {
5416       Victim->AddName(Entry.Key, PLURAL);
5417       Entry.String << p.second.Amount << ' ' << Entry.Key;
5418     }
5419 
5420     if(First)
5421     {
5422       FirstPronoun = Victim->GetSex() == UNDEFINED ? "it" : Victim->GetSex() == MALE ? "he" : "she";
5423       First = false;
5424     }
5425 
5426     const std::vector<killreason>& Reason = p.second.Reason;
5427     std::vector<festring>& Details = Entry.Details;
5428 
5429     if(Reason.size() == 1)
5430     {
5431       festring Begin;
5432 
5433       if(Reason[0].Amount == 1)
5434         Begin = "";
5435       else if(Reason[0].Amount == 2)
5436         Begin = "both ";
5437       else
5438         Begin = "all ";
5439 
5440       Details.push_back(Begin + Reason[0].String);
5441     }
5442     else
5443     {
5444       for(uint c = 0; c < Reason.size(); ++c)
5445         Details.push_back(CONST_S("") + Reason[c].Amount + ' ' + Reason[c].String);
5446 
5447       std::sort(Details.begin(), Details.end(), ignorecaseorderer());
5448     }
5449 
5450     MassacreSet.insert(Entry);
5451   }
5452 
5453   long Total = PlayerMassacreAmount + PetMassacreAmount + MiscMassacreAmount;
5454   festring MainTopic;
5455 
5456   if(Total == 1)
5457     MainTopic << "One creature perished during your adventure.";
5458   else
5459     MainTopic << Total << " creatures perished during your adventure.";
5460 
5461   felist List(MainTopic);
5462   SetStandardListAttributes(List);
5463   List.SetPageLength(15);
5464   List.AddFlags(SELECTABLE);
5465   List.SetEntryDrawer(CharacterEntryDrawer);
5466   List.AddDescription(CONST_S(""));
5467   festring SideTopic;
5468 
5469   if(Amount != Total)
5470   {
5471     SideTopic = CONST_S("The following ");
5472 
5473     if(Amount == 1)
5474       SideTopic << "one was killed " << Reason;
5475     else
5476       SideTopic << Amount << " were killed " << Reason;
5477   }
5478   else
5479   {
5480     if(Amount == 1)
5481     {
5482       FirstPronoun.Capitalize();
5483       SideTopic << FirstPronoun << " was killed " << Reason;
5484     }
5485     else
5486       SideTopic << "They were all killed " << Reason;
5487   }
5488 
5489   List.AddDescription(SideTopic);
5490   List.AddDescription(CONST_S(""));
5491   List.AddDescription("Choose a type of creatures to browse death details.");
5492 
5493   for(const massacresetentry& MSE : MassacreSet)
5494     List.AddEntry(MSE.String, LIGHT_GRAY, 0, MSE.ImageKey);
5495 
5496   for(;;)
5497   {
5498     int Chosen = List.Draw();
5499 
5500     if(Chosen & FELIST_ERROR_BIT)
5501       break;
5502 
5503     felist SubList(CONST_S("Massacre details"));
5504     SetStandardListAttributes(SubList);
5505     SubList.SetPageLength(20);
5506     int Counter = 0;
5507 
5508     for(const massacresetentry& MSE : MassacreSet)
5509     {
5510       if(Counter == Chosen)
5511       {
5512         for(uint c = 0; c < MSE.Details.size(); ++c)
5513           SubList.AddEntry(MSE.Details[c], LIGHT_GRAY);
5514 
5515         break;
5516       }
5517       ++Counter;
5518     }
5519 
5520     SubList.Draw();
5521   }
5522 
5523   ClearCharacterDrawVector();
5524 
5525   for(uint c = 0; c < GraveYard.size(); ++c)
5526     delete GraveYard[c];
5527 }
5528 
MassacreListsEmpty()5529 truth game::MassacreListsEmpty()
5530 {
5531   return PlayerMassacreMap.empty() && PetMassacreMap.empty() && MiscMassacreMap.empty();
5532 }
5533 
5534 #ifdef WIZARD
5535 
AutoPlayModeApply()5536 void game::AutoPlayModeApply(){
5537   int iTimeout=0;
5538   bool bPlayInBackground=false;
5539 
5540   const char* msg;
5541   switch(game::AutoPlayMode){
5542   case 0:
5543     // disabled
5544     msg="%s says \"I can rest now.\"";
5545     break;
5546   case 1:
5547     // no timeout, user needs to hit '.' to it autoplay once, the behavior is controled by AutoPlayMode AND the timeout delay that if 0 will have no timeout but will still autoplay.
5548     msg="%s says \"I won't rest!\"";
5549     break;
5550   case 2: // TIMEOUTs key press from here to below
5551     msg="%s says \"I can't wait anymore!\"";
5552     iTimeout=(1000);
5553     bPlayInBackground=true;
5554     break;
5555   case 3:
5556     msg="%s says \"I am in a hurry!\"";
5557     iTimeout=(1000/2);
5558     bPlayInBackground=true;
5559     break;
5560   case 4:
5561     msg="%s says \"I... *frenzy* yeah! Try to follow me now! Hahaha!\"";
5562     iTimeout=10;//min possible to be fastest //(1000/10); // like 10 FPS, so user has 100ms chance to disable it
5563     bPlayInBackground=true;
5564     break;
5565   }
5566   ADD_MESSAGE(msg, game::GetPlayer()->CHAR_NAME(DEFINITE));
5567 
5568   globalwindowhandler::SetPlayInBackground(bPlayInBackground);
5569 
5570   if(!ivanconfig::IsXBRZScale()){
5571     /**
5572      * TODO
5573      * This is an horrible gum solution...
5574      * I still have no idea why this happens.
5575      * Autoplay will timeout 2 times slower if xBRZ is disabled! why!??!?!?
5576      * But the debug log shows the correct timeouts :(, clueless for now...
5577      */
5578     iTimeout/=2;
5579   }
5580 
5581   globalwindowhandler::SetKeyTimeout(iTimeout,'.');//,'~');
5582 }
5583 
IncAutoPlayMode()5584 void game::IncAutoPlayMode() {
5585 //  if(!globalwindowhandler::IsKeyTimeoutEnabled()){
5586 //    if(AutoPlayMode>=2){
5587 //      AutoPlayMode=0; // TIMEOUT was disabled there at window handler! so reset here.
5588 //      AutoPlayModeApply();
5589 //    }
5590 //  }
5591 
5592   ++AutoPlayMode;
5593   if(AutoPlayMode>4)AutoPlayMode=0;
5594 
5595   AutoPlayModeApply();
5596 }
5597 
SeeWholeMap()5598 void game::SeeWholeMap()
5599 {
5600   if(SeeWholeMapCheatMode < 2)
5601     ++SeeWholeMapCheatMode;
5602   else
5603     SeeWholeMapCheatMode = 0;
5604 
5605   GetCurrentArea()->SendNewDrawRequest();
5606 }
5607 
5608 #endif
5609 
CreateBone()5610 void game::CreateBone()
5611 {
5612   if(!WizardModeIsActive() && !IsInWilderness() && RAND() & 3 && GetCurrentLevel()->PreProcessForBone())
5613   {
5614     int BoneIndex;
5615     festring BoneName;
5616 
5617     for(BoneIndex = 0; BoneIndex < 1000; ++BoneIndex)
5618     {
5619       BoneName = GetBoneDir() + "bon" + CurrentDungeonIndex + CurrentLevelIndex + BoneIndex;
5620       inputfile BoneFile(BoneName, 0, false);
5621 
5622       if(!BoneFile.IsOpen())
5623         break;
5624     }
5625 
5626     if(BoneIndex != 1000)
5627     {
5628       festring BoneName = GetBoneDir() + "bon" + CurrentDungeonIndex + CurrentLevelIndex + BoneIndex;
5629       outputfile BoneFile(BoneName);
5630       BoneFile << BONE_FILE_VERSION << PlayerName << CurrentLevel;
5631     }
5632   }
5633 }
5634 
PrepareRandomBone(int LevelIndex)5635 truth game::PrepareRandomBone(int LevelIndex)
5636 {
5637   if(WizardModeIsActive()
5638      || GetCurrentDungeon()->IsGenerated(LevelIndex)
5639      || !*GetCurrentDungeon()->GetLevelScript(LevelIndex)->CanGenerateBone())
5640     return false;
5641 
5642   int BoneIndex;
5643   festring BoneName;
5644 
5645   for(BoneIndex = 0; BoneIndex < 1000; ++BoneIndex)
5646   {
5647     BoneName = GetBoneDir() + "bon" + CurrentDungeonIndex + LevelIndex + BoneIndex;
5648     inputfile BoneFile(BoneName, 0, false); DBG1(BoneFile.GetFileName().CStr());
5649 
5650     if(BoneFile.IsOpen() && !(RAND() & 7))
5651     {
5652       if(ReadType<int>(BoneFile) != BONE_FILE_VERSION)
5653       {
5654         BoneFile.Close();
5655         remove(BoneName.CStr());
5656         continue;
5657       }
5658 
5659       festring Name;
5660       BoneFile >> Name;
5661       level* NewLevel = GetCurrentDungeon()->LoadLevel(BoneFile, LevelIndex);
5662 
5663       if(!NewLevel->PostProcessForBone())
5664       {
5665         delete NewLevel;
5666         GetBoneItemIDMap().clear();
5667         GetBoneCharacterIDMap().clear();
5668         continue;
5669       }
5670 
5671       NewLevel->FinalProcessForBone();
5672       GetBoneItemIDMap().clear();
5673       GetBoneCharacterIDMap().clear();
5674       SetCurrentArea(NewLevel);
5675       CurrentLevel = NewLevel;
5676       CurrentLSquareMap = NewLevel->GetMap();
5677       GetCurrentDungeon()->SetIsGenerated(LevelIndex, true);
5678 
5679       if(Name == PlayerName)
5680         ADD_MESSAGE("This place is oddly familiar. Like you had been here in one of your past lives.");
5681       else if(Player && Player->StateIsActivated(GAS_IMMUNITY))
5682         ADD_MESSAGE("You feel the cool breeze of death.");
5683                         else
5684         ADD_MESSAGE("You smell the stench of death.");
5685 
5686       break;
5687     }
5688   }
5689 
5690   Generating = true;
5691 
5692   if(BoneIndex != 1000)
5693   {
5694     remove(BoneName.CStr());
5695     return true;
5696   }
5697   else
5698     return false;
5699 }
5700 
CalculateAverageDanger(const charactervector & EnemyVector,character * Char)5701 double game::CalculateAverageDanger(const charactervector& EnemyVector, character* Char)
5702 {
5703   double DangerSum = 0;
5704   int Enemies = 0;
5705 
5706   for(uint c = 0; c < EnemyVector.size(); ++c)
5707   {
5708     DangerSum += EnemyVector[c]->GetRelativeDanger(Char, true);
5709     ++Enemies;
5710   }
5711 
5712   return DangerSum / Enemies;
5713 }
5714 
CalculateAverageDangerOfAllNormalEnemies()5715 double game::CalculateAverageDangerOfAllNormalEnemies()
5716 {
5717   double DangerSum = 0;
5718   int Enemies = 0;
5719 
5720   for(int c1 = 1; c1 < protocontainer<character>::GetSize(); ++c1)
5721   {
5722     const character::prototype* Proto = protocontainer<character>::GetProto(c1);
5723     const character::database*const* ConfigData = Proto->GetConfigData();
5724     int ConfigSize = Proto->GetConfigSize();
5725 
5726     for(int c2 = 0; c2 < ConfigSize; ++c2)
5727       if(!ConfigData[c2]->IsAbstract
5728          && !ConfigData[c2]->IsUnique
5729          && ConfigData[c2]->CanBeGenerated)
5730       {
5731         DangerSum += DangerMap.find(configid(c1, ConfigData[c2]->Config))->second.EquippedDanger;
5732         ++Enemies;
5733       }
5734   }
5735 
5736   return DangerSum / Enemies;
5737 }
5738 
CreateGhost()5739 bonesghost* game::CreateGhost()
5740 {
5741   double AverageDanger = CalculateAverageDangerOfAllNormalEnemies();
5742   charactervector EnemyVector;
5743   protosystem::CreateEveryNormalEnemy(EnemyVector);
5744   bonesghost* Ghost = bonesghost::Spawn();
5745   Ghost->SetTeam(GetTeam(MONSTER_TEAM));
5746   Ghost->SetGenerationDanger(CurrentLevel->GetDifficulty());
5747   Ghost->SetOwnerSoul(PlayerName);
5748   Ghost->SetIsActive(false);
5749   Ghost->EditAllAttributes(-4);
5750   Player->SetSoulID(Ghost->GetID());
5751   while(CalculateAverageDanger(EnemyVector, Ghost) > AverageDanger && Ghost->EditAllAttributes(1));
5752 
5753   for(uint c = 0; c < EnemyVector.size(); ++c)
5754     delete EnemyVector[c];
5755 
5756   return Ghost;
5757 }
5758 
HexToInt(festring fs)5759 int HexToInt(festring fs)
5760 {
5761   std::string str = fs.CStr();
5762   std::string strHex = str.substr(str.size() -1 -4); // -1 is "\n"
5763   int iVal=0;
5764   sscanf(strHex.c_str(),"%x",&iVal);
5765   return iVal;
5766 }
5767 
LoadCustomCommandKeys()5768 void game::LoadCustomCommandKeys()
5769 {
5770   static festring fsFile = GetUserDataDir() + CUSTOM_KEYS_FILENAME;
5771   FILE *fl = fopen(fsFile.CStr(), "rt");
5772   if(!fl)return;
5773 
5774   festring Line;
5775   festring fsMatch;
5776   festring fsPostFix="\"=0x";
5777   static const int iBuffSz=0xFF;
5778   char str[iBuffSz];
5779   while(fgets(str, iBuffSz, fl)){
5780     Line=str;
5781     command* cmd;
5782     for(int c = 1; (cmd=commandsystem::GetCommand(c)); ++c){
5783       fsMatch.Empty();
5784       fsMatch<<cmd->GetDescription()<<fsPostFix;
5785       if(Line.Find(fsMatch)==1){ // after "
5786         cmd->SetCustomKey(HexToInt(Line));
5787         break;
5788       }
5789     }
5790 
5791     int iVal;
5792     for(int c=0;c<8;c++){
5793       fsMatch.Empty();
5794       fsMatch<<GetMoveKeyDesc(c)<<fsPostFix;
5795       if(Line.Find(fsMatch)==1){ // after "
5796         game::MoveCustomCommandKey[c]=HexToInt(Line);
5797         break;
5798       }
5799     }
5800   }
5801 
5802   fclose(fl);
5803 }
5804 
GetMoveKeyDesc(int i)5805 festring game::GetMoveKeyDesc(int i)
5806 {
5807   switch(i){
5808     case 0: return "MoveKey Upper Left";
5809     case 1: return "MoveKey Up";
5810     case 2: return "MoveKey Upper Right";
5811     case 3: return "MoveKey Left";
5812     case 4: return "MoveKey Right";
5813     case 5: return "MoveKey Lower Left";
5814     case 6: return "MoveKey Down";
5815     case 7: return "MoveKey Lower Right";
5816     //case 8: return "MoveKey Stop"; //skip the last to keep as '.'
5817     default: ABORT("invalid move key index %d",i);
5818   }
5819   return ""; //dummy
5820 }
5821 
ValidateCustomCmdKey(int iNewKey,int iIgnoreIndex,bool bMoveKeys)5822 truth game::ValidateCustomCmdKey(int iNewKey, int iIgnoreIndex, bool bMoveKeys)
5823 {
5824   //TODO these SYSTEM messages messes the gameplay message log... but is better than a popup?
5825   bool bValid=true;
5826   festring fsDesc;
5827 
5828   // conflicts check
5829   if(bValid){
5830     command *cmd;
5831     for(int c = 1; (cmd=commandsystem::GetCommand(c)); ++c){
5832       if(!bMoveKeys && c==iIgnoreIndex)continue;
5833       if(iNewKey==cmd->GetKey()){
5834         fsDesc=cmd->GetDescription();
5835         bValid=false;
5836         break;
5837       }
5838     }
5839   }
5840   if(bValid){
5841     for(int c=0;c<8;c++){
5842       if(bMoveKeys && c==iIgnoreIndex)continue;
5843       if(iNewKey == game::MoveCustomCommandKey[c]){
5844         fsDesc=GetMoveKeyDesc(c).CStr();
5845         bValid=false;
5846         break;
5847       }
5848     }
5849   }
5850   if(!bValid){
5851     ADD_MESSAGE("SYSTEM: conflicting key '%s'(code is %d or 0x%04X) with command \"%s\", retry...",
5852       ToCharIfPossible(iNewKey).CStr(),iNewKey,iNewKey,fsDesc.CStr());
5853   }
5854 
5855   // general invalid key codes
5856   if(bValid){
5857     if(iNewKey<0x20){
5858       ADD_MESSAGE("SYSTEM: invalid key code 0x%04X, retry...",iNewKey);
5859       bValid=false;
5860     }
5861   }
5862 
5863   return bValid;
5864 }
5865 
IntToHexStr(int i)5866 festring IntToHexStr(int i)
5867 {
5868   static char hexbuf[100];
5869   sprintf(hexbuf, "0x%04X", i);
5870   festring fs;fs=hexbuf;
5871   return fs;
5872 }
5873 
ToCharIfPossible(int i)5874 festring game::ToCharIfPossible(int i)
5875 {
5876   switch(i){ // these are above 0xFF
5877     //TODO complete this list, if has no #define, use the hexa directly.
5878     case KEY_UP:
5879       return "Up";
5880     case KEY_DOWN:
5881       return "Down";
5882     case KEY_RIGHT:
5883       return "Right";
5884     case KEY_LEFT:
5885       return "Left";
5886     case KEY_HOME:
5887       return "Home";
5888     case KEY_END:
5889       return "End";
5890     case KEY_PAGE_DOWN:
5891       return "PgDn";
5892     case KEY_PAGE_UP:
5893       return "PgUp";
5894     case KEY_DELETE:
5895       return "Del";
5896     case KEY_INSERT:
5897       return "Ins";
5898   }
5899 
5900   if(i>=0 && i<=0xFF) //these are mapped at fonts gfx files
5901     return festring()+(char)i;
5902 
5903   return IntToHexStr(i);
5904 }
5905 
WriteCustomKeyBindingsCfgFile(FILE * fl,festring fsDesc,int iKey)5906 void WriteCustomKeyBindingsCfgFile(FILE *fl,festring fsDesc,int iKey){
5907   fprintf(fl, "\"%s\"=0x%04X\n", fsDesc.CStr(), iKey);
5908   fflush(fl);
5909 }
5910 
5911 /**
5912  * Command's (and movement keys) descriptions are used as identifiers at the config file.
5913  * These descriptions shall not clash and preferably should not be changed.
5914  * @return
5915  */
ConfigureCustomKeys()5916 truth game::ConfigureCustomKeys()
5917 {
5918   game::LoadCustomCommandKeys(); //in case there is anything already set
5919 
5920   felist fel(CONST_S("Configure custom keys:"));
5921   bool bRet=true;
5922   command* cmd;
5923   while(true){
5924     fel.Empty();
5925     int iMoveKeyStart=0;
5926     bool bWizIni=false;
5927     festring fsEntry;
5928     for(int c = 1; (cmd=commandsystem::GetCommand(c)); ++c){
5929       fsEntry=cmd->GetDescription();
5930       fsEntry.Resize(60);
5931       fsEntry<<"'"<<ToCharIfPossible(cmd->GetKey())<<"' ";
5932       fsEntry<<IntToHexStr(cmd->GetKey());
5933 
5934       if(!bWizIni && cmd->IsWizardModeFunction()){
5935         fel.AddEntry("Wizard mode keys:", DARK_GRAY, 20, NO_IMAGE, false);
5936         bWizIni=true;
5937       }
5938 
5939       fel.AddEntry(fsEntry, LIGHT_GRAY, 0, NO_IMAGE, true);
5940       iMoveKeyStart++;
5941     }
5942     fel.AddEntry("Movement keys:", DARK_GRAY, 20, NO_IMAGE, false);
5943     for(int c=0;c<8;c++){
5944       fsEntry=GetMoveKeyDesc(c);
5945       fsEntry.Resize(60);
5946       fsEntry<<"'"<<ToCharIfPossible(game::MoveCustomCommandKey[c])<<"' ";
5947       fsEntry<<IntToHexStr(game::MoveCustomCommandKey[c]);
5948       fel.AddEntry(fsEntry, LIGHT_GRAY, 0, NO_IMAGE, true);
5949     }
5950 
5951     game::SetStandardListAttributes(fel);
5952     fel.AddFlags(SELECTABLE);
5953     uint Select = fel.Draw();
5954     if(Select==ESCAPED || Select==NOTHING_SELECTED || Select==LIST_WAS_EMPTY){
5955       bRet=false;
5956       break;
5957     }
5958 
5959     bool bIsMoveKeys = Select >= iMoveKeyStart;
5960     int iMvKeyIndex = bIsMoveKeys ? Select-iMoveKeyStart : -1;
5961     int iCmdKeyIndex = bIsMoveKeys ? -1 : Select+1;
5962     int iNewKey;
5963     bool bIgnore=false;
5964     festring fsC = " (currently is set to '";
5965     while(true){
5966       cmd=NULL;
5967       festring fsAsk = "Press a key to assign to the command \"";
5968 
5969       if(bIsMoveKeys){
5970         fsAsk<<GetMoveKeyDesc(iMvKeyIndex)<<"\"";
5971         fsAsk<<fsC<<ToCharIfPossible(game::MoveCustomCommandKey[iMvKeyIndex]);
5972       }else{
5973         cmd = commandsystem::GetCommand(iCmdKeyIndex); //commands begin at index 1
5974         fsAsk<<cmd->GetDescription()<<"\"";
5975         fsAsk<<fsC<<ToCharIfPossible(cmd->GetKey());
5976       }
5977       fsAsk<<"' ";
5978       fsAsk<<IntToHexStr( bIsMoveKeys ? game::MoveCustomCommandKey[iMvKeyIndex] : cmd->GetKey());
5979       fsAsk<<")";
5980 
5981       iNewKey=game::AskForKeyPress(fsAsk);
5982       if(iNewKey==KEY_ESC){bIgnore=true;break;}
5983 
5984       if(bIsMoveKeys){
5985         if(ValidateCustomCmdKey(iNewKey,iMvKeyIndex,true))
5986           break;
5987       }else{
5988         if(ValidateCustomCmdKey(iNewKey,iCmdKeyIndex,false))
5989           break;
5990       }
5991     }
5992     if(bIgnore)continue;
5993 
5994     if(!bRet)break;
5995 
5996     if(bIsMoveKeys)
5997       game::MoveCustomCommandKey[iMvKeyIndex]=iNewKey;
5998     else
5999       commandsystem::GetCommand(iCmdKeyIndex)->SetCustomKey(iNewKey);
6000   }
6001 
6002   festring fsFl = GetUserDataDir() + CUSTOM_KEYS_FILENAME;
6003 
6004   // backup existing
6005   festring fsFlBkp=fsFl+".bkp";
6006   std::ifstream  src(fsFl.CStr()   , std::ios::binary);
6007   std::ofstream  dst(fsFlBkp.CStr(), std::ios::binary);
6008   dst << src.rdbuf();
6009 
6010   // write a new in full
6011   FILE *fl = fopen(fsFl.CStr(), "wt"); //"a");
6012   festring fsWriteLine;
6013   int iNewKey;
6014   for(int c = 1; (cmd=commandsystem::GetCommand(c)); ++c)
6015     WriteCustomKeyBindingsCfgFile(fl,cmd->GetDescription(),cmd->GetKey());
6016   for(int c=0;c<8;c++)
6017     WriteCustomKeyBindingsCfgFile(fl,GetMoveKeyDesc(c),game::MoveCustomCommandKey[c]);
6018   fclose(fl);
6019 
6020   if(bRet)game::LoadCustomCommandKeys(); //this here is more to validate if all went ok
6021   return bRet;
6022 }
6023 
6024 
6025 /**
6026  * check all other command keys versus MOVEMENT command keys on their specific branch
6027  */
ValidateCommandKeys(char Key1,char Key2,char Key3)6028 void game::ValidateCommandKeys(char Key1,char Key2,char Key3)
6029 {
6030   static const int iTot=9;
6031   for(int i=0;i<3;i++){
6032 //    static cint a[iTot]={0,0,0,0,0,0,0,0,0};
6033     static cint* pa;
6034     int Key = -1;
6035 
6036     switch(i){
6037     case DIR_NORM:
6038       pa=game::MoveNormalCommandKey; Key=Key1; break;
6039     case DIR_ALT:
6040       pa=game::MoveAbnormalCommandKey; Key=Key2; break;
6041     case DIR_HACK:
6042       pa=game::MoveNetHackCommandKey; Key=Key3; break;
6043 /*TODO case DIR_CUSTOM:
6044          pa=game::MoveCustomCommandKey; Key=???; break; */
6045     }
6046 
6047     for(int j=0;j<iTot;j++){
6048       if(Key==pa[j] && Key!='.'){
6049         char ac[2]={(char)pa[j],0};
6050         ABORT("conflicting command keys %d %d %d vs %d@%d '%s'",Key1,Key2,Key3,pa[j],i,ac);
6051       }
6052     }
6053   }
6054 }
6055 
GetMoveCommandKey(int I)6056 int game::GetMoveCommandKey(int I)
6057 {
6058   if(ivanconfig::IsSetupCustomKeys())
6059     return MoveCustomCommandKey[I];
6060 
6061   switch(ivanconfig::GetDirectionKeyMap())
6062   {
6063   case DIR_NORM:
6064     return MoveNormalCommandKey[I];
6065   case DIR_ALT:
6066     return MoveAbnormalCommandKey[I];
6067   case DIR_HACK:
6068     return MoveNetHackCommandKey[I];
6069   default:
6070     ABORT("This is not Emacs!");
6071     return MoveNormalCommandKey[I];
6072   }
6073 }
6074 
GetScore()6075 long game::GetScore()
6076 {
6077   double Counter = 0;
6078   massacremap SumMap = PlayerMassacreMap;
6079 
6080   for(const massacremap::value_type& p : PetMassacreMap)
6081   {
6082     killdata& KillData = SumMap[p.first];
6083     KillData.Amount += p.second.Amount;
6084     KillData.DangerSum += p.second.DangerSum;
6085   }
6086 
6087   for(const massacremap::value_type& p : SumMap)
6088   {
6089     character* Char = protocontainer<character>::GetProto(p.first.Type)->Spawn(p.first.Config);
6090     int SumOfAttributes = Char->GetSumOfAttributes();
6091     Counter += sqrt(p.second.DangerSum / DEFAULT_GENERATION_DANGER) * SumOfAttributes * SumOfAttributes;
6092     delete Char;
6093   }
6094 
6095   return long(0.01 * Counter);
6096 }
6097 
6098 /* Only works if New Attnam is loaded */
6099 
TweraifIsFree()6100 truth game::TweraifIsFree()
6101 {
6102   for(character* p : GetTeam(COLONIST_TEAM)->GetMember())
6103     if(p->IsEnabled())
6104       return false;
6105 
6106   return true;
6107 }
6108 
IsXMas()6109 truth game::IsXMas() // returns true if date is christmaseve or day
6110 {
6111   time_t Time = time(0);
6112   struct tm* TM = localtime(&Time);
6113   return (TM->tm_mon == 11 && (TM->tm_mday == 24 || TM->tm_mday == 25));
6114 }
6115 
AddToItemDrawVector(const itemvector & What)6116 int game::AddToItemDrawVector(const itemvector& What)
6117 {
6118   ItemDrawVector.push_back(What);
6119   return ItemDrawVector.size() - 1;
6120 }
6121 
6122 v2 ItemDisplacement[3][3] =
6123 {
6124   { v2(0, 0), ERROR_V2, ERROR_V2 },
6125   { v2(-2, -2), v2(2, 2), ERROR_V2 },
6126   { v2(-4, -4), v2(0, 0), v2(4, 4) }
6127 };
6128 
ItemEntryDrawer(bitmap * Bitmap,v2 Pos,uint I)6129 void game::ItemEntryDrawer(bitmap* Bitmap, v2 Pos, uint I)
6130 { DBG3(DBGAV2(Bitmap->GetSize()),DBGAV2(Pos),I);
6131   blitdata B = { Bitmap,
6132                  { 0, 0 },
6133                  { 0, 0 },
6134                  { TILE_SIZE, TILE_SIZE },
6135                  { NORMAL_LUMINANCE },
6136                  TRANSPARENT_COLOR,
6137                  ALLOW_ANIMATE }; DBGLN;
6138 
6139   itemvector ItemVector = ItemDrawVector[I]; DBGLN;
6140   int Amount = Min<int>(ItemVector.size(), 3); DBGLN;
6141 
6142   for(int c = 0; c < Amount; ++c)
6143   { DBGLN;
6144     v2 Displacement = ItemDisplacement[Amount - 1][c];
6145 
6146     if(!ItemVector[0]->HasNormalPictureDirection())
6147       Displacement.X = -Displacement.X;
6148 
6149     B.Dest = Pos + Displacement;
6150 
6151     if(ItemVector[c]->AllowAlphaEverywhere())
6152       B.CustomData |= ALLOW_ALPHA;
6153 
6154     ItemVector[c]->Draw(B);
6155     B.CustomData &= ~ALLOW_ALPHA;
6156   }
6157 
6158   if(ItemVector.size() > 3)
6159   { DBGLN;
6160     B.Src.X = 0;
6161     B.Src.Y = 16;
6162     B.Dest = ItemVector[0]->HasNormalPictureDirection() ? Pos + v2(11, -2) : Pos + v2(-2, -2);
6163     B.Flags = 0;
6164     igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
6165   }
6166 }
6167 
AddToCharacterDrawVector(character * What)6168 int game::AddToCharacterDrawVector(character* What)
6169 {
6170   CharacterDrawVector.push_back(What);
6171   return CharacterDrawVector.size() - 1;
6172 }
6173 
CharacterEntryDrawer(bitmap * Bitmap,v2 Pos,uint I)6174 void game::CharacterEntryDrawer(bitmap* Bitmap, v2 Pos, uint I)
6175 {
6176   if(CharacterDrawVector[I])
6177   {
6178     blitdata B = { Bitmap,
6179                    { 0, 0 },
6180                    { Pos.X, Pos.Y },
6181                    { TILE_SIZE, TILE_SIZE },
6182                    { NORMAL_LUMINANCE },
6183                    TRANSPARENT_COLOR,
6184                    ALLOW_ANIMATE|ALLOW_ALPHA };
6185 
6186     CharacterDrawVector[I]->DrawBodyParts(B);
6187   }
6188 }
6189 
GodEntryDrawer(bitmap * Bitmap,v2 Pos,uint I)6190 void game::GodEntryDrawer(bitmap* Bitmap, v2 Pos, uint I)
6191 {
6192   blitdata B = { Bitmap,
6193                  { static_cast<int>(I << 4), 0 },
6194                  { Pos.X, Pos.Y },
6195                  { TILE_SIZE, TILE_SIZE },
6196                  { 0 },
6197                  TRANSPARENT_COLOR,
6198                  0 };
6199 
6200   igraph::GetSymbolGraphic()->NormalMaskedBlit(B);
6201 }
6202 
GetSumo()6203 character* game::GetSumo()
6204 {
6205   return GetCurrentLevel()->GetLSquare(SUMO_ROOM_POS)->GetRoom()->GetMaster();
6206 }
6207 
TryToEnterSumoArena()6208 truth game::TryToEnterSumoArena()
6209 {
6210   character* Sumo = GetSumo();
6211 
6212   if(!Sumo || !Sumo->IsEnabled() || Sumo->GetRelation(Player) == HOSTILE || !Player->CanBeSeenBy(Sumo))
6213     return true;
6214 
6215   if(TweraifIsFree())
6216   {
6217     ADD_MESSAGE("\"You started this stupid revolution, after which I've been constantly hungry. Get lost!\"");
6218     return false;
6219   }
6220 
6221   if(PlayerIsSumoChampion())
6222   {
6223     ADD_MESSAGE("\"I don't really enjoy losing, especially many times to the same guy. Go away.\"");
6224     return false;
6225   }
6226 
6227   if(Player->IsPolymorphed())
6228   {
6229     ADD_MESSAGE("\"Don't try to cheat. Come back when you're normal again.\"");
6230     return false;
6231   }
6232 
6233   if(Player->GetHungerState() < SATIATED)
6234   {
6235     ADD_MESSAGE("\"Your figure is too slender for this sport. Eat a lot more and come back.\"");
6236     return false;
6237   }
6238 
6239   if(Player->GetHungerState() < BLOATED)
6240   {
6241     ADD_MESSAGE("\"You're still somewhat too thin. Eat some more and we'll compete.\"");
6242     return false;
6243   }
6244 
6245   ADD_MESSAGE("\"So you want to compete? Okay, I'll explain the rules. First, I'll make a mirror image out of us "
6246               "both. We'll enter the arena and fight till one is knocked out. Use of any equipment is not allowed. "
6247               "Note that we will not gain experience from fighting as a mirror image, but won't get really hurt, "
6248               "either. However, controlling the image is exhausting and you can get hungry very quickly.\"");
6249 
6250   if(!TruthQuestion("Do you want to challenge him? [y/N]"))
6251     return false;
6252 
6253   SumoWrestling = true;
6254   character* MirrorPlayer = Player->Duplicate(IGNORE_PROHIBITIONS);
6255   character* MirrorSumo = Sumo->Duplicate(IGNORE_PROHIBITIONS);
6256   SetPlayer(MirrorPlayer);
6257   charactervector Spectators;
6258 
6259   if(Player->GetTeam()->GetRelation(GetTeam(TOURIST_GUIDE_TEAM)) != HOSTILE
6260      && Player->GetTeam()->GetRelation(GetTeam(TOURIST_TEAM)) != HOSTILE)
6261   {
6262     GetTeam(TOURIST_GUIDE_TEAM)->MoveMembersTo(Spectators);
6263     GetTeam(TOURIST_TEAM)->MoveMembersTo(Spectators);
6264   }
6265 
6266   GetCurrentDungeon()->SaveLevel(SaveName(), 0);
6267   charactervector test;
6268   EnterArea(test, 1, STAIRS_UP);
6269   MirrorSumo->PutTo(SUMO_ARENA_POS + v2(6, 5));
6270   MirrorSumo->ChangeTeam(GetTeam(SUMO_TEAM));
6271   GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->SetMasterID(MirrorSumo->GetID());
6272 
6273   for(uint c = 0; c < Spectators.size(); ++c)
6274     Spectators[c]->PutToOrNear(SUMO_ARENA_POS + v2(6, 10));
6275 
6276   throw areachangerequest();
6277   return true;
6278 }
6279 
TryToExitSumoArena()6280 truth game::TryToExitSumoArena()
6281 {
6282   if(GetTeam(PLAYER_TEAM)->GetRelation(GetTeam(NEW_ATTNAM_TEAM)) == HOSTILE)
6283     return true;
6284 
6285   itemvector IVector;
6286   charactervector CVector;
6287 
6288   if(IsSumoWrestling())
6289   {
6290     if(TruthQuestion("Do you really wish to give up? [y/N]"))
6291       return EndSumoWrestling(LOST);
6292     else
6293       return false;
6294   }
6295   else
6296   {
6297     Player->Remove();
6298     GetCurrentLevel()->CollectEverything(IVector, CVector);
6299     GetCurrentDungeon()->SaveLevel(SaveName(), 1);
6300     std::vector<character*> test;
6301     EnterArea(test, 0, STAIRS_DOWN);
6302     Player->GetStackUnder()->AddItems(IVector);
6303 
6304     if(!IVector.empty())
6305     {
6306       character* Sumo = GetSumo();
6307 
6308       if(Sumo && Sumo->GetRelation(Player) != HOSTILE && Player->CanBeSeenBy(Sumo))
6309         ADD_MESSAGE("\"Don't leave anything there, please.\"");
6310     }
6311 
6312     v2 PlayerPos = Player->GetPos();
6313 
6314     for(uint c = 0; c < CVector.size(); ++c)
6315       CVector[c]->PutNear(PlayerPos);
6316 
6317     throw areachangerequest();
6318     return true;
6319   }
6320 }
6321 
EndSumoWrestling(int Result)6322 truth game::EndSumoWrestling(int Result)
6323 {
6324   msgsystem::LeaveBigMessageMode();
6325 
6326   if(Result == LOST)
6327     AskForKeyPress("You lose. [press any key to continue]");
6328   else if(Result == WON)
6329     AskForKeyPress("You win! [press any key to continue]");
6330   else if(Result == DISQUALIFIED)
6331     AskForKeyPress("You are disqualified! [press any key to continue]");
6332 
6333   character* Sumo = GetCurrentLevel()->GetLSquare(SUMO_ARENA_POS)->GetRoom()->GetMaster();
6334 
6335   /* We'll make a throw soon so deletes are allowed */
6336 
6337   if(Sumo)
6338   {
6339     Sumo->Remove();
6340     delete Sumo;
6341   }
6342 
6343   Player->Remove();
6344   delete Player;
6345   SetPlayer(0);
6346   itemvector IVector;
6347   charactervector CVector;
6348   GetCurrentLevel()->CollectEverything(IVector, CVector);
6349   GetCurrentDungeon()->SaveLevel(SaveName(), 1);
6350   charactervector test;
6351   EnterArea(test, 0, STAIRS_DOWN);
6352   SumoWrestling = false;
6353   Player->GetStackUnder()->AddItems(IVector);
6354   v2 PlayerPos = Player->GetPos();
6355 
6356   for(uint c = 0; c < CVector.size(); ++c)
6357     CVector[c]->PutNear(PlayerPos);
6358 
6359   if(Result == LOST)
6360     ADD_MESSAGE("\"I hope you've learned your lesson now!\"");
6361   else if(Result == DISQUALIFIED)
6362     ADD_MESSAGE("\"Don't do that again or I'll be really angry!\"");
6363   else
6364   {
6365     PlayerSumoChampion = true;
6366     character* Sumo = GetSumo();
6367     festring Msg = Sumo->GetName(DEFINITE) + " seems humbler than before. \"Darn. You bested me.\n";
6368     Msg << "Here's a little something as a reward,\" " << Sumo->GetPersonalPronoun()
6369         << " says and hands you a belt of levitation.\n\"";
6370     (belt::Spawn(BELT_OF_LEVITATION))->MoveTo(Player->GetStack());
6371     Msg << "Allow me to also teach you a few nasty martial art tricks the years have taught me.\"";
6372     Player->GetCWeaponSkill(UNARMED)->AddHit(100000);
6373     Player->GetCWeaponSkill(KICK)->AddHit(100000);
6374     character* Imperialist = GetCurrentLevel()->GetLSquare(5, 5)->GetRoom()->GetMaster();
6375 
6376     if(Imperialist && Imperialist->GetRelation(Player) != HOSTILE)
6377     {
6378       v2 Pos = Player->GetPos() + v2(0, 1);
6379       GetCurrentLevel()->GetLSquare(Pos)->KickAnyoneStandingHereAway();
6380       Imperialist->Remove();
6381       Imperialist->PutTo(Pos);
6382       Msg << "\n\nSuddenly you notice " << Imperialist->GetName(DEFINITE) << " has also entered.\n"
6383         "\"I see we have a promising fighter among us. I had already heard of your\n"
6384         "adventures outside the village, but hardly could I believe that one day you\n"
6385         "would defeat even the mighty Huang Ming Pong! A hero such as you is bound\n"
6386         "to become world famous, and can earn a fortune if wealthy sponsors are behind\n"
6387         "him. May I therefore propose a mutually profitable contract: I'll give you this\n"
6388         "nice shirt with my company's ad, and you'll wear it as you journey bravely to\n"
6389         "the unknown and fight epic battles against the limitless minions of evil. I'll\n"
6390         "reward you well when you return, depending on how much you have used it.\"";
6391       Player->GetStack()->AddItem(decosadshirt::Spawn());
6392     }
6393 
6394     TextScreen(Msg);
6395     GetCurrentArea()->SendNewDrawRequest();
6396     DrawEverything();
6397   }
6398 
6399   // Send the bananagrowers back to work
6400   std::vector<character*> VillagePeople = bugfixdp::FindCharactersOnLevel();
6401   for(int j = 0; j < VillagePeople.size(); j++)
6402   {
6403     character* Villager = VillagePeople[j];
6404     if(Villager && dynamic_cast<bananagrower*>(Villager))
6405     {
6406       Villager->SetFeedingSumo(false);
6407     }
6408   }
6409 
6410   Player->EditNP(-25000);
6411   Player->CheckStarvationDeath(CONST_S("exhausted after controlling a mirror image for too long"));
6412   throw areachangerequest();
6413   return true;
6414 }
6415 
ConstructGlobalRain()6416 rain* game::ConstructGlobalRain()
6417 {
6418   return new rain(GlobalRainLiquid, static_cast<lsquare*>(GetSquareInLoad()), GlobalRainSpeed, MONSTER_TEAM, false);
6419 }
6420 
GetSunLightDirectionVector()6421 v2 game::GetSunLightDirectionVector()
6422 {
6423   int Index = Tick % 48000 / 1000;
6424 
6425   /* Should have the same sign as sin(PI * Index / 24) and XTable[Index] /
6426      YTable[Index] should equal roughly -tan(PI * Index / 24). Also, vector
6427      (XTable[Index], YTable[Index]) + P should not be a valid position of
6428      any possible level L for any P belonging to L. */
6429 
6430   static int XTable[48] = {     0,  1000,  1000,  1000,  1000,  1000,
6431                                 1000,  1303,  1732,  2414,  3732,  7596,
6432                                 1000,  7596,  3732,  2414,  1732,  1303,
6433                                 1000,  1000,  1000,  1000,  1000,  1000,
6434                                 0, -1000, -1000, -1000, -1000, -1000,
6435                                 -1000, -1303, -1732, -2414, -3732, -7596,
6436                                 -1000, -7596, -3732, -2414, -1732, -1303,
6437                                 -1000, -1000, -1000, -1000, -1000, -1000 };
6438 
6439   /* Should have the same sign as -cos(PI * Index / 24) */
6440 
6441   static int YTable[48] = { -1000, -7596, -3732, -2414, -1732, -1303,
6442                             -1000, -1000, -1000, -1000, -1000, -1000,
6443                             0,  1000,  1000,  1000,  1000,  1000,
6444                             1000,  1303,  1732,  2414,  3732,  7596,
6445                             1000,  7596,  3732,  2414,  1732,  1303,
6446                             1000,  1000,  1000,  1000,  1000,  1000,
6447                             0, -1000, -1000, -1000, -1000, -1000,
6448                             -1000, -1303, -1732, -2414, -3732, -7596 };
6449 
6450   return v2(XTable[Index], YTable[Index]);
6451 }
6452 
CalculateMinimumEmitationRadius(col24 E)6453 int game::CalculateMinimumEmitationRadius(col24 E)
6454 {
6455   int MaxElement = Max(GetRed24(E), GetGreen24(E), GetBlue24(E));
6456   return int(sqrt(double(MaxElement << 7) / LIGHT_BORDER - 120.));
6457 }
6458 
IncreaseSquarePartEmitationTicks()6459 ulong game::IncreaseSquarePartEmitationTicks()
6460 {
6461   if((SquarePartEmitationTick += 2) == 0x100)
6462   {
6463     CurrentLevel->InitSquarePartEmitationTicks();
6464     SquarePartEmitationTick = 2;
6465   }
6466 
6467   return SquarePartEmitationTick;
6468 }
6469 
Wish(character * Wisher,cchar * MsgSingle,cchar * MsgPair,truth AllowExit)6470 int game::Wish(character* Wisher, cchar* MsgSingle, cchar* MsgPair, truth AllowExit)
6471 {
6472   if(Wisher->IsPlayerAutoPlay())return ABORTED;
6473 
6474   for(;;)
6475   {
6476     festring Temp;
6477     Temp << DefaultWish; // to let us fix previous instead of having to fully type it again
6478 
6479     if(DefaultQuestion(Temp, CONST_S("What do you want to wish for?"), DefaultWish, AllowExit) == ABORTED)
6480       return ABORTED;
6481 
6482     item* TempItem = protosystem::CreateItem(Temp, Wisher->IsPlayer());
6483 
6484     if(TempItem)
6485     {
6486       Wisher->GetStack()->AddItem(TempItem);
6487       TempItem->SpecialGenerationHandler();
6488 
6489       if(TempItem->HandleInPairs())
6490         ADD_MESSAGE(MsgPair, TempItem->CHAR_NAME(PLURAL));
6491       else
6492         ADD_MESSAGE(MsgSingle, TempItem->CHAR_NAME(INDEFINITE));
6493 
6494       return NORMAL_EXIT;
6495     }
6496   }
6497 }
6498 
DefaultQuestion(festring & Answer,festring Topic,festring & Default,truth AllowExit,stringkeyhandler KeyHandler)6499 int game::DefaultQuestion(festring& Answer, festring Topic, festring& Default,
6500                           truth AllowExit, stringkeyhandler KeyHandler)
6501 {
6502   festring ShortDefault = Default;
6503 
6504   if(Default.GetSize() > 29)
6505   {
6506     ShortDefault.Resize(27);
6507     ShortDefault = ShortDefault << CONST_S("...");
6508   }
6509 
6510   if(!Default.IsEmpty())
6511     Topic << " [" << ShortDefault << ']';
6512 
6513   if(StringQuestion(Answer, Topic, WHITE, 0, 80, AllowExit, KeyHandler) == ABORTED)
6514     return ABORTED;
6515 
6516   if(Answer.IsEmpty())
6517     Answer = Default;
6518   else
6519     Default = Answer;
6520 
6521   return NORMAL_EXIT;
6522 }
6523 
GetTime(ivantime & Time)6524 void game::GetTime(ivantime& Time)
6525 {
6526   Time.Hour = 12 + Tick / 2000;
6527   Time.Day = Time.Hour / 24 + 1;
6528   Time.Hour %= 24;
6529   Time.Min = Tick % 2000 * 60 / 2000;
6530 }
6531 
NameOrderer(character * C1,character * C2)6532 truth NameOrderer(character* C1, character* C2)
6533 {
6534   return festring::IgnoreCaseCompare(C1->GetName(UNARTICLED), C2->GetName(UNARTICLED));
6535 }
6536 
PolymorphControlKeyHandler(int Key,festring & String)6537 truth game::PolymorphControlKeyHandler(int Key, festring& String)
6538 {
6539   if(Key == '?')
6540   {
6541     felist List(CONST_S("List of known creatures and their intelligence requirements"));
6542     SetStandardListAttributes(List);
6543     List.SetPageLength(15);
6544     List.AddFlags(SELECTABLE);
6545     protosystem::CreateEverySeenCharacter(CharacterDrawVector);
6546     std::sort(CharacterDrawVector.begin(), CharacterDrawVector.end(), NameOrderer);
6547     List.SetEntryDrawer(CharacterEntryDrawer);
6548     std::vector<festring> StringVector;
6549     uint c;
6550 
6551     for(c = 0; c < CharacterDrawVector.size(); ++c)
6552     {
6553       character* Char = CharacterDrawVector[c];
6554 
6555       if(Char->CanBeWished())
6556       {
6557         festring Entry;
6558         Char->AddName(Entry, UNARTICLED);
6559         StringVector.push_back(Entry);
6560         int Req = Char->GetPolymorphIntelligenceRequirement();
6561 
6562         if(Char->IsSameAs(Player)
6563            || (Player->GetPolymorphBackup()
6564                && Player->GetPolymorphBackup()->IsSameAs(Char)))
6565           Req = 0;
6566 
6567         Entry << " (" << Req << ')';
6568         int Int = Player->GetAttribute(INTELLIGENCE);
6569         List.AddEntry(Entry, Req > Int ? RED : LIGHT_GRAY, 0, c);
6570         List.SetLastEntryHelp(festring() << "You can only intentionally polymorph into creatures that you have already "
6571                                          << "encountered during your adventure. In addition to that, you need a certain"
6572                                          << "amount of Intelligence to successfully control the transformation.");
6573       }
6574     }
6575 
6576     int Chosen = List.Draw();
6577 
6578     for(c = 0; c < CharacterDrawVector.size(); ++c)
6579       delete CharacterDrawVector[c];
6580 
6581     if(!(Chosen & FELIST_ERROR_BIT))
6582       String = StringVector[Chosen];
6583 
6584     CharacterDrawVector.clear();
6585     return true;
6586   }
6587 
6588   return false;
6589 }
6590 
operator <<(outputfile & SaveFile,const killdata & Value)6591 outputfile& operator<<(outputfile& SaveFile, const killdata& Value)
6592 {
6593   SaveFile << Value.Amount << Value.DangerSum << Value.Reason;
6594   return SaveFile;
6595 }
6596 
operator >>(inputfile & SaveFile,killdata & Value)6597 inputfile& operator>>(inputfile& SaveFile, killdata& Value)
6598 {
6599   SaveFile >> Value.Amount >> Value.DangerSum >> Value.Reason;
6600   return SaveFile;
6601 }
6602 
operator <<(outputfile & SaveFile,const killreason & Value)6603 outputfile& operator<<(outputfile& SaveFile, const killreason& Value)
6604 {
6605   SaveFile << Value.Amount << Value.String;
6606   return SaveFile;
6607 }
6608 
operator >>(inputfile & SaveFile,killreason & Value)6609 inputfile& operator>>(inputfile& SaveFile, killreason& Value)
6610 {
6611   SaveFile >> Value.Amount >> Value.String;
6612   return SaveFile;
6613 }
6614 
DistanceOrderer(character * C1,character * C2)6615 truth DistanceOrderer(character* C1, character* C2)
6616 {
6617   v2 PlayerPos = PLAYER->GetPos();
6618   v2 Pos1 = C1->GetPos();
6619   v2 Pos2 = C2->GetPos();
6620   int D1 = Max(abs(Pos1.X - PlayerPos.X), abs(Pos1.Y - PlayerPos.Y));
6621   int D2 = Max(abs(Pos2.X - PlayerPos.X), abs(Pos2.Y - PlayerPos.Y));
6622 
6623   if(D1 != D2)
6624     return D1 < D2;
6625   else if(Pos1.Y != Pos2.Y)
6626     return Pos1.Y < Pos2.Y;
6627   else
6628     return Pos1.X < Pos2.X;
6629 }
6630 
FillPetVector(cchar * Verb)6631 truth game::FillPetVector(cchar* Verb)
6632 {
6633   PetVector.clear();
6634   team* Team = GetTeam(PLAYER_TEAM);
6635 
6636   for(character* p : Team->GetMember())
6637     if(p->IsEnabled() && !p->IsPlayer() && p->CanBeSeenByPlayer())
6638       PetVector.push_back(p);
6639 
6640   if(PetVector.empty())
6641   {
6642     ADD_MESSAGE("You don't detect any friends to %s.", Verb);
6643     return false;
6644   }
6645 
6646   std::sort(PetVector.begin(), PetVector.end(), DistanceOrderer);
6647   LastPetUnderCursor = PetVector[0];
6648   return true;
6649 }
6650 
CommandQuestion()6651 truth game::CommandQuestion()
6652 {
6653   if(!FillPetVector("command"))
6654     return false;
6655 
6656   character* Char;
6657 
6658   if(PetVector.size() == 1)
6659     Char = PetVector[0];
6660   else
6661   {
6662     v2 Pos = PetVector[0]->GetPos();
6663     Pos = PositionQuestion(CONST_S("Whom do you wish to command? [direction keys/'+'/'-'/'a'll/space/esc]"),
6664                            Pos, &PetHandler, &CommandKeyHandler);
6665 
6666     if(Pos == ERROR_V2)
6667       return false;
6668 
6669     if(Pos == ABORT_V2)
6670       return true;
6671 
6672     Char = CurrentArea->GetSquare(Pos)->GetCharacter();
6673 
6674     if(!Char || !Char->CanBeSeenByPlayer())
6675     {
6676       ADD_MESSAGE("You don't see anyone here to command.");
6677       return false;
6678     }
6679 
6680     if(Char->IsPlayer())
6681     {
6682       ADD_MESSAGE("You do that all the time.");
6683       return false;
6684     }
6685 
6686     if(!Char->IsPet())
6687     {
6688       ADD_MESSAGE("%s refuses to be commanded by you.", Char->CHAR_NAME(DEFINITE));
6689       return false;
6690     }
6691   }
6692 
6693   return Char->IssuePetCommands();
6694 }
6695 
NameQuestion()6696 void game::NameQuestion()
6697 {
6698   if(!FillPetVector("name"))
6699     return;
6700 
6701   if(PetVector.size() == 1)
6702     PetVector[0]->TryToName();
6703   else
6704     PositionQuestion(CONST_S("Who do you want to name? [direction keys/'+'/'-'/'n'ame/esc]"),
6705                      PetVector[0]->GetPos(), &PetHandler, &NameKeyHandler);
6706 }
6707 
PetHandler(v2 CursorPos)6708 void game::PetHandler(v2 CursorPos)
6709 {
6710   character* Char = CurrentArea->GetSquare(CursorPos)->GetCharacter();
6711 
6712   if(Char && Char->CanBeSeenByPlayer() && Char->IsPet() && !Char->IsPlayer())
6713     CursorData = RED_CURSOR|CURSOR_TARGET;
6714   else
6715     CursorData = RED_CURSOR;
6716 
6717   if(Char && !Char->IsPlayer() && Char->IsPet())
6718     LastPetUnderCursor = Char;
6719 }
6720 
CommandKeyHandler(v2 CursorPos,int Key)6721 v2 game::CommandKeyHandler(v2 CursorPos, int Key)
6722 {
6723   if(SelectPet(Key))
6724     return LastPetUnderCursor->GetPos();
6725   else if(Key == 'a' || Key == 'A')
6726     return CommandAll() ? ABORT_V2 : ERROR_V2;
6727 
6728   return CursorPos;
6729 }
6730 
SelectPet(int Key)6731 truth game::SelectPet(int Key)
6732 {
6733   if(Key == '+')
6734   {
6735     for(uint c = 0; c < PetVector.size(); ++c)
6736       if(PetVector[c] == LastPetUnderCursor)
6737       {
6738         if(++c == PetVector.size())
6739           c = 0;
6740 
6741         LastPetUnderCursor = PetVector[c];
6742         return true;
6743       }
6744   }
6745   else if(Key == '-')
6746   {
6747     for(uint c = 0; c < PetVector.size(); ++c)
6748       if(PetVector[c] == LastPetUnderCursor)
6749       {
6750         if(!c)
6751           c = PetVector.size();
6752 
6753         LastPetUnderCursor = PetVector[--c];
6754         return true;
6755       }
6756   }
6757 
6758   return false;
6759 }
6760 
CommandScreen(cfestring & Topic,ulong PossibleFlags,ulong ConstantFlags,ulong & VaryFlags,ulong & Flags)6761 void game::CommandScreen(cfestring& Topic, ulong PossibleFlags, ulong ConstantFlags, ulong& VaryFlags, ulong& Flags)
6762 {
6763   static cchar* CommandDescription[COMMAND_FLAGS] =
6764     {
6765       "Follow me",
6766       "Flee from enemies",
6767       "Don't change your equipment",
6768       "Don't consume anything valuable"
6769     };
6770 
6771   felist List(Topic);
6772   SetStandardListAttributes(List);
6773   List.AddFlags(SELECTABLE);
6774   List.AddDescription(CONST_S(""));
6775   List.AddDescription(CONST_S("Command                                                        Active?"));
6776 
6777   for(;;)
6778   {
6779     int c, i;
6780 
6781     for(c = 0; c < COMMAND_FLAGS; ++c)
6782       if(1 << c & PossibleFlags)
6783       {
6784         truth Changeable = !(1 << c & ConstantFlags);
6785         festring Entry;
6786 
6787         if(Changeable)
6788         {
6789           Entry = CommandDescription[c];
6790           Entry.Resize(60);
6791         }
6792         else
6793         {
6794           Entry << "   " << CommandDescription[c];
6795           Entry.Resize(63);
6796         }
6797 
6798         if(1 << c & VaryFlags)
6799           Entry << "varies";
6800         else
6801           Entry << (1 << c & Flags ? "yes" : "no");
6802 
6803         List.AddEntry(Entry, Changeable ? LIGHT_GRAY : DARK_GRAY, 0, NO_IMAGE, Changeable);
6804       }
6805 
6806     int Chosen = List.Draw();
6807 
6808     if(Chosen & FELIST_ERROR_BIT)
6809       return;
6810 
6811     for(c = 0, i = 0; c < COMMAND_FLAGS; ++c)
6812       if(1 << c & PossibleFlags && !(1 << c & ConstantFlags) && i++ == Chosen)
6813       {
6814         if(1 << c & VaryFlags)
6815         {
6816           VaryFlags &= ~(1 << c);
6817           Flags |= 1 << c;
6818         }
6819         else
6820           Flags ^= 1 << c;
6821 
6822         break;
6823       }
6824 
6825     List.Empty();
6826     DrawEverythingNoBlit();
6827   }
6828 }
6829 
CommandAll()6830 truth game::CommandAll()
6831 {
6832   ulong PossibleFlags = 0, ConstantFlags = ALL_COMMAND_FLAGS, VaryFlags = 0, OldFlags = 0;
6833   uint c1, c2;
6834 
6835   for(c1 = 0; c1 < PetVector.size(); ++c1)
6836   {
6837     ConstantFlags &= PetVector[c1]->GetConstantCommandFlags();
6838     ulong C = PetVector[c1]->GetCommandFlags();
6839     ulong ThisPossible = PetVector[c1]->GetPossibleCommandFlags();
6840 
6841     for(c2 = 0; c2 < COMMAND_FLAGS; ++c2)
6842       if(1 << c2 & PossibleFlags & ThisPossible
6843          && (1 << c2 & C) != (1 << c2 & OldFlags))
6844         VaryFlags |= 1 << c2;
6845 
6846     PossibleFlags |= ThisPossible;
6847     OldFlags |= C & ThisPossible;
6848   }
6849 
6850   if(!PossibleFlags)
6851   {
6852     ADD_MESSAGE("Not a single creature in your visible team can be commanded.");
6853     return false;
6854   }
6855 
6856   ulong NewFlags = OldFlags;
6857   CommandScreen(CONST_S("Issue commands to whole visible team"), PossibleFlags, ConstantFlags, VaryFlags, NewFlags);
6858   truth Change = false;
6859 
6860   for(c1 = 0; c1 < PetVector.size(); ++c1)
6861   {
6862     character* Char = PetVector[c1];
6863 
6864     if(!Char->IsConscious())
6865       continue;
6866 
6867     ulong OldC = Char->GetCommandFlags();
6868     ulong ConstC = Char->GetConstantCommandFlags();
6869     ulong ThisC = (NewFlags
6870                   & Char->GetPossibleCommandFlags()
6871                   & ~(ConstC|VaryFlags))
6872                   | (OldC & (ConstC|VaryFlags));
6873 
6874     if(ThisC != OldC)
6875       Change = true;
6876 
6877     Char->SetCommandFlags(ThisC);
6878   }
6879 
6880   if(!Change)
6881     return false;
6882 
6883   Player->EditAP(-500);
6884   Player->EditExperience(CHARISMA, 50, 1 << 7);
6885   return true;
6886 }
6887 
GetAttributeColor(int I)6888 col16 game::GetAttributeColor(int I)
6889 {
6890   int Delta = GetTick() - LastAttributeChangeTick[I];
6891 
6892   if(OldAttribute[I] == NewAttribute[I] || Delta >= 510)
6893     return WHITE;
6894   else if(OldAttribute[I] < NewAttribute[I])
6895     return MakeRGB16(255, 255, Delta >> 1);
6896   else
6897     return MakeRGB16(255, Delta >> 1, Delta >> 1);
6898 }
6899 
UpdateAttributeMemory()6900 void game::UpdateAttributeMemory()
6901 {
6902   for(int c = 0; c < ATTRIBUTES; ++c)
6903   {
6904     int A = Player->GetAttribute(c);
6905 
6906     if(A != NewAttribute[c])
6907     {
6908       OldAttribute[c] = NewAttribute[c];
6909       NewAttribute[c] = A;
6910       LastAttributeChangeTick[c] = GetTick();
6911     }
6912   }
6913 }
6914 
InitAttributeMemory()6915 void game::InitAttributeMemory()
6916 {
6917   for(int c = 0; c < ATTRIBUTES; ++c)
6918     OldAttribute[c] = NewAttribute[c] = Player->GetAttribute(c);
6919 }
6920 
TeleportHandler(v2 CursorPos)6921 void game::TeleportHandler(v2 CursorPos)
6922 {
6923   if((CursorPos - Player->GetPos()).GetLengthSquare() > Player->GetTeleportRangeSquare())
6924     CursorData = BLUE_CURSOR|CURSOR_TARGET;
6925   else
6926     CursorData = RED_CURSOR|CURSOR_TARGET;
6927 }
6928 
GetGameSituationDanger()6929 double game::GetGameSituationDanger()
6930 {
6931   double SituationDanger = 0;
6932   character* Player = GetPlayer();
6933   truth PlayerStuck = Player->IsStuck();
6934   v2 PlayerPos = Player->GetPos();
6935   character* TruePlayer = Player;
6936 
6937   if(PlayerStuck)
6938     (Player = Player->Duplicate(IGNORE_PROHIBITIONS))->ChangeTeam(0);
6939 
6940   for(int c1 = 0; c1 < GetTeams(); ++c1)
6941     if(GetTeam(c1)->GetRelation(GetTeam(PLAYER_TEAM)) == HOSTILE)
6942       for(character* Enemy : GetTeam(c1)->GetMember())
6943       {
6944         if(Enemy->IsEnabled() && Enemy->CanAttack()
6945            && (Enemy->CanMove() || Enemy->GetPos().IsAdjacent(PlayerPos)))
6946         {
6947           truth EnemyStuck = Enemy->IsStuck();
6948           v2 EnemyPos = Enemy->GetPos();
6949           truth Sees = TruePlayer->CanBeSeenBy(Enemy);
6950           character* TrueEnemy = Enemy;
6951 
6952           if(EnemyStuck)
6953             Enemy = Enemy->Duplicate(IGNORE_PROHIBITIONS);
6954 
6955           double PlayerTeamDanger = 1 / Enemy->GetSituationDanger(Player, EnemyPos, PlayerPos, Sees);
6956 
6957           for(int c2 = 0; c2 < GetTeams(); ++c2)
6958             if(GetTeam(c2)->GetRelation(GetTeam(c1)) == HOSTILE)
6959               for(character* Friend : GetTeam(c2)->GetMember())
6960               {
6961                 if(Friend->IsEnabled() && !Friend->IsPlayer() && Friend->CanAttack()
6962                    && (Friend->CanMove() || Friend->GetPos().IsAdjacent(EnemyPos)))
6963                 {
6964                   v2 FriendPos = Friend->GetPos();
6965                   truth Sees = TrueEnemy->CanBeSeenBy(Friend);
6966 
6967                   if(Friend->IsStuck())
6968                   {
6969                     Friend = Friend->Duplicate(IGNORE_PROHIBITIONS);
6970                     PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees) * .2;
6971                     delete Friend;
6972                   }
6973                   else
6974                     PlayerTeamDanger += Friend->GetSituationDanger(Enemy, FriendPos, EnemyPos, Sees);
6975                 }
6976               }
6977 
6978           if(EnemyStuck)
6979           {
6980             PlayerTeamDanger *= 5;
6981             delete Enemy;
6982           }
6983 
6984           SituationDanger += 1 / PlayerTeamDanger;
6985         }
6986       }
6987 
6988   Player->ModifySituationDanger(SituationDanger);
6989 
6990   if(PlayerStuck)
6991   {
6992     SituationDanger *= 2;
6993     delete Player;
6994   }
6995 
6996   return SituationDanger;
6997 }
6998 
GetTimeSpent()6999 long game::GetTimeSpent()
7000 {
7001   return time::TimeAdd(time::TimeDifference(time(0), LastLoad), TimePlayedBeforeLastLoad);
7002 }
7003 
operator <<(outputfile & SaveFile,const massacreid & MI)7004 outputfile& operator<<(outputfile& SaveFile, const massacreid& MI)
7005 {
7006   SaveFile << MI.Type << MI.Config << MI.Name;
7007   return SaveFile;
7008 }
7009 
operator >>(inputfile & SaveFile,massacreid & MI)7010 inputfile& operator>>(inputfile& SaveFile, massacreid& MI)
7011 {
7012   SaveFile >> MI.Type >> MI.Config >> MI.Name;
7013   return SaveFile;
7014 }
7015 
PlayerIsRunning()7016 truth game::PlayerIsRunning()
7017 {
7018   return PlayerRunning && Player->CanMove();
7019 }
7020 
AddSpecialCursor(v2 Pos,int Data)7021 void game::AddSpecialCursor(v2 Pos, int Data)
7022 {
7023   SpecialCursorPos.push_back(Pos);
7024   SpecialCursorData.push_back(Data);
7025 }
7026 
RemoveSpecialCursors()7027 void game::RemoveSpecialCursors()
7028 {
7029   SpecialCursorPos.clear();
7030   SpecialCursorData.clear();
7031 }
7032 
LearnAbout(god * Who)7033 void game::LearnAbout(god* Who)
7034 {
7035   Who->SetIsKnown(true);
7036 
7037   /* slightly slow, but doesn't matter since
7038      this is run so rarely */
7039 
7040   if(PlayerKnowsAllGods() && !game::PlayerHasReceivedAllGodsKnownBonus)
7041   {
7042     GetPlayer()->ApplyAllGodsKnownBonus();
7043     game::PlayerHasReceivedAllGodsKnownBonus = true;
7044   }
7045 }
7046 
PlayerKnowsAllGods()7047 truth game::PlayerKnowsAllGods()
7048 {
7049   for(int c = 1; c <= GODS; ++c)
7050     if(!GetGod(c)->IsKnown())
7051       return false;
7052 
7053   return true;
7054 }
7055 
AdjustRelationsToAllGods(int Amount)7056 void game::AdjustRelationsToAllGods(int Amount)
7057 {
7058   for(int c = 1; c <= GODS; ++c)
7059     GetGod(c)->AdjustRelation(Amount);
7060 }
7061 
SetRelationsToAllGods(int Amount)7062 void game::SetRelationsToAllGods(int Amount)
7063 {
7064   for(int c = 1; c <= GODS; ++c)
7065     GetGod(c)->SetRelation(Amount);
7066 }
7067 
ShowDeathSmiley(bitmap * Buffer,truth)7068 void game::ShowDeathSmiley(bitmap* Buffer, truth)
7069 {
7070   static blitdata B = { 0,
7071                         { 0, 0 },
7072                         { (RES.X >> 1) - 24, RES.Y * 4 / 7 - 24 },
7073                         { 48, 48 },
7074                         { 0 },
7075                         TRANSPARENT_COLOR,
7076                         0 };
7077 
7078   int Tick = globalwindowhandler::UpdateTick();
7079 
7080   if(((Tick >> 1) & 31) == 1)
7081     B.Src.X = 48;
7082   else if(((Tick >> 1) & 31) == 2)
7083     B.Src.X = 96;
7084   else
7085     B.Src.X = 0;
7086 
7087   B.Bitmap = Buffer;
7088   igraph::GetSmileyGraphic()->NormalBlit(B);
7089 
7090   if(Buffer == DOUBLE_BUFFER)
7091     graphics::BlitDBToScreen();
7092 }
7093