|
|
|
|
/**
|
|
|
|
|
* @file diablo.cpp
|
|
|
|
|
*
|
|
|
|
|
* Implementation of the main game initialization functions.
|
|
|
|
|
*/
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <string_view>
|
|
|
|
|
|
|
|
|
|
#ifdef USE_SDL3
|
|
|
|
|
#include <SDL3/SDL_events.h>
|
|
|
|
|
#include <SDL3/SDL_init.h>
|
|
|
|
|
#include <SDL3/SDL_keycode.h>
|
|
|
|
|
#else
|
|
|
|
|
#include <SDL.h>
|
|
|
|
|
|
|
|
|
|
#ifdef USE_SDL1
|
|
|
|
|
#include "utils/sdl2_to_1_2_backports.h"
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <fmt/format.h>
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
|
|
#include "DiabloUI/selstart.h"
|
|
|
|
|
#include "appfat.h"
|
|
|
|
|
#include "automap.h"
|
|
|
|
|
#include "capture.h"
|
|
|
|
|
#include "control.h"
|
|
|
|
|
#include "cursor.h"
|
|
|
|
|
#include "dead.h"
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
#include "debug.h"
|
|
|
|
|
#endif
|
|
|
|
|
#include "DiabloUI/diabloui.h"
|
|
|
|
|
#include "controls/control_mode.hpp"
|
|
|
|
|
#include "controls/keymapper.hpp"
|
|
|
|
|
#include "controls/plrctrls.h"
|
|
|
|
|
#include "controls/remap_keyboard.h"
|
|
|
|
|
#include "diablo.h"
|
|
|
|
|
#include "diablo_msg.hpp"
|
|
|
|
|
#include "discord/discord.h"
|
|
|
|
|
#include "doom.h"
|
|
|
|
|
#include "encrypt.h"
|
|
|
|
|
#include "engine/backbuffer_state.hpp"
|
|
|
|
|
#include "engine/clx_sprite.hpp"
|
|
|
|
|
#include "engine/demomode.h"
|
|
|
|
|
#include "engine/dx.h"
|
|
|
|
|
#include "engine/events.hpp"
|
|
|
|
|
#include "engine/load_cel.hpp"
|
|
|
|
|
#include "engine/load_file.hpp"
|
|
|
|
|
#include "engine/random.hpp"
|
|
|
|
|
#include "engine/render/clx_render.hpp"
|
|
|
|
|
#include "engine/sound.h"
|
|
|
|
|
#include "game_mode.hpp"
|
|
|
|
|
#include "gamemenu.h"
|
|
|
|
|
#include "gmenu.h"
|
|
|
|
|
#include "headless_mode.hpp"
|
|
|
|
|
#include "help.h"
|
|
|
|
|
#include "hwcursor.hpp"
|
|
|
|
|
#include "init.hpp"
|
|
|
|
|
#include "inv.h"
|
|
|
|
|
#include "levels/drlg_l1.h"
|
|
|
|
|
#include "levels/drlg_l2.h"
|
|
|
|
|
#include "levels/drlg_l3.h"
|
|
|
|
|
#include "levels/drlg_l4.h"
|
|
|
|
|
#include "levels/gendung.h"
|
|
|
|
|
#include "levels/setmaps.h"
|
|
|
|
|
#include "levels/themes.h"
|
|
|
|
|
#include "levels/town.h"
|
|
|
|
|
#include "levels/trigs.h"
|
|
|
|
|
#include "lighting.h"
|
|
|
|
|
#include "loadsave.h"
|
|
|
|
|
#include "lua/lua_global.hpp"
|
|
|
|
|
#include "menu.h"
|
|
|
|
|
#include "minitext.h"
|
|
|
|
|
#include "missiles.h"
|
|
|
|
|
#include "monstdat.h"
|
|
|
|
|
#include "movie.h"
|
|
|
|
|
#include "multi.h"
|
|
|
|
|
#include "nthread.h"
|
|
|
|
|
#include "objects.h"
|
|
|
|
|
#include "options.h"
|
|
|
|
|
#include "panels/console.hpp"
|
|
|
|
|
#include "panels/info_box.hpp"
|
|
|
|
|
#include "panels/partypanel.hpp"
|
|
|
|
|
#include "panels/spell_book.hpp"
|
|
|
|
|
#include "panels/spell_list.hpp"
|
|
|
|
|
#include "pfile.h"
|
|
|
|
|
#include "playerdat.hpp"
|
|
|
|
|
#include "plrmsg.h"
|
|
|
|
|
#include "qol/chatlog.h"
|
|
|
|
|
#include "qol/floatingnumbers.h"
|
|
|
|
|
#include "qol/itemlabels.h"
|
|
|
|
|
#include "qol/monhealthbar.h"
|
|
|
|
|
#include "qol/stash.h"
|
|
|
|
|
#include "qol/xpbar.h"
|
|
|
|
|
#include "quick_messages.hpp"
|
|
|
|
|
#include "restrict.h"
|
|
|
|
|
#include "stores.h"
|
|
|
|
|
#include "storm/storm_net.hpp"
|
|
|
|
|
#include "storm/storm_svid.h"
|
|
|
|
|
#include "towners.h"
|
|
|
|
|
#include "track.h"
|
|
|
|
|
#include "utils/console.h"
|
|
|
|
|
#include "utils/display.h"
|
|
|
|
|
#include "utils/is_of.hpp"
|
|
|
|
|
#include "utils/language.h"
|
|
|
|
|
#include "utils/parse_int.hpp"
|
|
|
|
|
#include "utils/paths.h"
|
|
|
|
|
#include "utils/screen_reader.hpp"
|
|
|
|
|
#include "utils/sdl_compat.h"
|
|
|
|
|
#include "utils/sdl_thread.h"
|
|
|
|
|
#include "utils/status_macros.hpp"
|
|
|
|
|
#include "utils/str_cat.hpp"
|
|
|
|
|
#include "utils/utf8.hpp"
|
|
|
|
|
|
|
|
|
|
#ifndef USE_SDL1
|
|
|
|
|
#include "controls/touch/gamepad.h"
|
|
|
|
|
#include "controls/touch/renderers.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef __vita__
|
|
|
|
|
#include "platform/vita/touch.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef GPERF_HEAP_FIRST_GAME_ITERATION
|
|
|
|
|
#include <gperftools/heap-profiler.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace devilution {
|
|
|
|
|
|
|
|
|
|
uint32_t DungeonSeeds[NUMLEVELS];
|
|
|
|
|
std::optional<uint32_t> LevelSeeds[NUMLEVELS];
|
|
|
|
|
Point MousePosition;
|
|
|
|
|
bool gbRunGameResult;
|
|
|
|
|
bool ReturnToMainMenu;
|
|
|
|
|
/** Enable updating of player character, set to false once Diablo dies */
|
|
|
|
|
bool gbProcessPlayers;
|
|
|
|
|
bool gbLoadGame;
|
|
|
|
|
bool cineflag;
|
|
|
|
|
int PauseMode;
|
|
|
|
|
clicktype sgbMouseDown;
|
|
|
|
|
uint16_t gnTickDelay = 50;
|
|
|
|
|
char gszProductName[64] = "DevilutionX vUnknown";
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
bool DebugDisableNetworkTimeout = false;
|
|
|
|
|
std::vector<std::string> DebugCmdsFromCommandLine;
|
|
|
|
|
#endif
|
|
|
|
|
GameLogicStep gGameLogicStep = GameLogicStep::None;
|
|
|
|
|
|
|
|
|
|
/** This and the following mouse variables are for handling in-game click-and-hold actions */
|
|
|
|
|
PlayerActionType LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
|
|
|
|
|
// Controller support: Actions to run after updating the cursor state.
|
|
|
|
|
// Defined in SourceX/controls/plctrls.cpp.
|
|
|
|
|
extern void plrctrls_after_check_curs_move();
|
|
|
|
|
extern void plrctrls_every_frame();
|
|
|
|
|
extern void plrctrls_after_game_logic();
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
char gszVersionNumber[64] = "internal version unknown";
|
|
|
|
|
|
|
|
|
|
bool gbGameLoopStartup;
|
|
|
|
|
bool forceSpawn;
|
|
|
|
|
bool forceDiablo;
|
|
|
|
|
int sgnTimeoutCurs;
|
|
|
|
|
bool gbShowIntro = true;
|
|
|
|
|
/** To know if these things have been done when we get to the diablo_deinit() function */
|
|
|
|
|
bool was_archives_init = false;
|
|
|
|
|
/** To know if surfaces have been initialized or not */
|
|
|
|
|
bool was_window_init = false;
|
|
|
|
|
bool was_ui_init = false;
|
|
|
|
|
|
|
|
|
|
void StartGame(interface_mode uMsg)
|
|
|
|
|
{
|
|
|
|
|
CalcViewportGeometry();
|
|
|
|
|
cineflag = false;
|
|
|
|
|
InitCursor();
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
LoadDebugGFX();
|
|
|
|
|
#endif
|
|
|
|
|
assert(HeadlessMode || ghMainWnd);
|
|
|
|
|
music_stop();
|
|
|
|
|
InitMonsterHealthBar();
|
|
|
|
|
InitXPBar();
|
|
|
|
|
ShowProgress(uMsg);
|
|
|
|
|
gmenu_init_menu();
|
|
|
|
|
InitLevelCursor();
|
|
|
|
|
sgnTimeoutCurs = CURSOR_NONE;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeGame()
|
|
|
|
|
{
|
|
|
|
|
FreeMonsterHealthBar();
|
|
|
|
|
FreeXPBar();
|
|
|
|
|
FreeControlPan();
|
|
|
|
|
FreeInvGFX();
|
|
|
|
|
FreeGMenu();
|
|
|
|
|
FreeQuestText();
|
|
|
|
|
FreeInfoBoxGfx();
|
|
|
|
|
FreeStoreMem();
|
|
|
|
|
|
|
|
|
|
for (Player &player : Players)
|
|
|
|
|
ResetPlayerGFX(player);
|
|
|
|
|
|
|
|
|
|
FreeCursor();
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
FreeDebugGFX();
|
|
|
|
|
#endif
|
|
|
|
|
FreeGameMem();
|
|
|
|
|
stream_stop();
|
|
|
|
|
music_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProcessInput()
|
|
|
|
|
{
|
|
|
|
|
if (PauseMode == 2) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plrctrls_every_frame();
|
|
|
|
|
|
|
|
|
|
if (!gbIsMultiplayer && gmenu_is_active()) {
|
|
|
|
|
RedrawViewport();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!gmenu_is_active() && sgnTimeoutCurs == CURSOR_NONE) {
|
|
|
|
|
#ifdef __vita__
|
|
|
|
|
FinishSimulatedMouseClicks(MousePosition);
|
|
|
|
|
#endif
|
|
|
|
|
CheckCursMove();
|
|
|
|
|
plrctrls_after_check_curs_move();
|
|
|
|
|
RepeatPlayerAction();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LeftMouseCmd(bool bShift)
|
|
|
|
|
{
|
|
|
|
|
bool bNear;
|
|
|
|
|
|
|
|
|
|
assert(!GetMainPanel().contains(MousePosition));
|
|
|
|
|
|
|
|
|
|
if (leveltype == DTYPE_TOWN) {
|
|
|
|
|
CloseGoldWithdraw();
|
|
|
|
|
CloseStash();
|
|
|
|
|
if (pcursitem != -1 && pcurs == CURSOR_HAND)
|
|
|
|
|
NetSendCmdLocParam1(true, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursPosition, pcursitem);
|
|
|
|
|
if (pcursmonst != -1)
|
|
|
|
|
NetSendCmdLocParam1(true, CMD_TALKXY, cursPosition, pcursmonst);
|
|
|
|
|
if (pcursitem == -1 && pcursmonst == -1 && PlayerUnderCursor == nullptr) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::Walk;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, cursPosition);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Player &myPlayer = *MyPlayer;
|
|
|
|
|
bNear = myPlayer.position.tile.WalkingDistance(cursPosition) < 2;
|
|
|
|
|
if (pcursitem != -1 && pcurs == CURSOR_HAND && !bShift) {
|
|
|
|
|
NetSendCmdLocParam1(true, invflag ? CMD_GOTOGETITEM : CMD_GOTOAGETITEM, cursPosition, pcursitem);
|
|
|
|
|
} else if (ObjectUnderCursor != nullptr && !ObjectUnderCursor->IsDisabled() && (!bShift || (bNear && ObjectUnderCursor->_oBreak == 1))) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::OperateObject;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, pcurs == CURSOR_DISARM ? CMD_DISARMXY : CMD_OPOBJXY, cursPosition);
|
|
|
|
|
} else if (myPlayer.UsesRangedWeapon()) {
|
|
|
|
|
if (bShift) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::Attack;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, CMD_RATTACKXY, cursPosition);
|
|
|
|
|
} else if (pcursmonst != -1) {
|
|
|
|
|
if (CanTalkToMonst(Monsters[pcursmonst])) {
|
|
|
|
|
NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst);
|
|
|
|
|
} else {
|
|
|
|
|
LastPlayerAction = PlayerActionType::AttackMonsterTarget;
|
|
|
|
|
NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst);
|
|
|
|
|
}
|
|
|
|
|
} else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::AttackPlayerTarget;
|
|
|
|
|
NetSendCmdParam1(true, CMD_RATTACKPID, PlayerUnderCursor->getId());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (bShift) {
|
|
|
|
|
if (pcursmonst != -1) {
|
|
|
|
|
if (CanTalkToMonst(Monsters[pcursmonst])) {
|
|
|
|
|
NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst);
|
|
|
|
|
} else {
|
|
|
|
|
LastPlayerAction = PlayerActionType::Attack;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, CMD_SATTACKXY, cursPosition);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LastPlayerAction = PlayerActionType::Attack;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, CMD_SATTACKXY, cursPosition);
|
|
|
|
|
}
|
|
|
|
|
} else if (pcursmonst != -1) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::AttackMonsterTarget;
|
|
|
|
|
NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst);
|
|
|
|
|
} else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::AttackPlayerTarget;
|
|
|
|
|
NetSendCmdParam1(true, CMD_ATTACKPID, PlayerUnderCursor->getId());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!bShift && pcursitem == -1 && ObjectUnderCursor == nullptr && pcursmonst == -1 && PlayerUnderCursor == nullptr) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::Walk;
|
|
|
|
|
NetSendCmdLoc(MyPlayerId, true, CMD_WALKXY, cursPosition);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryOpenDungeonWithMouse()
|
|
|
|
|
{
|
|
|
|
|
if (leveltype != DTYPE_TOWN)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const Item &holdItem = MyPlayer->HoldItem;
|
|
|
|
|
if (holdItem.IDidx == IDI_RUNEBOMB && OpensHive(cursPosition))
|
|
|
|
|
OpenHive();
|
|
|
|
|
else if (holdItem.IDidx == IDI_MAPOFDOOM && OpensGrave(cursPosition))
|
|
|
|
|
OpenGrave();
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LeftMouseDown(uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
|
|
|
|
|
if (gmenu_left_mouse(true))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (CheckMuteButton())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (sgnTimeoutCurs != CURSOR_NONE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (MyPlayerIsDead) {
|
|
|
|
|
CheckMainPanelButtonDead();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (PauseMode == 2) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (DoomFlag) {
|
|
|
|
|
doom_close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SpellSelectFlag) {
|
|
|
|
|
SetSpell();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
CheckStoreBtn();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool isShiftHeld = (modState & SDL_KMOD_SHIFT) != 0;
|
|
|
|
|
const bool isCtrlHeld = (modState & SDL_KMOD_CTRL) != 0;
|
|
|
|
|
|
|
|
|
|
if (!GetMainPanel().contains(MousePosition)) {
|
|
|
|
|
if (!gmenu_is_active() && !TryIconCurs()) {
|
|
|
|
|
if (QuestLogIsOpen && GetLeftPanel().contains(MousePosition)) {
|
|
|
|
|
QuestlogESC();
|
|
|
|
|
} else if (qtextflag) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
} else if (CharFlag && GetLeftPanel().contains(MousePosition)) {
|
|
|
|
|
CheckChrBtns();
|
|
|
|
|
} else if (invflag && GetRightPanel().contains(MousePosition)) {
|
|
|
|
|
if (!DropGoldFlag)
|
|
|
|
|
CheckInvItem(isShiftHeld, isCtrlHeld);
|
|
|
|
|
} else if (IsStashOpen && GetLeftPanel().contains(MousePosition)) {
|
|
|
|
|
if (!IsWithdrawGoldOpen)
|
|
|
|
|
CheckStashItem(MousePosition, isShiftHeld, isCtrlHeld);
|
|
|
|
|
CheckStashButtonPress(MousePosition);
|
|
|
|
|
} else if (SpellbookFlag && GetRightPanel().contains(MousePosition)) {
|
|
|
|
|
CheckSBook();
|
|
|
|
|
} else if (!MyPlayer->HoldItem.isEmpty()) {
|
|
|
|
|
if (!TryOpenDungeonWithMouse()) {
|
|
|
|
|
const Point currentPosition = MyPlayer->position.tile;
|
|
|
|
|
std::optional<Point> itemTile = FindAdjacentPositionForItem(currentPosition, GetDirection(currentPosition, cursPosition));
|
|
|
|
|
if (itemTile) {
|
|
|
|
|
NetSendCmdPItem(true, CMD_PUTITEM, *itemTile, MyPlayer->HoldItem);
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
CheckLevelButton();
|
|
|
|
|
if (!LevelButtonDown)
|
|
|
|
|
LeftMouseCmd(isShiftHeld);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!ChatFlag && !DropGoldFlag && !IsWithdrawGoldOpen && !gmenu_is_active())
|
|
|
|
|
CheckInvScrn(isShiftHeld, isCtrlHeld);
|
|
|
|
|
CheckMainPanelButton();
|
|
|
|
|
CheckStashButtonPress(MousePosition);
|
|
|
|
|
if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM)
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LeftMouseUp(uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
gmenu_left_mouse(false);
|
|
|
|
|
CheckMuteButtonUp();
|
|
|
|
|
if (MainPanelButtonDown)
|
|
|
|
|
CheckMainPanelButtonUp();
|
|
|
|
|
CheckStashButtonRelease(MousePosition);
|
|
|
|
|
if (CharPanelButtonActive) {
|
|
|
|
|
const bool isShiftHeld = (modState & SDL_KMOD_SHIFT) != 0;
|
|
|
|
|
ReleaseChrBtns(isShiftHeld);
|
|
|
|
|
}
|
|
|
|
|
if (LevelButtonDown)
|
|
|
|
|
CheckLevelButtonUp();
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
ReleaseStoreBtn();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RightMouseDown(bool isShiftHeld)
|
|
|
|
|
{
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
|
|
|
|
|
if (gmenu_is_active() || sgnTimeoutCurs != CURSOR_NONE || PauseMode == 2 || MyPlayer->_pInvincible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (qtextflag) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DoomFlag) {
|
|
|
|
|
doom_close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
if (SpellSelectFlag) {
|
|
|
|
|
SetSpell();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (SpellbookFlag && GetRightPanel().contains(MousePosition))
|
|
|
|
|
return;
|
|
|
|
|
if (TryIconCurs())
|
|
|
|
|
return;
|
|
|
|
|
if (pcursinvitem != -1 && UseInvItem(pcursinvitem))
|
|
|
|
|
return;
|
|
|
|
|
if (pcursstashitem != StashStruct::EmptyCell && UseStashItem(pcursstashitem))
|
|
|
|
|
return;
|
|
|
|
|
if (DidRightClickPartyPortrait())
|
|
|
|
|
return;
|
|
|
|
|
if (pcurs == CURSOR_HAND) {
|
|
|
|
|
CheckPlrSpell(isShiftHeld);
|
|
|
|
|
} else if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ReleaseKey(SDL_Keycode vkey)
|
|
|
|
|
{
|
|
|
|
|
remap_keyboard_key(&vkey);
|
|
|
|
|
if (sgnTimeoutCurs != CURSOR_NONE)
|
|
|
|
|
return;
|
|
|
|
|
KeymapperRelease(vkey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClosePanels()
|
|
|
|
|
{
|
|
|
|
|
if (CanPanelsCoverView()) {
|
|
|
|
|
if (!IsLeftPanelOpen() && IsRightPanelOpen() && MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition + Displacement { 160, 0 });
|
|
|
|
|
} else if (!IsRightPanelOpen() && IsLeftPanelOpen() && MousePosition.x > 160 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition - Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CloseInventory();
|
|
|
|
|
CloseCharPanel();
|
|
|
|
|
SpellbookFlag = false;
|
|
|
|
|
QuestLogIsOpen = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PressKey(SDL_Keycode vkey, uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
Options &options = GetOptions();
|
|
|
|
|
remap_keyboard_key(&vkey);
|
|
|
|
|
|
|
|
|
|
if (vkey == SDLK_UNKNOWN)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (gmenu_presskeys(vkey) || CheckKeypress(vkey)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (MyPlayerIsDead) {
|
|
|
|
|
if (vkey == SDLK_ESCAPE) {
|
|
|
|
|
if (!gbIsMultiplayer) {
|
|
|
|
|
if (gbValidSaveFile)
|
|
|
|
|
gamemenu_load_game(false);
|
|
|
|
|
else
|
|
|
|
|
gamemenu_exit_game(false);
|
|
|
|
|
} else {
|
|
|
|
|
NetSendCmd(true, CMD_RETOWN);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sgnTimeoutCurs != CURSOR_NONE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
KeymapperPress(vkey);
|
|
|
|
|
if (vkey == SDLK_RETURN || vkey == SDLK_KP_ENTER) {
|
|
|
|
|
if ((modState & SDL_KMOD_ALT) != 0) {
|
|
|
|
|
options.Graphics.fullscreen.SetValue(!IsFullScreen());
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
} else {
|
|
|
|
|
TypeChatMessage();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (vkey != SDLK_ESCAPE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Disallow player from accessing escape menu during the frames before the death message appears
|
|
|
|
|
if (vkey == SDLK_ESCAPE && MyPlayer->_pHitPoints > 0) {
|
|
|
|
|
if (!PressEscKey()) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
gamemenu_on();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DropGoldFlag) {
|
|
|
|
|
control_drop_gold(vkey);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (IsWithdrawGoldOpen) {
|
|
|
|
|
WithdrawGoldKeyPress(vkey);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sgnTimeoutCurs != CURSOR_NONE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeymapperPress(vkey);
|
|
|
|
|
|
|
|
|
|
if (PauseMode == 2) {
|
|
|
|
|
if ((vkey == SDLK_RETURN || vkey == SDLK_KP_ENTER) && (modState & SDL_KMOD_ALT) != 0) {
|
|
|
|
|
options.Graphics.fullscreen.SetValue(!IsFullScreen());
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DoomFlag) {
|
|
|
|
|
doom_close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (vkey) {
|
|
|
|
|
case SDLK_PLUS:
|
|
|
|
|
case SDLK_KP_PLUS:
|
|
|
|
|
case SDLK_EQUALS:
|
|
|
|
|
case SDLK_KP_EQUALS:
|
|
|
|
|
if (AutomapActive) {
|
|
|
|
|
AutomapZoomIn();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_MINUS:
|
|
|
|
|
case SDLK_KP_MINUS:
|
|
|
|
|
case SDLK_UNDERSCORE:
|
|
|
|
|
if (AutomapActive) {
|
|
|
|
|
AutomapZoomOut();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
case SDLK_V:
|
|
|
|
|
if ((modState & SDL_KMOD_SHIFT) != 0)
|
|
|
|
|
NextDebugMonster();
|
|
|
|
|
else
|
|
|
|
|
GetDebugMonster();
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
case SDLK_RETURN:
|
|
|
|
|
case SDLK_KP_ENTER:
|
|
|
|
|
if ((modState & SDL_KMOD_ALT) != 0) {
|
|
|
|
|
options.Graphics.fullscreen.SetValue(!IsFullScreen());
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
} else if (IsPlayerInStore()) {
|
|
|
|
|
StoreEnter();
|
|
|
|
|
} else if (QuestLogIsOpen) {
|
|
|
|
|
QuestlogEnter();
|
|
|
|
|
} else {
|
|
|
|
|
TypeChatMessage();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_UP:
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreUp();
|
|
|
|
|
} else if (QuestLogIsOpen) {
|
|
|
|
|
QuestlogUp();
|
|
|
|
|
} else if (HelpFlag) {
|
|
|
|
|
HelpScrollUp();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollUp();
|
|
|
|
|
} else if (AutomapActive) {
|
|
|
|
|
AutomapUp();
|
|
|
|
|
} else if (IsStashOpen) {
|
|
|
|
|
Stash.PreviousPage();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_DOWN:
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreDown();
|
|
|
|
|
} else if (QuestLogIsOpen) {
|
|
|
|
|
QuestlogDown();
|
|
|
|
|
} else if (HelpFlag) {
|
|
|
|
|
HelpScrollDown();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollDown();
|
|
|
|
|
} else if (AutomapActive) {
|
|
|
|
|
AutomapDown();
|
|
|
|
|
} else if (IsStashOpen) {
|
|
|
|
|
Stash.NextPage();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_PAGEUP:
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StorePrior();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollTop();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_PAGEDOWN:
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreNext();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollBottom();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_LEFT:
|
|
|
|
|
if (AutomapActive && !ChatFlag)
|
|
|
|
|
AutomapLeft();
|
|
|
|
|
return;
|
|
|
|
|
case SDLK_RIGHT:
|
|
|
|
|
if (AutomapActive && !ChatFlag)
|
|
|
|
|
AutomapRight();
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandleMouseButtonDown(Uint8 button, uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore() && (button == SDL_BUTTON_X1
|
|
|
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
|| button == 8
|
|
|
|
|
#endif
|
|
|
|
|
)) {
|
|
|
|
|
StoreESC();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (button) {
|
|
|
|
|
case SDL_BUTTON_LEFT:
|
|
|
|
|
if (sgbMouseDown == CLICK_NONE) {
|
|
|
|
|
sgbMouseDown = CLICK_LEFT;
|
|
|
|
|
LeftMouseDown(modState);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case SDL_BUTTON_RIGHT:
|
|
|
|
|
if (sgbMouseDown == CLICK_NONE) {
|
|
|
|
|
sgbMouseDown = CLICK_RIGHT;
|
|
|
|
|
RightMouseDown((modState & SDL_KMOD_SHIFT) != 0);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
KeymapperPress(static_cast<SDL_Keycode>(button | KeymapperMouseButtonMask));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HandleMouseButtonUp(Uint8 button, uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
if (sgbMouseDown == CLICK_LEFT && button == SDL_BUTTON_LEFT) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
LeftMouseUp(modState);
|
|
|
|
|
} else if (sgbMouseDown == CLICK_RIGHT && button == SDL_BUTTON_RIGHT) {
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
} else {
|
|
|
|
|
KeymapperRelease(static_cast<SDL_Keycode>(button | KeymapperMouseButtonMask));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[[maybe_unused]] void LogUnhandledEvent(const char *name, int value)
|
|
|
|
|
{
|
|
|
|
|
LogVerbose("Unhandled SDL event: {} {}", name, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrepareForFadeIn()
|
|
|
|
|
{
|
|
|
|
|
if (HeadlessMode) return;
|
|
|
|
|
BlackPalette();
|
|
|
|
|
|
|
|
|
|
// Render the game to the buffer(s) with a fully black palette.
|
|
|
|
|
// Palette fade-in will gradually make it visible.
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
while (IsRedrawEverything()) {
|
|
|
|
|
DrawAndBlit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameEventHandler(const SDL_Event &event, uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
[[maybe_unused]] const Options &options = GetOptions();
|
|
|
|
|
StaticVector<ControllerButtonEvent, 4> ctrlEvents = ToControllerButtonEvents(event);
|
|
|
|
|
for (const ControllerButtonEvent ctrlEvent : ctrlEvents) {
|
|
|
|
|
GameAction action;
|
|
|
|
|
if (HandleControllerButtonEvent(event, ctrlEvent, action) && action.type == GameActionType_SEND_KEY) {
|
|
|
|
|
if ((action.send_key.vk_code & KeymapperMouseButtonMask) != 0) {
|
|
|
|
|
const unsigned button = action.send_key.vk_code & ~KeymapperMouseButtonMask;
|
|
|
|
|
if (!action.send_key.up)
|
|
|
|
|
HandleMouseButtonDown(static_cast<Uint8>(button), modState);
|
|
|
|
|
else
|
|
|
|
|
HandleMouseButtonUp(static_cast<Uint8>(button), modState);
|
|
|
|
|
} else {
|
|
|
|
|
if (!action.send_key.up)
|
|
|
|
|
PressKey(static_cast<SDL_Keycode>(action.send_key.vk_code), modState);
|
|
|
|
|
else
|
|
|
|
|
ReleaseKey(static_cast<SDL_Keycode>(action.send_key.vk_code));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ctrlEvents.size() > 0 && ctrlEvents[0].button != ControllerButton_NONE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
if (ConsoleHandleEvent(event)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (IsChatActive() && HandleTalkTextInputEvent(event)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (DropGoldFlag && HandleGoldDropTextInputEvent(event)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (IsWithdrawGoldOpen && HandleGoldWithdrawTextInputEvent(event)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case SDL_EVENT_KEY_DOWN:
|
|
|
|
|
PressKey(SDLC_EventKey(event), modState);
|
|
|
|
|
return;
|
|
|
|
|
case SDL_EVENT_KEY_UP:
|
|
|
|
|
ReleaseKey(SDLC_EventKey(event));
|
|
|
|
|
return;
|
|
|
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
|
|
|
if (ControlMode == ControlTypes::KeyboardAndMouse && invflag)
|
|
|
|
|
InvalidateInventorySlot();
|
|
|
|
|
MousePosition = { SDLC_EventMotionIntX(event), SDLC_EventMotionIntY(event) };
|
|
|
|
|
gmenu_on_mouse_move();
|
|
|
|
|
return;
|
|
|
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
|
|
|
MousePosition = { SDLC_EventButtonIntX(event), SDLC_EventButtonIntY(event) };
|
|
|
|
|
HandleMouseButtonDown(event.button.button, modState);
|
|
|
|
|
return;
|
|
|
|
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
|
|
|
MousePosition = { SDLC_EventButtonIntX(event), SDLC_EventButtonIntY(event) };
|
|
|
|
|
HandleMouseButtonUp(event.button.button, modState);
|
|
|
|
|
return;
|
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
case SDL_EVENT_MOUSE_WHEEL:
|
|
|
|
|
if (SDLC_EventWheelIntY(event)) { // Up
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreUp();
|
|
|
|
|
} else if (QuestLogIsOpen) {
|
|
|
|
|
QuestlogUp();
|
|
|
|
|
} else if (HelpFlag) {
|
|
|
|
|
HelpScrollUp();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollUp();
|
|
|
|
|
} else if (IsStashOpen) {
|
|
|
|
|
Stash.PreviousPage();
|
|
|
|
|
} else if (SDL_GetModState() & SDL_KMOD_CTRL) {
|
|
|
|
|
if (AutomapActive) {
|
|
|
|
|
AutomapZoomIn();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
KeymapperPress(MouseScrollUpButton);
|
|
|
|
|
}
|
|
|
|
|
} else if (SDLC_EventWheelIntY(event)) { // down
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreDown();
|
|
|
|
|
} else if (QuestLogIsOpen) {
|
|
|
|
|
QuestlogDown();
|
|
|
|
|
} else if (HelpFlag) {
|
|
|
|
|
HelpScrollDown();
|
|
|
|
|
} else if (ChatLogFlag) {
|
|
|
|
|
ChatLogScrollDown();
|
|
|
|
|
} else if (IsStashOpen) {
|
|
|
|
|
Stash.NextPage();
|
|
|
|
|
} else if (SDL_GetModState() & SDL_KMOD_CTRL) {
|
|
|
|
|
if (AutomapActive) {
|
|
|
|
|
AutomapZoomOut();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
KeymapperPress(MouseScrollDownButton);
|
|
|
|
|
}
|
|
|
|
|
} else if (SDLC_EventWheelIntX(event) > 0) { // left
|
|
|
|
|
KeymapperPress(MouseScrollLeftButton);
|
|
|
|
|
} else if (SDLC_EventWheelIntX(event) < 0) { // right
|
|
|
|
|
KeymapperPress(MouseScrollRightButton);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
if (IsCustomEvent(event.type)) {
|
|
|
|
|
if (gbIsMultiplayer)
|
|
|
|
|
pfile_write_hero();
|
|
|
|
|
nthread_ignore_mutex(true);
|
|
|
|
|
PaletteFadeOut(8);
|
|
|
|
|
sound_stop();
|
|
|
|
|
ShowProgress(GetCustomEvent(event));
|
|
|
|
|
|
|
|
|
|
PrepareForFadeIn();
|
|
|
|
|
LoadPWaterPalette();
|
|
|
|
|
if (gbRunGame)
|
|
|
|
|
PaletteFadeIn(8);
|
|
|
|
|
nthread_ignore_mutex(false);
|
|
|
|
|
gbGameLoopStartup = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
MainWndProc(event);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RunGameLoop(interface_mode uMsg)
|
|
|
|
|
{
|
|
|
|
|
demo::NotifyGameLoopStart();
|
|
|
|
|
|
|
|
|
|
nthread_ignore_mutex(true);
|
|
|
|
|
StartGame(uMsg);
|
|
|
|
|
assert(HeadlessMode || ghMainWnd);
|
|
|
|
|
EventHandler previousHandler = SetEventHandler(GameEventHandler);
|
|
|
|
|
run_delta_info();
|
|
|
|
|
gbRunGame = true;
|
|
|
|
|
gbProcessPlayers = IsDiabloAlive(true);
|
|
|
|
|
gbRunGameResult = true;
|
|
|
|
|
|
|
|
|
|
PrepareForFadeIn();
|
|
|
|
|
LoadPWaterPalette();
|
|
|
|
|
PaletteFadeIn(8);
|
|
|
|
|
InitBackbufferState();
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
gbGameLoopStartup = true;
|
|
|
|
|
nthread_ignore_mutex(false);
|
|
|
|
|
|
|
|
|
|
discord_manager::StartGame();
|
|
|
|
|
LuaEvent("GameStart");
|
|
|
|
|
#ifdef GPERF_HEAP_FIRST_GAME_ITERATION
|
|
|
|
|
unsigned run_game_iteration = 0;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
while (gbRunGame) {
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
if (!gbGameLoopStartup && !DebugCmdsFromCommandLine.empty()) {
|
|
|
|
|
InitConsole();
|
|
|
|
|
for (const std::string &cmd : DebugCmdsFromCommandLine) {
|
|
|
|
|
RunInConsole(cmd);
|
|
|
|
|
}
|
|
|
|
|
DebugCmdsFromCommandLine.clear();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
SDL_Event event;
|
|
|
|
|
uint16_t modState;
|
|
|
|
|
while (FetchMessage(&event, &modState)) {
|
|
|
|
|
if (event.type == SDL_EVENT_QUIT) {
|
|
|
|
|
gbRunGameResult = false;
|
|
|
|
|
gbRunGame = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
HandleMessage(event, modState);
|
|
|
|
|
}
|
|
|
|
|
if (!gbRunGame)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
bool drawGame = true;
|
|
|
|
|
bool processInput = true;
|
|
|
|
|
const bool runGameLoop = demo::IsRunning() ? demo::GetRunGameLoop(drawGame, processInput) : nthread_has_500ms_passed(&drawGame);
|
|
|
|
|
if (demo::IsRecording())
|
|
|
|
|
demo::RecordGameLoopResult(runGameLoop);
|
|
|
|
|
|
|
|
|
|
discord_manager::UpdateGame();
|
|
|
|
|
|
|
|
|
|
if (!runGameLoop) {
|
|
|
|
|
if (processInput)
|
|
|
|
|
ProcessInput();
|
|
|
|
|
if (!drawGame)
|
|
|
|
|
continue;
|
|
|
|
|
RedrawViewport();
|
|
|
|
|
DrawAndBlit();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
multi_process_network_packets();
|
|
|
|
|
if (game_loop(gbGameLoopStartup))
|
|
|
|
|
diablo_color_cyc_logic();
|
|
|
|
|
gbGameLoopStartup = false;
|
|
|
|
|
if (drawGame)
|
|
|
|
|
DrawAndBlit();
|
|
|
|
|
#ifdef GPERF_HEAP_FIRST_GAME_ITERATION
|
|
|
|
|
if (run_game_iteration++ == 0)
|
|
|
|
|
HeapProfilerDump("first_game_iteration");
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
demo::NotifyGameLoopEnd();
|
|
|
|
|
|
|
|
|
|
if (gbIsMultiplayer) {
|
|
|
|
|
pfile_write_hero(/*writeGameData=*/false);
|
|
|
|
|
sfile_write_stash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PaletteFadeOut(8);
|
|
|
|
|
NewCursor(CURSOR_NONE);
|
|
|
|
|
ClearScreenBuffer();
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
scrollrt_draw_game_screen();
|
|
|
|
|
previousHandler = SetEventHandler(previousHandler);
|
|
|
|
|
assert(HeadlessMode || previousHandler == GameEventHandler);
|
|
|
|
|
FreeGame();
|
|
|
|
|
|
|
|
|
|
if (cineflag) {
|
|
|
|
|
cineflag = false;
|
|
|
|
|
DoEnding();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintWithRightPadding(std::string_view str, size_t width)
|
|
|
|
|
{
|
|
|
|
|
printInConsole(str);
|
|
|
|
|
if (str.size() >= width)
|
|
|
|
|
return;
|
|
|
|
|
printInConsole(std::string(width - str.size(), ' '));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintHelpOption(std::string_view flags, std::string_view description)
|
|
|
|
|
{
|
|
|
|
|
printInConsole(" ");
|
|
|
|
|
PrintWithRightPadding(flags, 20);
|
|
|
|
|
printInConsole(" ");
|
|
|
|
|
PrintWithRightPadding(description, 30);
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
FILE *SdlLogFile = nullptr;
|
|
|
|
|
|
|
|
|
|
extern "C" void SdlLogToFile(void *userdata, int category, SDL_LogPriority priority, const char *message)
|
|
|
|
|
{
|
|
|
|
|
FILE *file = reinterpret_cast<FILE *>(userdata);
|
|
|
|
|
static const char *const LogPriorityPrefixes[SDL_LOG_PRIORITY_COUNT] = {
|
|
|
|
|
"",
|
|
|
|
|
"VERBOSE",
|
|
|
|
|
"DEBUG",
|
|
|
|
|
"INFO",
|
|
|
|
|
"WARN",
|
|
|
|
|
"ERROR",
|
|
|
|
|
"CRITICAL"
|
|
|
|
|
};
|
|
|
|
|
std::fprintf(file, "%s: %s\n", LogPriorityPrefixes[priority], message);
|
|
|
|
|
std::fflush(file);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
[[noreturn]] void PrintHelpAndExit()
|
|
|
|
|
{
|
|
|
|
|
printInConsole((/* TRANSLATORS: Commandline Option */ "Options:"));
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
PrintHelpOption("-h, --help", _(/* TRANSLATORS: Commandline Option */ "Print this message and exit"));
|
|
|
|
|
PrintHelpOption("--version", _(/* TRANSLATORS: Commandline Option */ "Print the version and exit"));
|
|
|
|
|
PrintHelpOption("--data-dir", _(/* TRANSLATORS: Commandline Option */ "Specify the folder of diabdat.mpq"));
|
|
|
|
|
PrintHelpOption("--save-dir", _(/* TRANSLATORS: Commandline Option */ "Specify the folder of save files"));
|
|
|
|
|
PrintHelpOption("--config-dir", _(/* TRANSLATORS: Commandline Option */ "Specify the location of diablo.ini"));
|
|
|
|
|
PrintHelpOption("--lang", _(/* TRANSLATORS: Commandline Option */ "Specify the language code (e.g. en or pt_BR)"));
|
|
|
|
|
PrintHelpOption("-n", _(/* TRANSLATORS: Commandline Option */ "Skip startup videos"));
|
|
|
|
|
PrintHelpOption("-f", _(/* TRANSLATORS: Commandline Option */ "Display frames per second"));
|
|
|
|
|
PrintHelpOption("--verbose", _(/* TRANSLATORS: Commandline Option */ "Enable verbose logging"));
|
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
PrintHelpOption("--log-to-file <path>", _(/* TRANSLATORS: Commandline Option */ "Log to a file instead of stderr"));
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef DISABLE_DEMOMODE
|
|
|
|
|
PrintHelpOption("--record <#>", _(/* TRANSLATORS: Commandline Option */ "Record a demo file"));
|
|
|
|
|
PrintHelpOption("--demo <#>", _(/* TRANSLATORS: Commandline Option */ "Play a demo file"));
|
|
|
|
|
PrintHelpOption("--timedemo", _(/* TRANSLATORS: Commandline Option */ "Disable all frame limiting during demo playback"));
|
|
|
|
|
#endif
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
printInConsole(_(/* TRANSLATORS: Commandline Option */ "Game selection:"));
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
PrintHelpOption("--spawn", _(/* TRANSLATORS: Commandline Option */ "Force Shareware mode"));
|
|
|
|
|
PrintHelpOption("--diablo", _(/* TRANSLATORS: Commandline Option */ "Force Diablo mode"));
|
|
|
|
|
PrintHelpOption("--hellfire", _(/* TRANSLATORS: Commandline Option */ "Force Hellfire mode"));
|
|
|
|
|
printInConsole(_(/* TRANSLATORS: Commandline Option */ "Hellfire options:"));
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
printInConsole("Debug options:");
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
PrintHelpOption("-i", "Ignore network timeout");
|
|
|
|
|
PrintHelpOption("+<internal command>", "Pass commands to the engine");
|
|
|
|
|
#endif
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
printInConsole(_("Report bugs at https://github.com/diasurgical/devilutionX/"));
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
diablo_quit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintFlagMessage(std::string_view flag, std::string_view message)
|
|
|
|
|
{
|
|
|
|
|
printInConsole(flag);
|
|
|
|
|
printInConsole(message);
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintFlagRequiresArgument(std::string_view flag)
|
|
|
|
|
{
|
|
|
|
|
PrintFlagMessage(flag, " requires an argument");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DiabloParseFlags(int argc, char **argv)
|
|
|
|
|
{
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
int argumentIndexOfLastCommandPart = -1;
|
|
|
|
|
std::string currentCommand;
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef DISABLE_DEMOMODE
|
|
|
|
|
bool timedemo = false;
|
|
|
|
|
int demoNumber = -1;
|
|
|
|
|
int recordNumber = -1;
|
|
|
|
|
bool createDemoReference = false;
|
|
|
|
|
#endif
|
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
|
const std::string_view arg = argv[i];
|
|
|
|
|
if (arg == "-h" || arg == "--help") {
|
|
|
|
|
PrintHelpAndExit();
|
|
|
|
|
} else if (arg == "--version") {
|
|
|
|
|
printInConsole(PROJECT_NAME);
|
|
|
|
|
printInConsole(" v");
|
|
|
|
|
printInConsole(PROJECT_VERSION);
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
diablo_quit(0);
|
|
|
|
|
} else if (arg == "--data-dir") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--data-dir");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
paths::SetBasePath(argv[++i]);
|
|
|
|
|
} else if (arg == "--save-dir") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--save-dir");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
paths::SetPrefPath(argv[++i]);
|
|
|
|
|
} else if (arg == "--config-dir") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--config-dir");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
paths::SetConfigPath(argv[++i]);
|
|
|
|
|
} else if (arg == "--lang") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--lang");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
forceLocale = argv[++i];
|
|
|
|
|
#ifndef DISABLE_DEMOMODE
|
|
|
|
|
} else if (arg == "--demo") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--demo");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
ParseIntResult<int> parsedParam = ParseInt<int>(argv[++i]);
|
|
|
|
|
if (!parsedParam.has_value()) {
|
|
|
|
|
PrintFlagMessage("--demo", " must be a number");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
demoNumber = parsedParam.value();
|
|
|
|
|
gbShowIntro = false;
|
|
|
|
|
} else if (arg == "--timedemo") {
|
|
|
|
|
timedemo = true;
|
|
|
|
|
} else if (arg == "--record") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--record");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
ParseIntResult<int> parsedParam = ParseInt<int>(argv[++i]);
|
|
|
|
|
if (!parsedParam.has_value()) {
|
|
|
|
|
PrintFlagMessage("--record", " must be a number");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
recordNumber = parsedParam.value();
|
|
|
|
|
} else if (arg == "--create-reference") {
|
|
|
|
|
createDemoReference = true;
|
|
|
|
|
#else
|
|
|
|
|
} else if (arg == "--demo" || arg == "--timedemo" || arg == "--record" || arg == "--create-reference") {
|
|
|
|
|
printInConsole("Binary compiled without demo mode support.");
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
diablo_quit(1);
|
|
|
|
|
#endif
|
|
|
|
|
} else if (arg == "-n") {
|
|
|
|
|
gbShowIntro = false;
|
|
|
|
|
} else if (arg == "-f") {
|
|
|
|
|
EnableFrameCount();
|
|
|
|
|
} else if (arg == "--spawn") {
|
|
|
|
|
forceSpawn = true;
|
|
|
|
|
} else if (arg == "--diablo") {
|
|
|
|
|
forceDiablo = true;
|
|
|
|
|
} else if (arg == "--hellfire") {
|
|
|
|
|
forceHellfire = true;
|
|
|
|
|
} else if (arg == "--vanilla") {
|
|
|
|
|
gbVanilla = true;
|
|
|
|
|
} else if (arg == "--verbose") {
|
|
|
|
|
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
|
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
} else if (arg == "--log-to-file") {
|
|
|
|
|
if (i + 1 == argc) {
|
|
|
|
|
PrintFlagRequiresArgument("--log-to-file");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
SdlLogFile = OpenFile(argv[++i], "wb");
|
|
|
|
|
if (SdlLogFile == nullptr) {
|
|
|
|
|
printInConsole("Failed to open log file for writing");
|
|
|
|
|
diablo_quit(64);
|
|
|
|
|
}
|
|
|
|
|
SDL_SetLogOutputFunction(&SdlLogToFile, /*userdata=*/SdlLogFile);
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
} else if (arg == "-i") {
|
|
|
|
|
DebugDisableNetworkTimeout = true;
|
|
|
|
|
} else if (arg[0] == '+') {
|
|
|
|
|
if (!currentCommand.empty())
|
|
|
|
|
DebugCmdsFromCommandLine.push_back(currentCommand);
|
|
|
|
|
argumentIndexOfLastCommandPart = i;
|
|
|
|
|
currentCommand = arg.substr(1);
|
|
|
|
|
} else if (arg[0] != '-' && (argumentIndexOfLastCommandPart + 1) == i) {
|
|
|
|
|
currentCommand.append(" ");
|
|
|
|
|
currentCommand.append(arg);
|
|
|
|
|
argumentIndexOfLastCommandPart = i;
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
printInConsole("unrecognized option '");
|
|
|
|
|
printInConsole(argv[i]);
|
|
|
|
|
printInConsole("'");
|
|
|
|
|
printNewlineInConsole();
|
|
|
|
|
PrintHelpAndExit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
if (!currentCommand.empty())
|
|
|
|
|
DebugCmdsFromCommandLine.push_back(currentCommand);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifndef DISABLE_DEMOMODE
|
|
|
|
|
if (demoNumber != -1)
|
|
|
|
|
demo::InitPlayBack(demoNumber, timedemo);
|
|
|
|
|
if (recordNumber != -1)
|
|
|
|
|
demo::InitRecording(recordNumber, createDemoReference);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DiabloInitScreen()
|
|
|
|
|
{
|
|
|
|
|
MousePosition = { gnScreenWidth / 2, gnScreenHeight / 2 };
|
|
|
|
|
if (ControlMode == ControlTypes::KeyboardAndMouse)
|
|
|
|
|
SetCursorPos(MousePosition);
|
|
|
|
|
|
|
|
|
|
ClrDiabloMsg();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetApplicationVersions()
|
|
|
|
|
{
|
|
|
|
|
*BufCopy(gszProductName, PROJECT_NAME, " v", PROJECT_VERSION) = '\0';
|
|
|
|
|
*BufCopy(gszVersionNumber, "version ", PROJECT_VERSION) = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CheckArchivesUpToDate()
|
|
|
|
|
{
|
|
|
|
|
const bool devilutionxMpqOutOfDate = IsDevilutionXMpqOutOfDate();
|
|
|
|
|
const bool fontsMpqOutOfDate = AreExtraFontsOutOfDate();
|
|
|
|
|
|
|
|
|
|
if (devilutionxMpqOutOfDate && fontsMpqOutOfDate) {
|
|
|
|
|
app_fatal(_("Please update devilutionx.mpq and fonts.mpq to the latest version"));
|
|
|
|
|
} else if (devilutionxMpqOutOfDate) {
|
|
|
|
|
app_fatal(_("Failed to load UI resources.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Make sure devilutionx.mpq is in the game folder and that it is up to date."));
|
|
|
|
|
} else if (fontsMpqOutOfDate) {
|
|
|
|
|
app_fatal(_("Please update fonts.mpq to the latest version"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ApplicationInit()
|
|
|
|
|
{
|
|
|
|
|
if (*GetOptions().Graphics.showFPS)
|
|
|
|
|
EnableFrameCount();
|
|
|
|
|
|
|
|
|
|
init_create_window();
|
|
|
|
|
was_window_init = true;
|
|
|
|
|
|
|
|
|
|
InitializeScreenReader();
|
|
|
|
|
LanguageInitialize();
|
|
|
|
|
|
|
|
|
|
SetApplicationVersions();
|
|
|
|
|
|
|
|
|
|
ReadOnlyTest();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DiabloInit()
|
|
|
|
|
{
|
|
|
|
|
if (forceSpawn || *GetOptions().GameMode.shareware)
|
|
|
|
|
gbIsSpawn = true;
|
|
|
|
|
|
|
|
|
|
bool wasHellfireDiscovered = false;
|
|
|
|
|
if (!forceDiablo && !forceHellfire)
|
|
|
|
|
wasHellfireDiscovered = (HaveHellfire() && *GetOptions().GameMode.gameMode == StartUpGameMode::Ask);
|
|
|
|
|
bool enableHellfire = forceHellfire || wasHellfireDiscovered;
|
|
|
|
|
if (!forceDiablo && *GetOptions().GameMode.gameMode == StartUpGameMode::Hellfire) { // Migrate legacy options
|
|
|
|
|
GetOptions().GameMode.gameMode.SetValue(StartUpGameMode::Diablo);
|
|
|
|
|
enableHellfire = true;
|
|
|
|
|
}
|
|
|
|
|
if (forceDiablo || enableHellfire) {
|
|
|
|
|
GetOptions().Mods.SetHellfireEnabled(enableHellfire);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gbIsHellfireSaveGame = gbIsHellfire;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < QuickMessages.size(); i++) {
|
|
|
|
|
auto &messages = GetOptions().Chat.szHotKeyMsgs[i];
|
|
|
|
|
if (messages.empty()) {
|
|
|
|
|
messages.emplace_back(_(QuickMessages[i].message));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef USE_SDL1
|
|
|
|
|
InitializeVirtualGamepad();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
UiInitialize();
|
|
|
|
|
was_ui_init = true;
|
|
|
|
|
|
|
|
|
|
if (wasHellfireDiscovered) {
|
|
|
|
|
UiSelStartUpGameOption();
|
|
|
|
|
if (!gbIsHellfire) {
|
|
|
|
|
// Reinitialize the UI Elements because we changed the game
|
|
|
|
|
UnloadUiGFX();
|
|
|
|
|
UiInitialize();
|
|
|
|
|
if (IsHardwareCursor())
|
|
|
|
|
SetHardwareCursor(CursorInfo::UnknownCursor());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DiabloInitScreen();
|
|
|
|
|
|
|
|
|
|
snd_init();
|
|
|
|
|
|
|
|
|
|
ui_sound_init();
|
|
|
|
|
|
|
|
|
|
// Item graphics are loaded early, they already get touched during hero selection.
|
|
|
|
|
InitItemGFX();
|
|
|
|
|
|
|
|
|
|
// Always available.
|
|
|
|
|
LoadSmallSelectionSpinner();
|
|
|
|
|
|
|
|
|
|
CheckArchivesUpToDate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DiabloSplash()
|
|
|
|
|
{
|
|
|
|
|
if (!gbShowIntro)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (*GetOptions().StartUp.splash == StartUpSplash::LogoAndTitleDialog)
|
|
|
|
|
play_movie("gendata\\logo.smk", true);
|
|
|
|
|
|
|
|
|
|
auto &intro = gbIsHellfire ? GetOptions().StartUp.hellfireIntro : GetOptions().StartUp.diabloIntro;
|
|
|
|
|
|
|
|
|
|
if (*intro != StartUpIntro::Off) {
|
|
|
|
|
if (gbIsHellfire)
|
|
|
|
|
play_movie("gendata\\Hellfire.smk", true);
|
|
|
|
|
else
|
|
|
|
|
play_movie("gendata\\diablo1.smk", true);
|
|
|
|
|
if (*intro == StartUpIntro::Once) {
|
|
|
|
|
intro.SetValue(StartUpIntro::Off);
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsAnyOf(*GetOptions().StartUp.splash, StartUpSplash::TitleDialog, StartUpSplash::LogoAndTitleDialog))
|
|
|
|
|
UiTitleDialog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DiabloDeinit()
|
|
|
|
|
{
|
|
|
|
|
FreeItemGFX();
|
|
|
|
|
|
|
|
|
|
LuaShutdown();
|
|
|
|
|
ShutDownScreenReader();
|
|
|
|
|
|
|
|
|
|
if (gbSndInited)
|
|
|
|
|
effects_cleanup_sfx();
|
|
|
|
|
snd_deinit();
|
|
|
|
|
if (was_ui_init)
|
|
|
|
|
UiDestroy();
|
|
|
|
|
if (was_archives_init)
|
|
|
|
|
init_cleanup();
|
|
|
|
|
if (was_window_init)
|
|
|
|
|
dx_cleanup(); // Cleanup SDL surfaces stuff, so we have to do it before SDL_Quit().
|
|
|
|
|
UnloadFonts();
|
|
|
|
|
if (SDL_WasInit((~0U) & ~SDL_INIT_HAPTIC) != 0)
|
|
|
|
|
SDL_Quit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadLvlGFX()
|
|
|
|
|
{
|
|
|
|
|
assert(pDungeonCels == nullptr);
|
|
|
|
|
constexpr int SpecialCelWidth = 64;
|
|
|
|
|
|
|
|
|
|
const auto loadAll = [](const char *cel, const char *til, const char *special) -> tl::expected<void, std::string> {
|
|
|
|
|
ASSIGN_OR_RETURN(pDungeonCels, LoadFileInMemWithStatus(cel));
|
|
|
|
|
ASSIGN_OR_RETURN(pMegaTiles, LoadFileInMemWithStatus<MegaTile>(til));
|
|
|
|
|
ASSIGN_OR_RETURN(pSpecialCels, LoadCelWithStatus(special, SpecialCelWidth));
|
|
|
|
|
return {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
switch (leveltype) {
|
|
|
|
|
case DTYPE_TOWN: {
|
|
|
|
|
auto cel = LoadFileInMemWithStatus("nlevels\\towndata\\town.cel");
|
|
|
|
|
if (!cel.has_value()) {
|
|
|
|
|
ASSIGN_OR_RETURN(pDungeonCels, LoadFileInMemWithStatus("levels\\towndata\\town.cel"));
|
|
|
|
|
} else {
|
|
|
|
|
pDungeonCels = std::move(*cel);
|
|
|
|
|
}
|
|
|
|
|
auto til = LoadFileInMemWithStatus<MegaTile>("nlevels\\towndata\\town.til");
|
|
|
|
|
if (!til.has_value()) {
|
|
|
|
|
ASSIGN_OR_RETURN(pMegaTiles, LoadFileInMemWithStatus<MegaTile>("levels\\towndata\\town.til"));
|
|
|
|
|
} else {
|
|
|
|
|
pMegaTiles = std::move(*til);
|
|
|
|
|
}
|
|
|
|
|
ASSIGN_OR_RETURN(pSpecialCels, LoadCelWithStatus("levels\\towndata\\towns", SpecialCelWidth));
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
case DTYPE_CATHEDRAL:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"levels\\l1data\\l1.cel",
|
|
|
|
|
"levels\\l1data\\l1.til",
|
|
|
|
|
"levels\\l1data\\l1s");
|
|
|
|
|
case DTYPE_CATACOMBS:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"levels\\l2data\\l2.cel",
|
|
|
|
|
"levels\\l2data\\l2.til",
|
|
|
|
|
"levels\\l2data\\l2s");
|
|
|
|
|
case DTYPE_CAVES:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"levels\\l3data\\l3.cel",
|
|
|
|
|
"levels\\l3data\\l3.til",
|
|
|
|
|
"levels\\l1data\\l1s");
|
|
|
|
|
case DTYPE_HELL:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"levels\\l4data\\l4.cel",
|
|
|
|
|
"levels\\l4data\\l4.til",
|
|
|
|
|
"levels\\l2data\\l2s");
|
|
|
|
|
case DTYPE_NEST:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"nlevels\\l6data\\l6.cel",
|
|
|
|
|
"nlevels\\l6data\\l6.til",
|
|
|
|
|
"levels\\l1data\\l1s");
|
|
|
|
|
case DTYPE_CRYPT:
|
|
|
|
|
return loadAll(
|
|
|
|
|
"nlevels\\l5data\\l5.cel",
|
|
|
|
|
"nlevels\\l5data\\l5.til",
|
|
|
|
|
"nlevels\\l5data\\l5s");
|
|
|
|
|
default:
|
|
|
|
|
return tl::make_unexpected("LoadLvlGFX");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadAllGFX()
|
|
|
|
|
{
|
|
|
|
|
IncProgress();
|
|
|
|
|
#if !defined(USE_SDL1) && !defined(__vita__)
|
|
|
|
|
InitVirtualGamepadGFX();
|
|
|
|
|
#endif
|
|
|
|
|
IncProgress();
|
|
|
|
|
RETURN_IF_ERROR(InitObjectGFX());
|
|
|
|
|
IncProgress();
|
|
|
|
|
RETURN_IF_ERROR(InitMissileGFX());
|
|
|
|
|
IncProgress();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param entry Where is the player entering from
|
|
|
|
|
*/
|
|
|
|
|
void CreateLevel(lvl_entry entry)
|
|
|
|
|
{
|
|
|
|
|
CreateDungeon(DungeonSeeds[currlevel], entry);
|
|
|
|
|
|
|
|
|
|
switch (leveltype) {
|
|
|
|
|
case DTYPE_TOWN:
|
|
|
|
|
InitTownTriggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_CATHEDRAL:
|
|
|
|
|
InitL1Triggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_CATACOMBS:
|
|
|
|
|
InitL2Triggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_CAVES:
|
|
|
|
|
InitL3Triggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_HELL:
|
|
|
|
|
InitL4Triggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_NEST:
|
|
|
|
|
InitHiveTriggers();
|
|
|
|
|
break;
|
|
|
|
|
case DTYPE_CRYPT:
|
|
|
|
|
InitCryptTriggers();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
app_fatal("CreateLevel");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
Freeupstairs();
|
|
|
|
|
}
|
|
|
|
|
LoadRndLvlPal(leveltype);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UnstuckChargers()
|
|
|
|
|
{
|
|
|
|
|
if (gbIsMultiplayer) {
|
|
|
|
|
for (Player &player : Players) {
|
|
|
|
|
if (!player.plractive)
|
|
|
|
|
continue;
|
|
|
|
|
if (player._pLvlChanging)
|
|
|
|
|
continue;
|
|
|
|
|
if (!player.isOnActiveLevel())
|
|
|
|
|
continue;
|
|
|
|
|
if (&player == MyPlayer)
|
|
|
|
|
continue;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < ActiveMonsterCount; i++) {
|
|
|
|
|
Monster &monster = Monsters[ActiveMonsters[i]];
|
|
|
|
|
if (monster.mode == MonsterMode::Charge)
|
|
|
|
|
monster.mode = MonsterMode::Stand;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UpdateMonsterLights()
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < ActiveMonsterCount; i++) {
|
|
|
|
|
Monster &monster = Monsters[ActiveMonsters[i]];
|
|
|
|
|
|
|
|
|
|
if ((monster.flags & MFLAG_BERSERK) != 0) {
|
|
|
|
|
const int lightRadius = leveltype == DTYPE_NEST ? 9 : 3;
|
|
|
|
|
monster.lightId = AddLight(monster.position.tile, lightRadius);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (monster.lightId != NO_LIGHT) {
|
|
|
|
|
if (monster.lightId == MyPlayer->lightId) { // Fix old saves where some monsters had 0 instead of NO_LIGHT
|
|
|
|
|
monster.lightId = NO_LIGHT;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Light &light = Lights[monster.lightId];
|
|
|
|
|
if (monster.position.tile != light.position.tile) {
|
|
|
|
|
ChangeLightXY(monster.lightId, monster.position.tile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameLogic()
|
|
|
|
|
{
|
|
|
|
|
if (!ProcessInput()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (gbProcessPlayers) {
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessPlayers;
|
|
|
|
|
ProcessPlayers();
|
|
|
|
|
}
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessMonsters;
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
if (!DebugInvisible)
|
|
|
|
|
#endif
|
|
|
|
|
ProcessMonsters();
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessObjects;
|
|
|
|
|
ProcessObjects();
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessMissiles;
|
|
|
|
|
ProcessMissiles();
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessItems;
|
|
|
|
|
ProcessItems();
|
|
|
|
|
ProcessLightList();
|
|
|
|
|
ProcessVisionList();
|
|
|
|
|
} else {
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessTowners;
|
|
|
|
|
ProcessTowners();
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessItemsTown;
|
|
|
|
|
ProcessItems();
|
|
|
|
|
gGameLogicStep = GameLogicStep::ProcessMissilesTown;
|
|
|
|
|
ProcessMissiles();
|
|
|
|
|
}
|
|
|
|
|
gGameLogicStep = GameLogicStep::None;
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
if (DebugScrollViewEnabled && (SDL_GetModState() & SDL_KMOD_SHIFT) != 0) {
|
|
|
|
|
ScrollView();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
sound_update();
|
|
|
|
|
CheckTriggers();
|
|
|
|
|
CheckQuests();
|
|
|
|
|
RedrawViewport();
|
|
|
|
|
pfile_update(false);
|
|
|
|
|
|
|
|
|
|
plrctrls_after_game_logic();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TimeoutCursor(bool bTimeout)
|
|
|
|
|
{
|
|
|
|
|
if (bTimeout) {
|
|
|
|
|
if (sgnTimeoutCurs == CURSOR_NONE && sgbMouseDown == CLICK_NONE) {
|
|
|
|
|
sgnTimeoutCurs = pcurs;
|
|
|
|
|
multi_net_ping();
|
|
|
|
|
InfoString = StringOrView {};
|
|
|
|
|
AddInfoBoxString(_("-- Network timeout --"));
|
|
|
|
|
AddInfoBoxString(_("-- Waiting for players --"));
|
|
|
|
|
NewCursor(CURSOR_HOURGLASS);
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
}
|
|
|
|
|
scrollrt_draw_game_screen();
|
|
|
|
|
} else if (sgnTimeoutCurs != CURSOR_NONE) {
|
|
|
|
|
// Timeout is gone, we should restore the previous cursor.
|
|
|
|
|
// But the timeout cursor could already be changed by the now processed messages (for example item cursor from CMD_GETITEM).
|
|
|
|
|
// Changing the item cursor back to the previous (hand) cursor could result in deleted items, because this resets Player.HoldItem (see NewCursor).
|
|
|
|
|
if (pcurs == CURSOR_HOURGLASS)
|
|
|
|
|
NewCursor(sgnTimeoutCurs);
|
|
|
|
|
sgnTimeoutCurs = CURSOR_NONE;
|
|
|
|
|
InfoString = StringOrView {};
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HelpKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (HelpFlag) {
|
|
|
|
|
HelpFlag = false;
|
|
|
|
|
} else if (IsPlayerInStore()) {
|
|
|
|
|
InfoString = StringOrView {};
|
|
|
|
|
AddInfoBoxString(_("No help available")); /// BUGFIX: message isn't displayed
|
|
|
|
|
AddInfoBoxString(_("while in stores"));
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
} else {
|
|
|
|
|
CloseInventory();
|
|
|
|
|
CloseCharPanel();
|
|
|
|
|
SpellbookFlag = false;
|
|
|
|
|
SpellSelectFlag = false;
|
|
|
|
|
if (qtextflag && leveltype == DTYPE_TOWN) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
}
|
|
|
|
|
QuestLogIsOpen = false;
|
|
|
|
|
CancelCurrentDiabloMsg();
|
|
|
|
|
gamemenu_off();
|
|
|
|
|
DisplayHelp();
|
|
|
|
|
doom_close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InventoryKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
invflag = !invflag;
|
|
|
|
|
if (!IsLeftPanelOpen() && CanPanelsCoverView()) {
|
|
|
|
|
if (!invflag) { // We closed the inventory
|
|
|
|
|
if (MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition + Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
} else if (!SpellbookFlag) { // We opened the inventory
|
|
|
|
|
if (MousePosition.x > 160 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition - Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SpellbookFlag = false;
|
|
|
|
|
CloseGoldWithdraw();
|
|
|
|
|
CloseStash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CharacterSheetKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
if (!IsRightPanelOpen() && CanPanelsCoverView()) {
|
|
|
|
|
if (CharFlag) { // We are closing the character sheet
|
|
|
|
|
if (MousePosition.x > 160 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition - Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
} else if (!QuestLogIsOpen) { // We opened the character sheet
|
|
|
|
|
if (MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition + Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ToggleCharPanel();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PartyPanelSideToggleKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
PartySidePanelOpen = !PartySidePanelOpen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QuestLogKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
if (!QuestLogIsOpen) {
|
|
|
|
|
StartQuestlog();
|
|
|
|
|
} else {
|
|
|
|
|
QuestLogIsOpen = false;
|
|
|
|
|
}
|
|
|
|
|
if (!IsRightPanelOpen() && CanPanelsCoverView()) {
|
|
|
|
|
if (!QuestLogIsOpen) { // We closed the quest log
|
|
|
|
|
if (MousePosition.x > 160 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition - Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
} else if (!CharFlag) { // We opened the character quest log
|
|
|
|
|
if (MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition + Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CloseCharPanel();
|
|
|
|
|
CloseGoldWithdraw();
|
|
|
|
|
CloseStash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DisplaySpellsKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
CloseCharPanel();
|
|
|
|
|
QuestLogIsOpen = false;
|
|
|
|
|
CloseInventory();
|
|
|
|
|
SpellbookFlag = false;
|
|
|
|
|
if (!SpellSelectFlag) {
|
|
|
|
|
DoSpeedBook();
|
|
|
|
|
} else {
|
|
|
|
|
SpellSelectFlag = false;
|
|
|
|
|
}
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpellBookKeyPressed()
|
|
|
|
|
{
|
|
|
|
|
if (IsPlayerInStore())
|
|
|
|
|
return;
|
|
|
|
|
SpellbookFlag = !SpellbookFlag;
|
|
|
|
|
if (!IsLeftPanelOpen() && CanPanelsCoverView()) {
|
|
|
|
|
if (!SpellbookFlag) { // We closed the inventory
|
|
|
|
|
if (MousePosition.x < 480 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition + Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
} else if (!invflag) { // We opened the inventory
|
|
|
|
|
if (MousePosition.x > 160 && MousePosition.y < GetMainPanel().position.y) {
|
|
|
|
|
SetCursorPos(MousePosition - Displacement { 160, 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CloseInventory();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CycleSpellHotkeys(bool next)
|
|
|
|
|
{
|
|
|
|
|
StaticVector<size_t, NumHotkeys> validHotKeyIndexes;
|
|
|
|
|
std::optional<size_t> currentIndex;
|
|
|
|
|
for (size_t slot = 0; slot < NumHotkeys; slot++) {
|
|
|
|
|
if (!IsValidSpeedSpell(slot))
|
|
|
|
|
continue;
|
|
|
|
|
if (MyPlayer->_pRSpell == MyPlayer->_pSplHotKey[slot] && MyPlayer->_pRSplType == MyPlayer->_pSplTHotKey[slot]) {
|
|
|
|
|
// found current
|
|
|
|
|
currentIndex = validHotKeyIndexes.size();
|
|
|
|
|
}
|
|
|
|
|
validHotKeyIndexes.emplace_back(slot);
|
|
|
|
|
}
|
|
|
|
|
if (validHotKeyIndexes.size() == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
size_t newIndex;
|
|
|
|
|
if (!currentIndex) {
|
|
|
|
|
newIndex = next ? 0 : (validHotKeyIndexes.size() - 1);
|
|
|
|
|
} else if (next) {
|
|
|
|
|
newIndex = (*currentIndex == validHotKeyIndexes.size() - 1) ? 0 : (*currentIndex + 1);
|
|
|
|
|
} else {
|
|
|
|
|
newIndex = *currentIndex == 0 ? (validHotKeyIndexes.size() - 1) : (*currentIndex - 1);
|
|
|
|
|
}
|
|
|
|
|
ToggleSpell(validHotKeyIndexes[newIndex]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsPlayerDead()
|
|
|
|
|
{
|
|
|
|
|
return MyPlayer->_pmode == PM_DEATH || MyPlayerIsDead;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsGameRunning()
|
|
|
|
|
{
|
|
|
|
|
return PauseMode != 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CanPlayerTakeAction()
|
|
|
|
|
{
|
|
|
|
|
return !IsPlayerDead() && IsGameRunning();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CanAutomapBeToggledOff()
|
|
|
|
|
{
|
|
|
|
|
// check if every window is closed - if yes, automap can be toggled off
|
|
|
|
|
if (!QuestLogIsOpen && !IsWithdrawGoldOpen && !IsStashOpen && !CharFlag
|
|
|
|
|
&& !SpellbookFlag && !invflag && !isGameMenuOpen && !qtextflag && !SpellSelectFlag
|
|
|
|
|
&& !ChatLogFlag && !HelpFlag)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OptionLanguageCodeChanged()
|
|
|
|
|
{
|
|
|
|
|
UnloadFonts();
|
|
|
|
|
LanguageInitialize();
|
|
|
|
|
LoadLanguageArchive();
|
|
|
|
|
effects_cleanup_sfx();
|
|
|
|
|
if (gbRunGame)
|
|
|
|
|
sound_init();
|
|
|
|
|
else
|
|
|
|
|
ui_sound_init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto OptionChangeHandlerLanguage = (GetOptions().Language.code.SetValueChangedCallback(OptionLanguageCodeChanged), true);
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void InitKeymapActions()
|
|
|
|
|
{
|
|
|
|
|
Options &options = GetOptions();
|
|
|
|
|
for (uint32_t i = 0; i < 8; ++i) {
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"BeltItem{}",
|
|
|
|
|
N_("Belt item {}"),
|
|
|
|
|
N_("Use Belt item."),
|
|
|
|
|
'1' + i,
|
|
|
|
|
[i] {
|
|
|
|
|
const Player &myPlayer = *MyPlayer;
|
|
|
|
|
if (!myPlayer.SpdList[i].isEmpty() && myPlayer.SpdList[i]._itype != ItemType::Gold) {
|
|
|
|
|
UseInvItem(INVITEM_BELT_FIRST + i);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction,
|
|
|
|
|
i + 1);
|
|
|
|
|
}
|
|
|
|
|
for (uint32_t i = 0; i < NumHotkeys; ++i) {
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickSpell{}",
|
|
|
|
|
N_("Quick spell {}"),
|
|
|
|
|
N_("Hotkey for skill or spell."),
|
|
|
|
|
i < 4 ? static_cast<uint32_t>(SDLK_F5) + i : static_cast<uint32_t>(SDLK_UNKNOWN),
|
|
|
|
|
[i]() {
|
|
|
|
|
if (SpellSelectFlag) {
|
|
|
|
|
SetSpeedSpell(i);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!*GetOptions().Gameplay.quickCast)
|
|
|
|
|
ToggleSpell(i);
|
|
|
|
|
else
|
|
|
|
|
QuickCast(i);
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction,
|
|
|
|
|
i + 1);
|
|
|
|
|
}
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickSpellPrevious",
|
|
|
|
|
N_("Previous quick spell"),
|
|
|
|
|
N_("Selects the previous quick spell (cycles)."),
|
|
|
|
|
MouseScrollUpButton,
|
|
|
|
|
[] { CycleSpellHotkeys(false); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickSpellNext",
|
|
|
|
|
N_("Next quick spell"),
|
|
|
|
|
N_("Selects the next quick spell (cycles)."),
|
|
|
|
|
MouseScrollDownButton,
|
|
|
|
|
[] { CycleSpellHotkeys(true); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"UseHealthPotion",
|
|
|
|
|
N_("Use health potion"),
|
|
|
|
|
N_("Use health potions from belt."),
|
|
|
|
|
SDLK_UNKNOWN,
|
|
|
|
|
[] { UseBeltItem(BeltItemType::Healing); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"UseManaPotion",
|
|
|
|
|
N_("Use mana potion"),
|
|
|
|
|
N_("Use mana potions from belt."),
|
|
|
|
|
SDLK_UNKNOWN,
|
|
|
|
|
[] { UseBeltItem(BeltItemType::Mana); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"DisplaySpells",
|
|
|
|
|
N_("Speedbook"),
|
|
|
|
|
N_("Open Speedbook."),
|
|
|
|
|
'S',
|
|
|
|
|
DisplaySpellsKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickSave",
|
|
|
|
|
N_("Quick save"),
|
|
|
|
|
N_("Saves the game."),
|
|
|
|
|
SDLK_F2,
|
|
|
|
|
[] { gamemenu_save_game(false); },
|
|
|
|
|
nullptr,
|
|
|
|
|
[&]() { return !gbIsMultiplayer && CanPlayerTakeAction(); });
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickLoad",
|
|
|
|
|
N_("Quick load"),
|
|
|
|
|
N_("Loads the game."),
|
|
|
|
|
SDLK_F3,
|
|
|
|
|
[] { gamemenu_load_game(false); },
|
|
|
|
|
nullptr,
|
|
|
|
|
[&]() { return !gbIsMultiplayer && gbValidSaveFile && !IsPlayerInStore() && IsGameRunning(); });
|
|
|
|
|
#ifndef NOEXIT
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuitGame",
|
|
|
|
|
N_("Quit game"),
|
|
|
|
|
N_("Closes the game."),
|
|
|
|
|
SDLK_UNKNOWN,
|
|
|
|
|
[] { gamemenu_quit_game(false); });
|
|
|
|
|
#endif
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"StopHero",
|
|
|
|
|
N_("Stop hero"),
|
|
|
|
|
N_("Stops walking and cancel pending actions."),
|
|
|
|
|
SDLK_UNKNOWN,
|
|
|
|
|
[] { MyPlayer->Stop(); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"ItemHighlighting",
|
|
|
|
|
N_("Item highlighting"),
|
|
|
|
|
N_("Show/hide items on ground."),
|
|
|
|
|
SDLK_LALT,
|
|
|
|
|
[] { HighlightKeyPressed(true); },
|
|
|
|
|
[] { HighlightKeyPressed(false); });
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"ToggleItemHighlighting",
|
|
|
|
|
N_("Toggle item highlighting"),
|
|
|
|
|
N_("Permanent show/hide items on ground."),
|
|
|
|
|
SDLK_RCTRL,
|
|
|
|
|
nullptr,
|
|
|
|
|
[] { ToggleItemLabelHighlight(); });
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"ToggleAutomap",
|
|
|
|
|
N_("Toggle automap"),
|
|
|
|
|
N_("Toggles if automap is displayed."),
|
|
|
|
|
SDLK_TAB,
|
|
|
|
|
DoAutoMap,
|
|
|
|
|
nullptr,
|
|
|
|
|
IsGameRunning);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"CycleAutomapType",
|
|
|
|
|
N_("Cycle map type"),
|
|
|
|
|
N_("Opaque -> Transparent -> Minimap -> None"),
|
|
|
|
|
SDLK_M,
|
|
|
|
|
CycleAutomapType,
|
|
|
|
|
nullptr,
|
|
|
|
|
IsGameRunning);
|
|
|
|
|
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Inventory",
|
|
|
|
|
N_("Inventory"),
|
|
|
|
|
N_("Open Inventory screen."),
|
|
|
|
|
'I',
|
|
|
|
|
InventoryKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Character",
|
|
|
|
|
N_("Character"),
|
|
|
|
|
N_("Open Character screen."),
|
|
|
|
|
'C',
|
|
|
|
|
CharacterSheetKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Party",
|
|
|
|
|
N_("Party"),
|
|
|
|
|
N_("Open side Party panel."),
|
|
|
|
|
'Y',
|
|
|
|
|
PartyPanelSideToggleKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuestLog",
|
|
|
|
|
N_("Quest log"),
|
|
|
|
|
N_("Open Quest log."),
|
|
|
|
|
'Q',
|
|
|
|
|
QuestLogKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"SpellBook",
|
|
|
|
|
N_("Spellbook"),
|
|
|
|
|
N_("Open Spellbook."),
|
|
|
|
|
'B',
|
|
|
|
|
SpellBookKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
for (uint32_t i = 0; i < QuickMessages.size(); ++i) {
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"QuickMessage{}",
|
|
|
|
|
N_("Quick Message {}"),
|
|
|
|
|
N_("Use Quick Message in chat."),
|
|
|
|
|
(i < 4) ? static_cast<uint32_t>(SDLK_F9) + i : static_cast<uint32_t>(SDLK_UNKNOWN),
|
|
|
|
|
[i]() { DiabloHotkeyMsg(i); },
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr,
|
|
|
|
|
i + 1);
|
|
|
|
|
}
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"HideInfoScreens",
|
|
|
|
|
N_("Hide Info Screens"),
|
|
|
|
|
N_("Hide all info screens."),
|
|
|
|
|
SDLK_SPACE,
|
|
|
|
|
[] {
|
|
|
|
|
if (CanAutomapBeToggledOff())
|
|
|
|
|
AutomapActive = false;
|
|
|
|
|
|
|
|
|
|
ClosePanels();
|
|
|
|
|
HelpFlag = false;
|
|
|
|
|
ChatLogFlag = false;
|
|
|
|
|
SpellSelectFlag = false;
|
|
|
|
|
if (qtextflag && leveltype == DTYPE_TOWN) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CancelCurrentDiabloMsg();
|
|
|
|
|
gamemenu_off();
|
|
|
|
|
doom_close();
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
IsGameRunning);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Zoom",
|
|
|
|
|
N_("Zoom"),
|
|
|
|
|
N_("Zoom Game Screen."),
|
|
|
|
|
'Z',
|
|
|
|
|
[] {
|
|
|
|
|
GetOptions().Graphics.zoom.SetValue(!*GetOptions().Graphics.zoom);
|
|
|
|
|
CalcViewportGeometry();
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"PauseGame",
|
|
|
|
|
N_("Pause Game"),
|
|
|
|
|
N_("Pauses the game."),
|
|
|
|
|
'P',
|
|
|
|
|
diablo_pause_game);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"PauseGameAlternate",
|
|
|
|
|
N_("Pause Game (Alternate)"),
|
|
|
|
|
N_("Pauses the game."),
|
|
|
|
|
SDLK_PAUSE,
|
|
|
|
|
diablo_pause_game);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"DecreaseBrightness",
|
|
|
|
|
N_("Decrease Brightness"),
|
|
|
|
|
N_("Reduce screen brightness."),
|
|
|
|
|
'F',
|
|
|
|
|
DecreaseBrightness,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"IncreaseBrightness",
|
|
|
|
|
N_("Increase Brightness"),
|
|
|
|
|
N_("Increase screen brightness."),
|
|
|
|
|
'G',
|
|
|
|
|
IncreaseBrightness,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Help",
|
|
|
|
|
N_("Help"),
|
|
|
|
|
N_("Open Help Screen."),
|
|
|
|
|
SDLK_F1,
|
|
|
|
|
HelpKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"Screenshot",
|
|
|
|
|
N_("Screenshot"),
|
|
|
|
|
N_("Takes a screenshot."),
|
|
|
|
|
SDLK_PRINTSCREEN,
|
|
|
|
|
nullptr,
|
|
|
|
|
CaptureScreen);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"GameInfo",
|
|
|
|
|
N_("Game info"),
|
|
|
|
|
N_("Displays game infos."),
|
|
|
|
|
'V',
|
|
|
|
|
[] {
|
|
|
|
|
EventPlrMsg(fmt::format(
|
|
|
|
|
fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. */ "{:s} {:s}")),
|
|
|
|
|
PROJECT_NAME,
|
|
|
|
|
PROJECT_VERSION),
|
|
|
|
|
UiFlags::ColorWhite);
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"ChatLog",
|
|
|
|
|
N_("Chat Log"),
|
|
|
|
|
N_("Displays chat log."),
|
|
|
|
|
'L',
|
|
|
|
|
[] {
|
|
|
|
|
ToggleChatLog();
|
|
|
|
|
});
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"SortInv",
|
|
|
|
|
N_("Sort Inventory"),
|
|
|
|
|
N_("Sorts the inventory."),
|
|
|
|
|
'R',
|
|
|
|
|
[] {
|
|
|
|
|
ReorganizeInventory(*MyPlayer);
|
|
|
|
|
});
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"OpenConsole",
|
|
|
|
|
N_("Console"),
|
|
|
|
|
N_("Opens Lua console."),
|
|
|
|
|
SDLK_GRAVE,
|
|
|
|
|
OpenConsole);
|
|
|
|
|
options.Keymapper.AddAction(
|
|
|
|
|
"DebugToggle",
|
|
|
|
|
"Debug toggle",
|
|
|
|
|
"Programming is like magic.",
|
|
|
|
|
'X',
|
|
|
|
|
[] {
|
|
|
|
|
DebugToggle = !DebugToggle;
|
|
|
|
|
});
|
|
|
|
|
#endif
|
|
|
|
|
options.Keymapper.CommitActions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InitPadmapActions()
|
|
|
|
|
{
|
|
|
|
|
Options &options = GetOptions();
|
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"BeltItem{}",
|
|
|
|
|
N_("Belt item {}"),
|
|
|
|
|
N_("Use Belt item."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[i] {
|
|
|
|
|
const Player &myPlayer = *MyPlayer;
|
|
|
|
|
if (!myPlayer.SpdList[i].isEmpty() && myPlayer.SpdList[i]._itype != ItemType::Gold) {
|
|
|
|
|
UseInvItem(INVITEM_BELT_FIRST + i);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction,
|
|
|
|
|
i + 1);
|
|
|
|
|
}
|
|
|
|
|
for (uint32_t i = 0; i < NumHotkeys; ++i) {
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"QuickSpell{}",
|
|
|
|
|
N_("Quick spell {}"),
|
|
|
|
|
N_("Hotkey for skill or spell."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[i]() {
|
|
|
|
|
if (SpellSelectFlag) {
|
|
|
|
|
SetSpeedSpell(i);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!*GetOptions().Gameplay.quickCast)
|
|
|
|
|
ToggleSpell(i);
|
|
|
|
|
else
|
|
|
|
|
QuickCast(i);
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); },
|
|
|
|
|
i + 1);
|
|
|
|
|
}
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"PrimaryAction",
|
|
|
|
|
N_("Primary action"),
|
|
|
|
|
N_("Attack monsters, talk to towners, lift and place inventory items."),
|
|
|
|
|
ControllerButton_BUTTON_B,
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_PRIMARY_ACTION;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
PerformPrimaryAction();
|
|
|
|
|
},
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_NONE;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
},
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"SecondaryAction",
|
|
|
|
|
N_("Secondary action"),
|
|
|
|
|
N_("Open chests, interact with doors, pick up items."),
|
|
|
|
|
ControllerButton_BUTTON_Y,
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_SECONDARY_ACTION;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
PerformSecondaryAction();
|
|
|
|
|
},
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_NONE;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
},
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"SpellAction",
|
|
|
|
|
N_("Spell action"),
|
|
|
|
|
N_("Cast the active spell."),
|
|
|
|
|
ControllerButton_BUTTON_X,
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_CAST_SPELL;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
PerformSpellAction();
|
|
|
|
|
},
|
|
|
|
|
[] {
|
|
|
|
|
ControllerActionHeld = GameActionType_NONE;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
},
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"CancelAction",
|
|
|
|
|
N_("Cancel action"),
|
|
|
|
|
N_("Close menus."),
|
|
|
|
|
ControllerButton_BUTTON_A,
|
|
|
|
|
[] {
|
|
|
|
|
if (DoomFlag) {
|
|
|
|
|
doom_close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameAction action;
|
|
|
|
|
if (SpellSelectFlag)
|
|
|
|
|
action = GameAction(GameActionType_TOGGLE_QUICK_SPELL_MENU);
|
|
|
|
|
else if (invflag)
|
|
|
|
|
action = GameAction(GameActionType_TOGGLE_INVENTORY);
|
|
|
|
|
else if (SpellbookFlag)
|
|
|
|
|
action = GameAction(GameActionType_TOGGLE_SPELL_BOOK);
|
|
|
|
|
else if (QuestLogIsOpen)
|
|
|
|
|
action = GameAction(GameActionType_TOGGLE_QUEST_LOG);
|
|
|
|
|
else if (CharFlag)
|
|
|
|
|
action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO);
|
|
|
|
|
ProcessGameAction(action);
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[] { return DoomFlag || SpellSelectFlag || invflag || SpellbookFlag || QuestLogIsOpen || CharFlag; });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MoveUp",
|
|
|
|
|
N_("Move up"),
|
|
|
|
|
N_("Moves the player character up."),
|
|
|
|
|
ControllerButton_BUTTON_DPAD_UP,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MoveDown",
|
|
|
|
|
N_("Move down"),
|
|
|
|
|
N_("Moves the player character down."),
|
|
|
|
|
ControllerButton_BUTTON_DPAD_DOWN,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MoveLeft",
|
|
|
|
|
N_("Move left"),
|
|
|
|
|
N_("Moves the player character left."),
|
|
|
|
|
ControllerButton_BUTTON_DPAD_LEFT,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MoveRight",
|
|
|
|
|
N_("Move right"),
|
|
|
|
|
N_("Moves the player character right."),
|
|
|
|
|
ControllerButton_BUTTON_DPAD_RIGHT,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"StandGround",
|
|
|
|
|
N_("Stand ground"),
|
|
|
|
|
N_("Hold to prevent the player from moving."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ToggleStandGround",
|
|
|
|
|
N_("Toggle stand ground"),
|
|
|
|
|
N_("Toggle whether the player moves."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] { StandToggle = !StandToggle; },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"UseHealthPotion",
|
|
|
|
|
N_("Use health potion"),
|
|
|
|
|
N_("Use health potions from belt."),
|
|
|
|
|
ControllerButton_BUTTON_LEFTSHOULDER,
|
|
|
|
|
[] { UseBeltItem(BeltItemType::Healing); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"UseManaPotion",
|
|
|
|
|
N_("Use mana potion"),
|
|
|
|
|
N_("Use mana potions from belt."),
|
|
|
|
|
ControllerButton_BUTTON_RIGHTSHOULDER,
|
|
|
|
|
[] { UseBeltItem(BeltItemType::Mana); },
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"Character",
|
|
|
|
|
N_("Character"),
|
|
|
|
|
N_("Open Character screen."),
|
|
|
|
|
ControllerButton_AXIS_TRIGGERLEFT,
|
|
|
|
|
[] {
|
|
|
|
|
ProcessGameAction(GameAction { GameActionType_TOGGLE_CHARACTER_INFO });
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"Inventory",
|
|
|
|
|
N_("Inventory"),
|
|
|
|
|
N_("Open Inventory screen."),
|
|
|
|
|
ControllerButton_AXIS_TRIGGERRIGHT,
|
|
|
|
|
[] {
|
|
|
|
|
ProcessGameAction(GameAction { GameActionType_TOGGLE_INVENTORY });
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"QuestLog",
|
|
|
|
|
N_("Quest log"),
|
|
|
|
|
N_("Open Quest log."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_AXIS_TRIGGERLEFT },
|
|
|
|
|
[] {
|
|
|
|
|
ProcessGameAction(GameAction { GameActionType_TOGGLE_QUEST_LOG });
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"SpellBook",
|
|
|
|
|
N_("Spellbook"),
|
|
|
|
|
N_("Open Spellbook."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_AXIS_TRIGGERRIGHT },
|
|
|
|
|
[] {
|
|
|
|
|
ProcessGameAction(GameAction { GameActionType_TOGGLE_SPELL_BOOK });
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"DisplaySpells",
|
|
|
|
|
N_("Speedbook"),
|
|
|
|
|
N_("Open Speedbook."),
|
|
|
|
|
ControllerButton_BUTTON_A,
|
|
|
|
|
[] {
|
|
|
|
|
ProcessGameAction(GameAction { GameActionType_TOGGLE_QUICK_SPELL_MENU });
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
[]() { return CanPlayerTakeAction() && !InGameMenu(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ToggleAutomap",
|
|
|
|
|
N_("Toggle automap"),
|
|
|
|
|
N_("Toggles if automap is displayed."),
|
|
|
|
|
ControllerButton_BUTTON_LEFTSTICK,
|
|
|
|
|
DoAutoMap);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"AutomapMoveUp",
|
|
|
|
|
N_("Automap Move Up"),
|
|
|
|
|
N_("Moves the automap up when active."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"AutomapMoveDown",
|
|
|
|
|
N_("Automap Move Down"),
|
|
|
|
|
N_("Moves the automap down when active."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"AutomapMoveLeft",
|
|
|
|
|
N_("Automap Move Left"),
|
|
|
|
|
N_("Moves the automap left when active."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"AutomapMoveRight",
|
|
|
|
|
N_("Automap Move Right"),
|
|
|
|
|
N_("Moves the automap right when active."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MouseUp",
|
|
|
|
|
N_("Move mouse up"),
|
|
|
|
|
N_("Simulates upward mouse movement."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_DPAD_UP },
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MouseDown",
|
|
|
|
|
N_("Move mouse down"),
|
|
|
|
|
N_("Simulates downward mouse movement."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_DPAD_DOWN },
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MouseLeft",
|
|
|
|
|
N_("Move mouse left"),
|
|
|
|
|
N_("Simulates leftward mouse movement."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_DPAD_LEFT },
|
|
|
|
|
[] {});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"MouseRight",
|
|
|
|
|
N_("Move mouse right"),
|
|
|
|
|
N_("Simulates rightward mouse movement."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_DPAD_RIGHT },
|
|
|
|
|
[] {});
|
|
|
|
|
auto leftMouseDown = [] {
|
|
|
|
|
const ControllerButtonCombo standGroundCombo = GetOptions().Padmapper.ButtonComboForAction("StandGround");
|
|
|
|
|
const bool standGround = StandToggle || IsControllerButtonComboPressed(standGroundCombo);
|
|
|
|
|
sgbMouseDown = CLICK_LEFT;
|
|
|
|
|
LeftMouseDown(standGround ? SDL_KMOD_SHIFT : SDL_KMOD_NONE);
|
|
|
|
|
};
|
|
|
|
|
auto leftMouseUp = [] {
|
|
|
|
|
const ControllerButtonCombo standGroundCombo = GetOptions().Padmapper.ButtonComboForAction("StandGround");
|
|
|
|
|
const bool standGround = StandToggle || IsControllerButtonComboPressed(standGroundCombo);
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
LeftMouseUp(standGround ? SDL_KMOD_SHIFT : SDL_KMOD_NONE);
|
|
|
|
|
};
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"LeftMouseClick1",
|
|
|
|
|
N_("Left mouse click"),
|
|
|
|
|
N_("Simulates the left mouse button."),
|
|
|
|
|
ControllerButton_BUTTON_RIGHTSTICK,
|
|
|
|
|
leftMouseDown,
|
|
|
|
|
leftMouseUp);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"LeftMouseClick2",
|
|
|
|
|
N_("Left mouse click"),
|
|
|
|
|
N_("Simulates the left mouse button."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_LEFTSHOULDER },
|
|
|
|
|
leftMouseDown,
|
|
|
|
|
leftMouseUp);
|
|
|
|
|
auto rightMouseDown = [] {
|
|
|
|
|
const ControllerButtonCombo standGroundCombo = GetOptions().Padmapper.ButtonComboForAction("StandGround");
|
|
|
|
|
const bool standGround = StandToggle || IsControllerButtonComboPressed(standGroundCombo);
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_RIGHT;
|
|
|
|
|
RightMouseDown(standGround);
|
|
|
|
|
};
|
|
|
|
|
auto rightMouseUp = [] {
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
};
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"RightMouseClick1",
|
|
|
|
|
N_("Right mouse click"),
|
|
|
|
|
N_("Simulates the right mouse button."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_RIGHTSTICK },
|
|
|
|
|
rightMouseDown,
|
|
|
|
|
rightMouseUp);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"RightMouseClick2",
|
|
|
|
|
N_("Right mouse click"),
|
|
|
|
|
N_("Simulates the right mouse button."),
|
|
|
|
|
{ ControllerButton_BUTTON_BACK, ControllerButton_BUTTON_RIGHTSHOULDER },
|
|
|
|
|
rightMouseDown,
|
|
|
|
|
rightMouseUp);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"PadHotspellMenu",
|
|
|
|
|
N_("Gamepad hotspell menu"),
|
|
|
|
|
N_("Hold to set or use spell hotkeys."),
|
|
|
|
|
ControllerButton_BUTTON_BACK,
|
|
|
|
|
[] { PadHotspellMenuActive = true; },
|
|
|
|
|
[] { PadHotspellMenuActive = false; });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"PadMenuNavigator",
|
|
|
|
|
N_("Gamepad menu navigator"),
|
|
|
|
|
N_("Hold to access gamepad menu navigation."),
|
|
|
|
|
ControllerButton_BUTTON_START,
|
|
|
|
|
[] { PadMenuNavigatorActive = true; },
|
|
|
|
|
[] { PadMenuNavigatorActive = false; });
|
|
|
|
|
auto toggleGameMenu = [] {
|
|
|
|
|
const bool inMenu = gmenu_is_active();
|
|
|
|
|
PressEscKey();
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
PadHotspellMenuActive = false;
|
|
|
|
|
PadMenuNavigatorActive = false;
|
|
|
|
|
if (!inMenu)
|
|
|
|
|
gamemenu_on();
|
|
|
|
|
};
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ToggleGameMenu1",
|
|
|
|
|
N_("Toggle game menu"),
|
|
|
|
|
N_("Opens the game menu."),
|
|
|
|
|
{
|
|
|
|
|
ControllerButton_BUTTON_BACK,
|
|
|
|
|
ControllerButton_BUTTON_START,
|
|
|
|
|
},
|
|
|
|
|
toggleGameMenu);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ToggleGameMenu2",
|
|
|
|
|
N_("Toggle game menu"),
|
|
|
|
|
N_("Opens the game menu."),
|
|
|
|
|
{
|
|
|
|
|
ControllerButton_BUTTON_START,
|
|
|
|
|
ControllerButton_BUTTON_BACK,
|
|
|
|
|
},
|
|
|
|
|
toggleGameMenu);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"QuickSave",
|
|
|
|
|
N_("Quick save"),
|
|
|
|
|
N_("Saves the game."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] { gamemenu_save_game(false); },
|
|
|
|
|
nullptr,
|
|
|
|
|
[&]() { return !gbIsMultiplayer && CanPlayerTakeAction(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"QuickLoad",
|
|
|
|
|
N_("Quick load"),
|
|
|
|
|
N_("Loads the game."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] { gamemenu_load_game(false); },
|
|
|
|
|
nullptr,
|
|
|
|
|
[&]() { return !gbIsMultiplayer && gbValidSaveFile && !IsPlayerInStore() && IsGameRunning(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ItemHighlighting",
|
|
|
|
|
N_("Item highlighting"),
|
|
|
|
|
N_("Show/hide items on ground."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] { HighlightKeyPressed(true); },
|
|
|
|
|
[] { HighlightKeyPressed(false); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ToggleItemHighlighting",
|
|
|
|
|
N_("Toggle item highlighting"),
|
|
|
|
|
N_("Permanent show/hide items on ground."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
nullptr,
|
|
|
|
|
[] { ToggleItemLabelHighlight(); });
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"HideInfoScreens",
|
|
|
|
|
N_("Hide Info Screens"),
|
|
|
|
|
N_("Hide all info screens."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {
|
|
|
|
|
if (CanAutomapBeToggledOff())
|
|
|
|
|
AutomapActive = false;
|
|
|
|
|
|
|
|
|
|
ClosePanels();
|
|
|
|
|
HelpFlag = false;
|
|
|
|
|
ChatLogFlag = false;
|
|
|
|
|
SpellSelectFlag = false;
|
|
|
|
|
if (qtextflag && leveltype == DTYPE_TOWN) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CancelCurrentDiabloMsg();
|
|
|
|
|
gamemenu_off();
|
|
|
|
|
doom_close();
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
IsGameRunning);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"Zoom",
|
|
|
|
|
N_("Zoom"),
|
|
|
|
|
N_("Zoom Game Screen."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {
|
|
|
|
|
GetOptions().Graphics.zoom.SetValue(!*GetOptions().Graphics.zoom);
|
|
|
|
|
CalcViewportGeometry();
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"PauseGame",
|
|
|
|
|
N_("Pause Game"),
|
|
|
|
|
N_("Pauses the game."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
diablo_pause_game);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"DecreaseBrightness",
|
|
|
|
|
N_("Decrease Brightness"),
|
|
|
|
|
N_("Reduce screen brightness."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
DecreaseBrightness,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"IncreaseBrightness",
|
|
|
|
|
N_("Increase Brightness"),
|
|
|
|
|
N_("Increase screen brightness."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
IncreaseBrightness,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"Help",
|
|
|
|
|
N_("Help"),
|
|
|
|
|
N_("Open Help Screen."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
HelpKeyPressed,
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"Screenshot",
|
|
|
|
|
N_("Screenshot"),
|
|
|
|
|
N_("Takes a screenshot."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
nullptr,
|
|
|
|
|
CaptureScreen);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"GameInfo",
|
|
|
|
|
N_("Game info"),
|
|
|
|
|
N_("Displays game infos."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {
|
|
|
|
|
EventPlrMsg(fmt::format(
|
|
|
|
|
fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name, Game Version, Game Difficulty. */ "{:s} {:s}")),
|
|
|
|
|
PROJECT_NAME,
|
|
|
|
|
PROJECT_VERSION),
|
|
|
|
|
UiFlags::ColorWhite);
|
|
|
|
|
},
|
|
|
|
|
nullptr,
|
|
|
|
|
CanPlayerTakeAction);
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"SortInv",
|
|
|
|
|
N_("Sort Inventory"),
|
|
|
|
|
N_("Sorts the inventory."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {
|
|
|
|
|
ReorganizeInventory(*MyPlayer);
|
|
|
|
|
});
|
|
|
|
|
options.Padmapper.AddAction(
|
|
|
|
|
"ChatLog",
|
|
|
|
|
N_("Chat Log"),
|
|
|
|
|
N_("Displays chat log."),
|
|
|
|
|
ControllerButton_NONE,
|
|
|
|
|
[] {
|
|
|
|
|
ToggleChatLog();
|
|
|
|
|
});
|
|
|
|
|
options.Padmapper.CommitActions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetCursorPos(Point position)
|
|
|
|
|
{
|
|
|
|
|
if (ControlDevice != ControlTypes::KeyboardAndMouse) {
|
|
|
|
|
MousePosition = position;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogicalToOutput(&position.x, &position.y);
|
|
|
|
|
if (!demo::IsRunning())
|
|
|
|
|
SDL_WarpMouseInWindow(ghMainWnd, position.x, position.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FreeGameMem()
|
|
|
|
|
{
|
|
|
|
|
pDungeonCels = nullptr;
|
|
|
|
|
pMegaTiles = nullptr;
|
|
|
|
|
pSpecialCels = std::nullopt;
|
|
|
|
|
|
|
|
|
|
FreeMonsters();
|
|
|
|
|
FreeMissileGFX();
|
|
|
|
|
FreeObjectGFX();
|
|
|
|
|
FreeTownerGFX();
|
|
|
|
|
FreeStashGFX();
|
|
|
|
|
#ifndef USE_SDL1
|
|
|
|
|
DeactivateVirtualGamepad();
|
|
|
|
|
FreeVirtualGamepadGFX();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StartGame(bool bNewGame, bool bSinglePlayer)
|
|
|
|
|
{
|
|
|
|
|
gbSelectProvider = true;
|
|
|
|
|
ReturnToMainMenu = false;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
gbLoadGame = false;
|
|
|
|
|
|
|
|
|
|
if (!NetInit(bSinglePlayer)) {
|
|
|
|
|
gbRunGameResult = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save 2.8 MiB of RAM by freeing all main menu resources
|
|
|
|
|
// before starting the game.
|
|
|
|
|
UiDestroy();
|
|
|
|
|
|
|
|
|
|
gbSelectProvider = false;
|
|
|
|
|
|
|
|
|
|
if (bNewGame || !gbValidSaveFile) {
|
|
|
|
|
InitLevels();
|
|
|
|
|
InitQuests();
|
|
|
|
|
InitPortals();
|
|
|
|
|
InitDungMsgs(*MyPlayer);
|
|
|
|
|
DeltaSyncJunk();
|
|
|
|
|
}
|
|
|
|
|
giNumberOfLevels = gbIsHellfire ? 25 : 17;
|
|
|
|
|
interface_mode uMsg = WM_DIABNEWGAME;
|
|
|
|
|
if (gbValidSaveFile && gbLoadGame) {
|
|
|
|
|
uMsg = WM_DIABLOADGAME;
|
|
|
|
|
}
|
|
|
|
|
RunGameLoop(uMsg);
|
|
|
|
|
NetClose();
|
|
|
|
|
UnloadFonts();
|
|
|
|
|
|
|
|
|
|
// If the player left the game into the main menu,
|
|
|
|
|
// initialize main menu resources.
|
|
|
|
|
if (gbRunGameResult)
|
|
|
|
|
UiInitialize();
|
|
|
|
|
if (ReturnToMainMenu)
|
|
|
|
|
return true;
|
|
|
|
|
} while (gbRunGameResult);
|
|
|
|
|
|
|
|
|
|
SNetDestroy();
|
|
|
|
|
return gbRunGameResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void diablo_quit(int exitStatus)
|
|
|
|
|
{
|
|
|
|
|
FreeGameMem();
|
|
|
|
|
music_stop();
|
|
|
|
|
DiabloDeinit();
|
|
|
|
|
|
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
|
|
|
if (SdlLogFile != nullptr) std::fclose(SdlLogFile);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
exit(exitStatus);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef __UWP__
|
|
|
|
|
void (*onInitialized)() = NULL;
|
|
|
|
|
|
|
|
|
|
void setOnInitialized(void (*callback)())
|
|
|
|
|
{
|
|
|
|
|
onInitialized = callback;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int DiabloMain(int argc, char **argv)
|
|
|
|
|
{
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
DiabloParseFlags(argc, argv);
|
|
|
|
|
InitKeymapActions();
|
|
|
|
|
InitPadmapActions();
|
|
|
|
|
|
|
|
|
|
// Need to ensure devilutionx.mpq (and fonts.mpq if available) are loaded before attempting to read translation settings
|
|
|
|
|
LoadCoreArchives();
|
|
|
|
|
was_archives_init = true;
|
|
|
|
|
|
|
|
|
|
// Read settings including translation next. This will use the presence of fonts.mpq and look for assets in devilutionx.mpq
|
|
|
|
|
LoadOptions();
|
|
|
|
|
if (demo::IsRunning()) demo::OverrideOptions();
|
|
|
|
|
|
|
|
|
|
// Then look for a voice pack file based on the selected translation
|
|
|
|
|
LoadLanguageArchive();
|
|
|
|
|
|
|
|
|
|
ApplicationInit();
|
|
|
|
|
LuaInitialize();
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
|
|
|
|
|
// Finally load game data
|
|
|
|
|
LoadGameArchives();
|
|
|
|
|
|
|
|
|
|
LoadTextData();
|
|
|
|
|
|
|
|
|
|
// Load dynamic data before we go into the menu as we need to initialise player characters in memory pretty early.
|
|
|
|
|
LoadPlayerDataFiles();
|
|
|
|
|
|
|
|
|
|
// TODO: We can probably load this much later (when the game is starting).
|
|
|
|
|
LoadSpellData();
|
|
|
|
|
LoadMissileData();
|
|
|
|
|
LoadMonsterData();
|
|
|
|
|
LoadItemData();
|
|
|
|
|
LoadObjectData();
|
|
|
|
|
LoadQuestData();
|
|
|
|
|
|
|
|
|
|
DiabloInit();
|
|
|
|
|
#ifdef __UWP__
|
|
|
|
|
onInitialized();
|
|
|
|
|
#endif
|
|
|
|
|
if (!demo::IsRunning()) SaveOptions();
|
|
|
|
|
|
|
|
|
|
DiabloSplash();
|
|
|
|
|
mainmenu_loop();
|
|
|
|
|
DiabloDeinit();
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TryIconCurs()
|
|
|
|
|
{
|
|
|
|
|
if (pcurs == CURSOR_RESURRECT) {
|
|
|
|
|
if (PlayerUnderCursor != nullptr) {
|
|
|
|
|
NetSendCmdParam1(true, CMD_RESURRECT, PlayerUnderCursor->getId());
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_HEALOTHER) {
|
|
|
|
|
if (PlayerUnderCursor != nullptr) {
|
|
|
|
|
NetSendCmdParam1(true, CMD_HEALOTHER, PlayerUnderCursor->getId());
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_TELEKINESIS) {
|
|
|
|
|
DoTelekinesis();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Player &myPlayer = *MyPlayer;
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_IDENTIFY) {
|
|
|
|
|
if (pcursinvitem != -1 && !IsInspectingPlayer())
|
|
|
|
|
CheckIdentify(myPlayer, pcursinvitem);
|
|
|
|
|
else if (pcursstashitem != StashStruct::EmptyCell) {
|
|
|
|
|
Item &item = Stash.stashList[pcursstashitem];
|
|
|
|
|
item._iIdentified = true;
|
|
|
|
|
}
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_REPAIR) {
|
|
|
|
|
if (pcursinvitem != -1 && !IsInspectingPlayer())
|
|
|
|
|
DoRepair(myPlayer, pcursinvitem);
|
|
|
|
|
else if (pcursstashitem != StashStruct::EmptyCell) {
|
|
|
|
|
Item &item = Stash.stashList[pcursstashitem];
|
|
|
|
|
RepairItem(item, myPlayer.getCharacterLevel());
|
|
|
|
|
}
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_RECHARGE) {
|
|
|
|
|
if (pcursinvitem != -1 && !IsInspectingPlayer())
|
|
|
|
|
DoRecharge(myPlayer, pcursinvitem);
|
|
|
|
|
else if (pcursstashitem != StashStruct::EmptyCell) {
|
|
|
|
|
Item &item = Stash.stashList[pcursstashitem];
|
|
|
|
|
RechargeItem(item, myPlayer);
|
|
|
|
|
}
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_OIL) {
|
|
|
|
|
bool changeCursor = true;
|
|
|
|
|
if (pcursinvitem != -1 && !IsInspectingPlayer())
|
|
|
|
|
changeCursor = DoOil(myPlayer, pcursinvitem);
|
|
|
|
|
else if (pcursstashitem != StashStruct::EmptyCell) {
|
|
|
|
|
Item &item = Stash.stashList[pcursstashitem];
|
|
|
|
|
changeCursor = ApplyOilToItem(item, myPlayer);
|
|
|
|
|
}
|
|
|
|
|
if (changeCursor)
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_TELEPORT) {
|
|
|
|
|
const SpellID spellID = myPlayer.inventorySpell;
|
|
|
|
|
const SpellType spellType = SpellType::Scroll;
|
|
|
|
|
const int spellFrom = myPlayer.spellFrom;
|
|
|
|
|
if (IsWallSpell(spellID)) {
|
|
|
|
|
const Direction sd = GetDirection(myPlayer.position.tile, cursPosition);
|
|
|
|
|
NetSendCmdLocParam4(true, CMD_SPELLXYD, cursPosition, static_cast<int8_t>(spellID), static_cast<uint8_t>(spellType), static_cast<uint16_t>(sd), spellFrom);
|
|
|
|
|
} else if (pcursmonst != -1 && leveltype != DTYPE_TOWN) {
|
|
|
|
|
NetSendCmdParam4(true, CMD_SPELLID, pcursmonst, static_cast<int8_t>(spellID), static_cast<uint8_t>(spellType), spellFrom);
|
|
|
|
|
} else if (PlayerUnderCursor != nullptr && !myPlayer.friendlyMode) {
|
|
|
|
|
NetSendCmdParam4(true, CMD_SPELLPID, PlayerUnderCursor->getId(), static_cast<int8_t>(spellID), static_cast<uint8_t>(spellType), spellFrom);
|
|
|
|
|
} else {
|
|
|
|
|
NetSendCmdLocParam3(true, CMD_SPELLXY, cursPosition, static_cast<int8_t>(spellID), static_cast<uint8_t>(spellType), spellFrom);
|
|
|
|
|
}
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pcurs == CURSOR_DISARM && ObjectUnderCursor == nullptr) {
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void diablo_pause_game()
|
|
|
|
|
{
|
|
|
|
|
if (!gbIsMultiplayer) {
|
|
|
|
|
if (PauseMode != 0) {
|
|
|
|
|
PauseMode = 0;
|
|
|
|
|
} else {
|
|
|
|
|
PauseMode = 2;
|
|
|
|
|
sound_stop();
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RedrawEverything();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GameWasAlreadyPaused = false;
|
|
|
|
|
bool MinimizePaused = false;
|
|
|
|
|
|
|
|
|
|
bool diablo_is_focused()
|
|
|
|
|
{
|
|
|
|
|
#ifndef USE_SDL1
|
|
|
|
|
return SDL_GetKeyboardFocus() == ghMainWnd;
|
|
|
|
|
#else
|
|
|
|
|
Uint8 appState = SDL_GetAppState();
|
|
|
|
|
return (appState & SDL_APPINPUTFOCUS) != 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void diablo_focus_pause()
|
|
|
|
|
{
|
|
|
|
|
if (!movie_playing && (gbIsMultiplayer || MinimizePaused)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameWasAlreadyPaused = PauseMode != 0;
|
|
|
|
|
|
|
|
|
|
if (!GameWasAlreadyPaused) {
|
|
|
|
|
PauseMode = 2;
|
|
|
|
|
sound_stop();
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SVidMute();
|
|
|
|
|
music_mute();
|
|
|
|
|
|
|
|
|
|
MinimizePaused = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void diablo_focus_unpause()
|
|
|
|
|
{
|
|
|
|
|
if (!GameWasAlreadyPaused) {
|
|
|
|
|
PauseMode = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SVidUnmute();
|
|
|
|
|
music_unmute();
|
|
|
|
|
|
|
|
|
|
MinimizePaused = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PressEscKey()
|
|
|
|
|
{
|
|
|
|
|
bool rv = false;
|
|
|
|
|
|
|
|
|
|
if (DoomFlag) {
|
|
|
|
|
doom_close();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (HelpFlag) {
|
|
|
|
|
HelpFlag = false;
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ChatLogFlag) {
|
|
|
|
|
ChatLogFlag = false;
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (qtextflag) {
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
stream_stop();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsPlayerInStore()) {
|
|
|
|
|
StoreESC();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsDiabloMsgAvailable()) {
|
|
|
|
|
CancelCurrentDiabloMsg();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ChatFlag) {
|
|
|
|
|
ResetChat();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DropGoldFlag) {
|
|
|
|
|
control_drop_gold(SDLK_ESCAPE);
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsWithdrawGoldOpen) {
|
|
|
|
|
WithdrawGoldKeyPress(SDLK_ESCAPE);
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SpellSelectFlag) {
|
|
|
|
|
SpellSelectFlag = false;
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (IsLeftPanelOpen() || IsRightPanelOpen()) {
|
|
|
|
|
ClosePanels();
|
|
|
|
|
rv = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DisableInputEventHandler(const SDL_Event &event, uint16_t modState)
|
|
|
|
|
{
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
|
|
|
MousePosition = { SDLC_EventMotionIntX(event), SDLC_EventMotionIntY(event) };
|
|
|
|
|
return;
|
|
|
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
|
|
|
if (sgbMouseDown != CLICK_NONE)
|
|
|
|
|
return;
|
|
|
|
|
switch (event.button.button) {
|
|
|
|
|
case SDL_BUTTON_LEFT:
|
|
|
|
|
sgbMouseDown = CLICK_LEFT;
|
|
|
|
|
return;
|
|
|
|
|
case SDL_BUTTON_RIGHT:
|
|
|
|
|
sgbMouseDown = CLICK_RIGHT;
|
|
|
|
|
return;
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWndProc(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelStopMusic(_music_id neededTrack)
|
|
|
|
|
{
|
|
|
|
|
if (neededTrack != sgnMusicTrack)
|
|
|
|
|
music_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelStartMusic(_music_id neededTrack)
|
|
|
|
|
{
|
|
|
|
|
if (sgnMusicTrack != neededTrack)
|
|
|
|
|
music_start(neededTrack);
|
|
|
|
|
|
|
|
|
|
if (MinimizePaused) {
|
|
|
|
|
music_mute();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelResetCursor()
|
|
|
|
|
{
|
|
|
|
|
if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
|
|
|
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetRndSeedForDungeonLevel()
|
|
|
|
|
{
|
|
|
|
|
if (setlevel) {
|
|
|
|
|
// Maps are not randomly generated, but the monsters max hitpoints are.
|
|
|
|
|
// So we need to ensure that we have a stable seed when generating quest/set-maps.
|
|
|
|
|
// For this purpose we reuse the normal dungeon seeds.
|
|
|
|
|
SetRndSeed(DungeonSeeds[static_cast<size_t>(setlvlnum)]);
|
|
|
|
|
} else {
|
|
|
|
|
SetRndSeed(DungeonSeeds[currlevel]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelFirstFlagEntry()
|
|
|
|
|
{
|
|
|
|
|
CloseInventory();
|
|
|
|
|
qtextflag = false;
|
|
|
|
|
if (!HeadlessMode) {
|
|
|
|
|
InitInv();
|
|
|
|
|
ClearUniqueItemFlags();
|
|
|
|
|
InitQuestText();
|
|
|
|
|
InitInfoBoxGfx();
|
|
|
|
|
InitHelp();
|
|
|
|
|
}
|
|
|
|
|
InitStores();
|
|
|
|
|
InitAutomapOnce();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelStores()
|
|
|
|
|
{
|
|
|
|
|
if (leveltype == DTYPE_TOWN) {
|
|
|
|
|
SetupTownStores();
|
|
|
|
|
} else {
|
|
|
|
|
FreeStoreMem();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelStash()
|
|
|
|
|
{
|
|
|
|
|
const bool isHellfireSaveGame = gbIsHellfireSaveGame;
|
|
|
|
|
|
|
|
|
|
gbIsHellfireSaveGame = gbIsHellfire;
|
|
|
|
|
LoadStash();
|
|
|
|
|
gbIsHellfireSaveGame = isHellfireSaveGame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadGameLevelDungeon(bool firstflag, lvl_entry lvldir, const Player &myPlayer)
|
|
|
|
|
{
|
|
|
|
|
if (firstflag || lvldir == ENTRY_LOAD || !myPlayer._pLvlVisited[currlevel] || gbIsMultiplayer) {
|
|
|
|
|
HoldThemeRooms();
|
|
|
|
|
[[maybe_unused]] const uint32_t mid1Seed = GetLCGEngineState();
|
|
|
|
|
InitGolems();
|
|
|
|
|
InitObjects();
|
|
|
|
|
[[maybe_unused]] const uint32_t mid2Seed = GetLCGEngineState();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(InitMonsters());
|
|
|
|
|
InitItems();
|
|
|
|
|
CreateThemeRooms();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
[[maybe_unused]] const uint32_t mid3Seed = GetLCGEngineState();
|
|
|
|
|
InitMissiles();
|
|
|
|
|
InitCorpses();
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
SetDebugLevelSeedInfos(mid1Seed, mid2Seed, mid3Seed, GetLCGEngineState());
|
|
|
|
|
#endif
|
|
|
|
|
SavePreLighting();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (gbIsMultiplayer)
|
|
|
|
|
DeltaLoadLevel();
|
|
|
|
|
} else {
|
|
|
|
|
HoldThemeRooms();
|
|
|
|
|
InitGolems();
|
|
|
|
|
RETURN_IF_ERROR(InitMonsters());
|
|
|
|
|
InitMissiles();
|
|
|
|
|
InitCorpses();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(LoadLevel());
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelSyncPlayerEntry(lvl_entry lvldir)
|
|
|
|
|
{
|
|
|
|
|
for (Player &player : Players) {
|
|
|
|
|
if (player.plractive && player.isOnActiveLevel() && (!player._pLvlChanging || &player == MyPlayer)) {
|
|
|
|
|
if (player._pHitPoints > 0) {
|
|
|
|
|
if (lvldir != ENTRY_LOAD)
|
|
|
|
|
SyncInitPlrPos(player);
|
|
|
|
|
} else {
|
|
|
|
|
dFlags[player.position.tile.x][player.position.tile.y] |= DungeonFlag::DeadPlayer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelLightVision()
|
|
|
|
|
{
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
memcpy(dLight, dPreLight, sizeof(dLight)); // resets the light on entering a level to get rid of incorrect light
|
|
|
|
|
ChangeLightXY(Players[MyPlayerId].lightId, Players[MyPlayerId].position.tile); // forces player light refresh
|
|
|
|
|
ProcessLightList();
|
|
|
|
|
ProcessVisionList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelReturn()
|
|
|
|
|
{
|
|
|
|
|
ViewPosition = GetMapReturnPosition();
|
|
|
|
|
if (Quests[Q_BETRAYER]._qactive == QUEST_DONE)
|
|
|
|
|
Quests[Q_BETRAYER]._qvar2 = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelInitPlayers(bool firstflag, lvl_entry lvldir)
|
|
|
|
|
{
|
|
|
|
|
for (Player &player : Players) {
|
|
|
|
|
if (player.plractive && player.isOnActiveLevel()) {
|
|
|
|
|
InitPlayerGFX(player);
|
|
|
|
|
if (lvldir != ENTRY_LOAD)
|
|
|
|
|
InitPlayer(player, firstflag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelSetVisited()
|
|
|
|
|
{
|
|
|
|
|
bool visited = false;
|
|
|
|
|
for (const Player &player : Players) {
|
|
|
|
|
if (player.plractive)
|
|
|
|
|
visited = visited || player._pLvlVisited[currlevel];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadGameLevelTown(bool firstflag, lvl_entry lvldir, const Player &myPlayer)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < MAXDUNX; i++) { // NOLINT(modernize-loop-convert)
|
|
|
|
|
for (int j = 0; j < MAXDUNY; j++) {
|
|
|
|
|
dFlags[i][j] |= DungeonFlag::Lit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitTowners();
|
|
|
|
|
InitStash();
|
|
|
|
|
InitItems();
|
|
|
|
|
InitMissiles();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (!firstflag && lvldir != ENTRY_LOAD && myPlayer._pLvlVisited[currlevel] && !gbIsMultiplayer)
|
|
|
|
|
RETURN_IF_ERROR(LoadLevel());
|
|
|
|
|
if (gbIsMultiplayer)
|
|
|
|
|
DeltaLoadLevel();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < DMAXX; x++)
|
|
|
|
|
for (int y = 0; y < DMAXY; y++)
|
|
|
|
|
UpdateAutomapExplorer({ x, y }, MAP_EXP_SELF);
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadGameLevelSetLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer)
|
|
|
|
|
{
|
|
|
|
|
LoadSetMap();
|
|
|
|
|
IncProgress();
|
|
|
|
|
RETURN_IF_ERROR(GetLevelMTypes());
|
|
|
|
|
IncProgress();
|
|
|
|
|
InitGolems();
|
|
|
|
|
RETURN_IF_ERROR(InitMonsters());
|
|
|
|
|
IncProgress();
|
|
|
|
|
if (!HeadlessMode) {
|
|
|
|
|
#if !defined(USE_SDL1) && !defined(__vita__)
|
|
|
|
|
InitVirtualGamepadGFX();
|
|
|
|
|
#endif
|
|
|
|
|
RETURN_IF_ERROR(InitMissileGFX());
|
|
|
|
|
IncProgress();
|
|
|
|
|
}
|
|
|
|
|
InitCorpses();
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (lvldir == ENTRY_WARPLVL)
|
|
|
|
|
GetPortalLvlPos();
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
for (Player &player : Players) {
|
|
|
|
|
if (player.plractive && player.isOnActiveLevel()) {
|
|
|
|
|
InitPlayerGFX(player);
|
|
|
|
|
if (lvldir != ENTRY_LOAD)
|
|
|
|
|
InitPlayer(player, firstflag);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
IncProgress();
|
|
|
|
|
InitMultiView();
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (firstflag || lvldir == ENTRY_LOAD || !myPlayer._pSLvlVisited[setlvlnum] || gbIsMultiplayer) {
|
|
|
|
|
InitItems();
|
|
|
|
|
SavePreLighting();
|
|
|
|
|
} else {
|
|
|
|
|
RETURN_IF_ERROR(LoadLevel());
|
|
|
|
|
}
|
|
|
|
|
if (gbIsMultiplayer) {
|
|
|
|
|
DeltaLoadLevel();
|
|
|
|
|
if (!UseMultiplayerQuests())
|
|
|
|
|
ResyncQuests();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlayDungMsgs();
|
|
|
|
|
InitMissiles();
|
|
|
|
|
IncProgress();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer)
|
|
|
|
|
{
|
|
|
|
|
CreateLevel(lvldir);
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
SetRndSeedForDungeonLevel();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN) {
|
|
|
|
|
RETURN_IF_ERROR(GetLevelMTypes());
|
|
|
|
|
InitThemes();
|
|
|
|
|
if (!HeadlessMode)
|
|
|
|
|
RETURN_IF_ERROR(LoadAllGFX());
|
|
|
|
|
} else if (!HeadlessMode) {
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
#if !defined(USE_SDL1) && !defined(__vita__)
|
|
|
|
|
InitVirtualGamepadGFX();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(InitMissileGFX());
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
IncProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (lvldir == ENTRY_RTNLVL) {
|
|
|
|
|
LoadGameLevelReturn();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lvldir == ENTRY_WARPLVL)
|
|
|
|
|
GetPortalLvlPos();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
LoadGameLevelInitPlayers(firstflag, lvldir);
|
|
|
|
|
InitMultiView();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
LoadGameLevelSetVisited();
|
|
|
|
|
|
|
|
|
|
SetRndSeedForDungeonLevel();
|
|
|
|
|
|
|
|
|
|
if (leveltype == DTYPE_TOWN) {
|
|
|
|
|
LoadGameLevelTown(firstflag, lvldir, myPlayer);
|
|
|
|
|
} else {
|
|
|
|
|
LoadGameLevelDungeon(firstflag, lvldir, myPlayer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlayDungMsgs();
|
|
|
|
|
|
|
|
|
|
if (UseMultiplayerQuests())
|
|
|
|
|
ResyncMPQuests();
|
|
|
|
|
else
|
|
|
|
|
ResyncQuests();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelCrypt()
|
|
|
|
|
{
|
|
|
|
|
if (CornerStone.isAvailable()) {
|
|
|
|
|
CornerstoneLoad(CornerStone.position);
|
|
|
|
|
}
|
|
|
|
|
if (Quests[Q_NAKRUL]._qactive == QUEST_DONE && currlevel == 24) {
|
|
|
|
|
SyncNakrulRoom();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LoadGameLevelCalculateCursor()
|
|
|
|
|
{
|
|
|
|
|
// Recalculate mouse selection of entities after level change/load
|
|
|
|
|
LastPlayerAction = PlayerActionType::None;
|
|
|
|
|
sgbMouseDown = CLICK_NONE;
|
|
|
|
|
ResetItemlabelHighlighted(); // level changed => item changed
|
|
|
|
|
pcursmonst = -1; // ensure pcurstemp is set to a valid value
|
|
|
|
|
CheckCursMove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tl::expected<void, std::string> LoadGameLevel(bool firstflag, lvl_entry lvldir)
|
|
|
|
|
{
|
|
|
|
|
const _music_id neededTrack = GetLevelMusic(leveltype);
|
|
|
|
|
|
|
|
|
|
ClearFloatingNumbers();
|
|
|
|
|
LoadGameLevelStopMusic(neededTrack);
|
|
|
|
|
LoadGameLevelResetCursor();
|
|
|
|
|
SetRndSeedForDungeonLevel();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(LoadTrns());
|
|
|
|
|
MakeLightTable();
|
|
|
|
|
RETURN_IF_ERROR(LoadLevelSOLData());
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
RETURN_IF_ERROR(LoadLvlGFX());
|
Make `dun_render` a standalone library
Does not make `dun_render_benchmark` standalone yet as that will require
more untangling.
Benchmark is neutral:
```
Benchmark Time CPU Time Old Time New CPU Old CPU New
----------------------------------------------------------------------------------------------------------------------------------------------------------
Render<LeftTriangle, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Solid, FullyLit>_mean +0.0119 +0.0120 8377 8477 8375 8475
Render<LeftTriangle, Solid, FullyLit>_median +0.0119 +0.0119 8376 8477 8375 8475
Render<LeftTriangle, Solid, FullyLit>_stddev -0.0884 -0.2462 2 1 1 1
Render<LeftTriangle, Solid, FullyLit>_cv -0.0992 -0.2551 0 0 0 0
Render<LeftTriangle, Solid, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Solid, FullyDark>_mean +0.0910 +0.0910 21174 23100 21170 23097
Render<LeftTriangle, Solid, FullyDark>_median +0.0869 +0.0869 21183 23023 21179 23019
Render<LeftTriangle, Solid, FullyDark>_stddev -0.1528 -0.1593 267 226 268 225
Render<LeftTriangle, Solid, FullyDark>_cv -0.2234 -0.2294 0 0 0 0
Render<LeftTriangle, Solid, PartiallyLit>_pvalue 0.0013 0.0013 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Solid, PartiallyLit>_mean +0.0065 +0.0065 81168 81698 81151 81680
Render<LeftTriangle, Solid, PartiallyLit>_median +0.0075 +0.0073 81143 81748 81136 81730
Render<LeftTriangle, Solid, PartiallyLit>_stddev +0.8663 +0.8787 167 311 164 307
Render<LeftTriangle, Solid, PartiallyLit>_cv +0.8542 +0.8665 0 0 0 0
Render<LeftTriangle, Transparent, FullyLit>_pvalue 0.0028 0.0017 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Transparent, FullyLit>_mean -0.0239 -0.0239 94989 92719 94973 92703
Render<LeftTriangle, Transparent, FullyLit>_median -0.0122 -0.0123 93867 92717 93856 92704
Render<LeftTriangle, Transparent, FullyLit>_stddev -0.9920 -0.9955 2370 19 2368 11
Render<LeftTriangle, Transparent, FullyLit>_cv -0.9918 -0.9954 0 0 0 0
Render<LeftTriangle, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Transparent, FullyDark>_mean -0.0841 -0.0841 76234 69821 76220 69809
Render<LeftTriangle, Transparent, FullyDark>_median -0.0831 -0.0832 76209 69877 76202 69864
Render<LeftTriangle, Transparent, FullyDark>_stddev -0.4486 -0.4538 441 243 440 241
Render<LeftTriangle, Transparent, FullyDark>_cv -0.3979 -0.4037 0 0 0 0
Render<LeftTriangle, Transparent, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTriangle, Transparent, PartiallyLit>_mean +0.0022 +0.0021 128812 129091 128792 129067
Render<LeftTriangle, Transparent, PartiallyLit>_median +0.0023 +0.0023 128820 129115 128805 129096
Render<LeftTriangle, Transparent, PartiallyLit>_stddev +0.8757 +0.6866 50 93 53 90
Render<LeftTriangle, Transparent, PartiallyLit>_cv +0.8716 +0.6830 0 0 0 0
Render<RightTriangle, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Solid, FullyLit>_mean +0.0057 +0.0057 8521 8570 8520 8569
Render<RightTriangle, Solid, FullyLit>_median +0.0057 +0.0057 8522 8570 8520 8568
Render<RightTriangle, Solid, FullyLit>_stddev -0.1826 -0.0420 1 1 1 1
Render<RightTriangle, Solid, FullyLit>_cv -0.1872 -0.0475 0 0 0 0
Render<RightTriangle, Solid, FullyDark>_pvalue 0.0006 0.0006 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Solid, FullyDark>_mean -0.0303 -0.0303 22678 21991 22675 21987
Render<RightTriangle, Solid, FullyDark>_median -0.0360 -0.0359 22704 21888 22699 21883
Render<RightTriangle, Solid, FullyDark>_stddev +0.4759 +0.4648 195 288 196 287
Render<RightTriangle, Solid, FullyDark>_cv +0.5220 +0.5106 0 0 0 0
Render<RightTriangle, Solid, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Solid, PartiallyLit>_mean +0.0338 +0.0338 83355 86170 83341 86157
Render<RightTriangle, Solid, PartiallyLit>_median +0.0347 +0.0348 83248 86140 83230 86126
Render<RightTriangle, Solid, PartiallyLit>_stddev +0.3670 +0.3423 238 326 240 322
Render<RightTriangle, Solid, PartiallyLit>_cv +0.3224 +0.2985 0 0 0 0
Render<RightTriangle, Transparent, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Transparent, FullyLit>_mean -0.0617 -0.0616 102726 96392 102706 96375
Render<RightTriangle, Transparent, FullyLit>_median -0.0598 -0.0597 102521 96394 102498 96375
Render<RightTriangle, Transparent, FullyLit>_stddev -0.9516 -0.9548 456 22 461 21
Render<RightTriangle, Transparent, FullyLit>_cv -0.9485 -0.9518 0 0 0 0
Render<RightTriangle, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Transparent, FullyDark>_mean -0.1377 -0.1377 84505 72865 84492 72853
Render<RightTriangle, Transparent, FullyDark>_median -0.1374 -0.1374 84339 72748 84323 72740
Render<RightTriangle, Transparent, FullyDark>_stddev -0.2760 -0.2867 526 381 528 377
Render<RightTriangle, Transparent, FullyDark>_cv -0.1604 -0.1727 0 0 0 0
Render<RightTriangle, Transparent, PartiallyLit>_pvalue 0.0036 0.0017 U Test, Repetitions: 10 vs 10
Render<RightTriangle, Transparent, PartiallyLit>_mean +0.0010 +0.0010 131672 131808 131649 131784
Render<RightTriangle, Transparent, PartiallyLit>_median +0.0010 +0.0008 131665 131797 131654 131757
Render<RightTriangle, Transparent, PartiallyLit>_stddev -0.0688 -0.0128 81 75 72 71
Render<RightTriangle, Transparent, PartiallyLit>_cv -0.0697 -0.0138 0 0 0 0
Render<TransparentSquare, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Solid, FullyLit>_mean -0.0300 -0.0300 143323 139021 143300 139000
Render<TransparentSquare, Solid, FullyLit>_median -0.0300 -0.0301 143321 139014 143310 138990
Render<TransparentSquare, Solid, FullyLit>_stddev +0.0008 -0.0820 43 43 43 39
Render<TransparentSquare, Solid, FullyLit>_cv +0.0318 -0.0536 0 0 0 0
Render<TransparentSquare, Solid, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Solid, FullyDark>_mean -0.0100 -0.0100 134939 133588 134914 133565
Render<TransparentSquare, Solid, FullyDark>_median -0.0106 -0.0108 134964 133526 134948 133497
Render<TransparentSquare, Solid, FullyDark>_stddev +1.7508 +1.8682 99 273 96 276
Render<TransparentSquare, Solid, FullyDark>_cv +1.7786 +1.8972 0 0 0 0
Render<TransparentSquare, Solid, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Solid, PartiallyLit>_mean -0.0460 -0.0460 152043 145043 152016 145020
Render<TransparentSquare, Solid, PartiallyLit>_median -0.0463 -0.0461 152012 144978 151964 144962
Render<TransparentSquare, Solid, PartiallyLit>_stddev -0.4453 -0.4334 267 148 266 151
Render<TransparentSquare, Solid, PartiallyLit>_cv -0.4185 -0.4060 0 0 0 0
Render<TransparentSquare, Transparent, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Transparent, FullyLit>_mean -0.0846 -0.0846 181333 165997 181304 165969
Render<TransparentSquare, Transparent, FullyLit>_median -0.0840 -0.0839 181184 165972 181147 165945
Render<TransparentSquare, Transparent, FullyLit>_stddev -0.5808 -0.5755 319 134 320 136
Render<TransparentSquare, Transparent, FullyLit>_cv -0.5421 -0.5362 0 0 0 0
Render<TransparentSquare, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Transparent, FullyDark>_mean -0.0250 -0.0250 142232 138672 142208 138648
Render<TransparentSquare, Transparent, FullyDark>_median -0.0245 -0.0245 142144 138663 142128 138639
Render<TransparentSquare, Transparent, FullyDark>_stddev +0.1011 +0.0806 288 317 290 313
Render<TransparentSquare, Transparent, FullyDark>_cv +0.1294 +0.1084 0 0 0 0
Render<TransparentSquare, Transparent, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<TransparentSquare, Transparent, PartiallyLit>_mean +0.0105 +0.0105 205439 207589 205397 207556
Render<TransparentSquare, Transparent, PartiallyLit>_median +0.0106 +0.0107 205402 207575 205355 207558
Render<TransparentSquare, Transparent, PartiallyLit>_stddev -0.4410 -0.3876 182 102 167 102
Render<TransparentSquare, Transparent, PartiallyLit>_cv -0.4468 -0.3940 0 0 0 0
Render<Square, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Solid, FullyLit>_mean -0.0010 -0.0010 11109 11098 11107 11096
Render<Square, Solid, FullyLit>_median -0.0010 -0.0010 11109 11097 11107 11095
Render<Square, Solid, FullyLit>_stddev -0.2265 +0.2791 3 2 2 2
Render<Square, Solid, FullyLit>_cv -0.2257 +0.2804 0 0 0 0
Render<Square, Solid, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Solid, FullyDark>_mean +0.0904 +0.0904 8513 9283 8512 9282
Render<Square, Solid, FullyDark>_median +0.0902 +0.0902 8521 9290 8519 9288
Render<Square, Solid, FullyDark>_stddev -0.1884 -0.1616 21 17 21 18
Render<Square, Solid, FullyDark>_cv -0.2557 -0.2311 0 0 0 0
Render<Square, Solid, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Solid, PartiallyLit>_mean +0.0038 +0.0038 163661 164289 163632 164259
Render<Square, Solid, PartiallyLit>_median +0.0038 +0.0040 163665 164290 163621 164269
Render<Square, Solid, PartiallyLit>_stddev +0.1746 +0.4412 34 40 28 40
Render<Square, Solid, PartiallyLit>_cv +0.1701 +0.4356 0 0 0 0
Render<Square, Transparent, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Transparent, FullyLit>_mean -0.0029 -0.0029 197906 197340 197876 197304
Render<Square, Transparent, FullyLit>_median -0.0030 -0.0029 197929 197339 197872 197307
Render<Square, Transparent, FullyLit>_stddev -0.5965 -0.7554 61 25 62 15
Render<Square, Transparent, FullyLit>_cv -0.5953 -0.7547 0 0 0 0
Render<Square, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Transparent, FullyDark>_mean -0.0163 -0.0163 125659 123607 125641 123588
Render<Square, Transparent, FullyDark>_median -0.0163 -0.0163 125651 123609 125629 123579
Render<Square, Transparent, FullyDark>_stddev -0.7943 -0.8033 180 37 181 36
Render<Square, Transparent, FullyDark>_cv -0.7909 -0.8000 0 0 0 0
Render<Square, Transparent, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<Square, Transparent, PartiallyLit>_mean +0.0182 +0.0182 278103 283157 278043 283107
Render<Square, Transparent, PartiallyLit>_median +0.0184 +0.0184 278086 283190 278017 283120
Render<Square, Transparent, PartiallyLit>_stddev +1.6051 +1.5303 81 210 82 209
Render<Square, Transparent, PartiallyLit>_cv +1.5586 +1.4850 0 0 0 0
Render<LeftTrapezoid, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Solid, FullyLit>_mean -0.0068 -0.0068 3299 3276 3298 3276
Render<LeftTrapezoid, Solid, FullyLit>_median -0.0068 -0.0068 3299 3276 3298 3276
Render<LeftTrapezoid, Solid, FullyLit>_stddev -0.4844 -0.6856 1 0 1 0
Render<LeftTrapezoid, Solid, FullyLit>_cv -0.4809 -0.6834 0 0 0 0
Render<LeftTrapezoid, Solid, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Solid, FullyDark>_mean +0.3996 +0.3997 5163 7227 5162 7226
Render<LeftTrapezoid, Solid, FullyDark>_median +0.3973 +0.3974 5174 7230 5173 7229
Render<LeftTrapezoid, Solid, FullyDark>_stddev -0.7835 -0.7789 89 19 89 20
Render<LeftTrapezoid, Solid, FullyDark>_cv -0.8453 -0.8420 0 0 0 0
Render<LeftTrapezoid, Solid, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Solid, PartiallyLit>_mean -0.1228 -0.1228 50053 43907 50044 43900
Render<LeftTrapezoid, Solid, PartiallyLit>_median -0.1228 -0.1228 50062 43916 50054 43906
Render<LeftTrapezoid, Solid, PartiallyLit>_stddev +1.3916 +1.3800 63 150 64 151
Render<LeftTrapezoid, Solid, PartiallyLit>_cv +1.7263 +1.7131 0 0 0 0
Render<LeftTrapezoid, Transparent, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Transparent, FullyLit>_mean -0.1584 -0.1583 62677 52751 62665 52743
Render<LeftTrapezoid, Transparent, FullyLit>_median -0.1585 -0.1585 62670 52736 62656 52728
Render<LeftTrapezoid, Transparent, FullyLit>_stddev +1.1429 +1.4086 26 55 23 55
Render<LeftTrapezoid, Transparent, FullyLit>_cv +1.5461 +1.8617 0 0 0 0
Render<LeftTrapezoid, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Transparent, FullyDark>_mean -0.1929 -0.1929 57688 46558 57679 46551
Render<LeftTrapezoid, Transparent, FullyDark>_median -0.1943 -0.1944 57681 46473 57672 46459
Render<LeftTrapezoid, Transparent, FullyDark>_stddev +2.8190 +2.7914 62 237 63 238
Render<LeftTrapezoid, Transparent, FullyDark>_cv +3.7319 +3.6978 0 0 0 0
Render<LeftTrapezoid, Transparent, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<LeftTrapezoid, Transparent, PartiallyLit>_mean -0.0054 -0.0054 70694 70313 70682 70301
Render<LeftTrapezoid, Transparent, PartiallyLit>_median -0.0050 -0.0048 70671 70319 70650 70311
Render<LeftTrapezoid, Transparent, PartiallyLit>_stddev -0.7448 -0.7617 163 42 168 40
Render<LeftTrapezoid, Transparent, PartiallyLit>_cv -0.7434 -0.7604 0 0 0 0
Render<RightTrapezoid, Solid, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Solid, FullyLit>_mean +0.0123 +0.0123 2985 3022 2984 3021
Render<RightTrapezoid, Solid, FullyLit>_median +0.0123 +0.0123 2985 3021 2984 3021
Render<RightTrapezoid, Solid, FullyLit>_stddev -0.4207 -0.4667 1 0 1 0
Render<RightTrapezoid, Solid, FullyLit>_cv -0.4277 -0.4731 0 0 0 0
Render<RightTrapezoid, Solid, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Solid, FullyDark>_mean +0.1105 +0.1105 4894 5435 4893 5434
Render<RightTrapezoid, Solid, FullyDark>_median +0.1083 +0.1082 4902 5433 4901 5432
Render<RightTrapezoid, Solid, FullyDark>_stddev -0.1973 -0.1947 45 37 45 37
Render<RightTrapezoid, Solid, FullyDark>_cv -0.2772 -0.2748 0 0 0 0
Render<RightTrapezoid, Solid, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Solid, PartiallyLit>_mean -0.0169 -0.0169 48201 47386 48192 47379
Render<RightTrapezoid, Solid, PartiallyLit>_median -0.0172 -0.0170 48184 47355 48170 47351
Render<RightTrapezoid, Solid, PartiallyLit>_stddev +0.6070 +0.5204 48 78 50 76
Render<RightTrapezoid, Solid, PartiallyLit>_cv +0.6346 +0.5465 0 0 0 0
Render<RightTrapezoid, Transparent, FullyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Transparent, FullyLit>_mean -0.0023 -0.0023 48751 48639 48742 48632
Render<RightTrapezoid, Transparent, FullyLit>_median -0.0020 -0.0018 48751 48654 48738 48651
Render<RightTrapezoid, Transparent, FullyLit>_stddev +2.4354 +2.4427 10 35 11 36
Render<RightTrapezoid, Transparent, FullyLit>_cv +2.4433 +2.4505 0 0 0 0
Render<RightTrapezoid, Transparent, FullyDark>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Transparent, FullyDark>_mean -0.2247 -0.2247 40942 31742 40936 31736
Render<RightTrapezoid, Transparent, FullyDark>_median -0.2241 -0.2240 40904 31739 40895 31734
Render<RightTrapezoid, Transparent, FullyDark>_stddev -0.3455 -0.3546 165 108 167 108
Render<RightTrapezoid, Transparent, FullyDark>_cv -0.1558 -0.1676 0 0 0 0
Render<RightTrapezoid, Transparent, PartiallyLit>_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
Render<RightTrapezoid, Transparent, PartiallyLit>_mean -0.0908 -0.0908 74269 67523 74256 67512
Render<RightTrapezoid, Transparent, PartiallyLit>_median -0.0898 -0.0897 74196 67536 74176 67523
Render<RightTrapezoid, Transparent, PartiallyLit>_stddev -0.6590 -0.6568 147 50 146 50
Render<RightTrapezoid, Transparent, PartiallyLit>_cv -0.6250 -0.6225 0 0 0 0
BM_RenderBlackTile_pvalue 0.0539 0.0539 U Test, Repetitions: 10 vs 10
BM_RenderBlackTile_mean -0.0188 -0.0188 125 123 125 123
BM_RenderBlackTile_median -0.0263 -0.0264 126 122 125 122
BM_RenderBlackTile_stddev +1.0907 +1.0966 1 3 1 3
BM_RenderBlackTile_cv +1.1307 +1.1368 0 0 0 0
OVERALL_GEOMEAN -0.0207 -0.0207 0 0 0 0
```
8 months ago
|
|
|
SetDungeonMicros(pDungeonCels, MicroTileLen);
|
|
|
|
|
ClearClxDrawCache();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (firstflag) {
|
|
|
|
|
LoadGameLevelFirstFlagEntry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetRndSeedForDungeonLevel();
|
|
|
|
|
|
|
|
|
|
LoadGameLevelStores();
|
|
|
|
|
|
|
|
|
|
if (firstflag || lvldir == ENTRY_LOAD) {
|
|
|
|
|
LoadGameLevelStash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
InitAutomap();
|
|
|
|
|
|
|
|
|
|
if (leveltype != DTYPE_TOWN && lvldir != ENTRY_LOAD) {
|
|
|
|
|
InitLighting();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InitLevelMonsters();
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
const Player &myPlayer = *MyPlayer;
|
|
|
|
|
|
|
|
|
|
if (setlevel) {
|
|
|
|
|
RETURN_IF_ERROR(LoadGameLevelSetLevel(firstflag, lvldir, myPlayer));
|
|
|
|
|
} else {
|
|
|
|
|
RETURN_IF_ERROR(LoadGameLevelStandardLevel(firstflag, lvldir, myPlayer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SyncPortals();
|
|
|
|
|
LoadGameLevelSyncPlayerEntry(lvldir);
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
if (firstflag) {
|
|
|
|
|
RETURN_IF_ERROR(InitMainPanel());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IncProgress();
|
|
|
|
|
|
|
|
|
|
UpdateMonsterLights();
|
|
|
|
|
UnstuckChargers();
|
|
|
|
|
|
|
|
|
|
LoadGameLevelLightVision();
|
|
|
|
|
|
|
|
|
|
if (leveltype == DTYPE_CRYPT) {
|
|
|
|
|
LoadGameLevelCrypt();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef USE_SDL1
|
|
|
|
|
ActivateVirtualGamepad();
|
|
|
|
|
#endif
|
|
|
|
|
LoadGameLevelStartMusic(neededTrack);
|
|
|
|
|
|
|
|
|
|
CompleteProgress();
|
|
|
|
|
|
|
|
|
|
LoadGameLevelCalculateCursor();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool game_loop(bool bStartup)
|
|
|
|
|
{
|
|
|
|
|
const uint16_t wait = bStartup ? sgGameInitInfo.nTickRate * 3 : 3;
|
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < wait; i++) {
|
|
|
|
|
if (!multi_handle_delta()) {
|
|
|
|
|
TimeoutCursor(true);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
TimeoutCursor(false);
|
|
|
|
|
GameLogic();
|
|
|
|
|
ClearLastSentPlayerCmd();
|
|
|
|
|
|
|
|
|
|
if (!gbRunGame || !gbIsMultiplayer || demo::IsRunning() || demo::IsRecording() || !nthread_has_500ms_passed())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void diablo_color_cyc_logic()
|
|
|
|
|
{
|
|
|
|
|
if (!*GetOptions().Graphics.colorCycling)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (PauseMode != 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (leveltype == DTYPE_CAVES) {
|
|
|
|
|
if (setlevel && setlvlnum == Quests[Q_PWATER]._qslvl) {
|
|
|
|
|
UpdatePWaterPalette();
|
|
|
|
|
} else {
|
|
|
|
|
palette_update_caves();
|
|
|
|
|
}
|
|
|
|
|
} else if (leveltype == DTYPE_HELL) {
|
|
|
|
|
lighting_color_cycling();
|
|
|
|
|
} else if (leveltype == DTYPE_NEST) {
|
|
|
|
|
palette_update_hive();
|
|
|
|
|
} else if (leveltype == DTYPE_CRYPT) {
|
|
|
|
|
palette_update_crypt();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsDiabloAlive(bool playSFX)
|
|
|
|
|
{
|
|
|
|
|
if (Quests[Q_DIABLO]._qactive == QUEST_DONE && !gbIsMultiplayer) {
|
|
|
|
|
if (playSFX)
|
|
|
|
|
PlaySFX(SfxID::DiabloDeath);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintScreen(SDL_Keycode vkey)
|
|
|
|
|
{
|
|
|
|
|
ReleaseKey(vkey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace devilution
|