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