Browse Source

Add Arena Support

pull/5415/head
obligaron 4 years ago committed by Anders Jenbo
parent
commit
795a43dd89
  1. 3
      CMake/Assets.cmake
  2. BIN
      Packaging/resources/assets/arena/church.dun
  3. BIN
      Packaging/resources/assets/arena/circle_of_death.dun
  4. BIN
      Packaging/resources/assets/arena/hell.dun
  5. 104
      Source/control.cpp
  6. 47
      Source/interfac.cpp
  7. 19
      Source/levels/gendung.h
  8. 66
      Source/levels/setmaps.cpp
  9. 41
      Source/levels/trigs.cpp
  10. 3
      Source/missiles.cpp
  11. 2
      Source/player.cpp
  12. 5
      Source/player.h
  13. 7
      Source/quests.cpp

3
CMake/Assets.cmake

@ -41,6 +41,9 @@ if (Gettext_FOUND)
endif()
set(devilutionx_assets
arena/church.dun
arena/circle_of_death.dun
arena/hell.dun
data/boxleftend.clx
data/boxmiddle.clx
data/boxrightend.clx

BIN
Packaging/resources/assets/arena/church.dun

Binary file not shown.

BIN
Packaging/resources/assets/arena/circle_of_death.dun

Binary file not shown.

BIN
Packaging/resources/assets/arena/hell.dun

Binary file not shown.

104
Source/control.cpp

@ -26,6 +26,7 @@
#include "init.h"
#include "inv.h"
#include "inv_iterators.hpp"
#include "levels/setmaps.h"
#include "levels/trigs.h"
#include "lighting.h"
#include "minitext.h"
@ -317,12 +318,113 @@ int DrawDurIcon4Item(const Surface &out, Item &pItem, int x, int c)
return x - 32 - 8;
}
struct TextCmdItem {
const std::string text;
const std::string description;
const std::string requiredParameter;
std::string (*actionProc)(const string_view);
};
extern std::vector<TextCmdItem> TextCmdList;
std::string TextCmdHelp(const string_view parameter)
{
if (parameter.empty()) {
std::string ret;
StrAppend(ret, _("Available Commands:"));
for (const TextCmdItem &textCmd : TextCmdList) {
StrAppend(ret, " ", _(textCmd.text));
}
return ret;
}
auto textCmdIterator = std::find_if(TextCmdList.begin(), TextCmdList.end(), [&](const TextCmdItem &elem) { return elem.text == parameter; });
if (textCmdIterator == TextCmdList.end())
return StrCat(_("Command "), parameter, _(" is unkown."));
auto &textCmdItem = *textCmdIterator;
if (textCmdItem.requiredParameter.empty())
return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: No additional parameter needed."));
return StrCat(_("Description: "), _(textCmdItem.description), _("\nParameters: "), _(textCmdItem.requiredParameter));
}
void AppendArenaOverview(std::string &ret)
{
for (int arena = SL_FIRST_ARENA; arena <= SL_LAST; arena++) {
StrAppend(ret, "\n", arena - SL_FIRST_ARENA + 1, " (", QuestLevelNames[arena], ")");
}
}
const dungeon_type DungeonTypeForArena[] = {
dungeon_type::DTYPE_CATHEDRAL, // SL_ARENA_CHURCH
dungeon_type::DTYPE_HELL, // SL_ARENA_HELL
dungeon_type::DTYPE_HELL, // SL_ARENA_CIRCLE_OF_LIFE
};
std::string TextCmdArena(const string_view parameter)
{
std::string ret;
if (!gbIsMultiplayer) {
StrAppend(ret, _("Arenas are only supported in multiplayer."));
return ret;
}
if (parameter.empty()) {
StrAppend(ret, _("What arena do you want to visit?"));
AppendArenaOverview(ret);
return ret;
}
int arenaNumber = atoi(parameter.data());
_setlevels arenaLevel = static_cast<_setlevels>(arenaNumber - 1 + SL_FIRST_ARENA);
if (arenaNumber < 0 || !IsArenaLevel(arenaLevel)) {
StrAppend(ret, _("Invalid arena-number. Valid numbers are:"));
AppendArenaOverview(ret);
return ret;
}
if (!MyPlayer->isOnLevel(0) && !MyPlayer->isOnArenaLevel()) {
StrAppend(ret, _("To enter a arena, you need to be in town or another arena."));
return ret;
}
setlvltype = DungeonTypeForArena[arenaLevel - SL_FIRST_ARENA];
StartNewLvl(*MyPlayer, WM_DIABSETLVL, arenaLevel);
return ret;
}
std::vector<TextCmdItem> TextCmdList = {
{ N_("/help"), N_("Prints help overview or help for a specific command."), N_("({command})"), &TextCmdHelp },
{ N_("/arena"), N_("Enter a PvP Arena."), N_("{arena-number}"), &TextCmdArena }
};
bool CheckTextCommand(const string_view text)
{
if (text.size() < 1 || text[0] != '/')
return false;
auto textCmdIterator = std::find_if(TextCmdList.begin(), TextCmdList.end(), [&](const TextCmdItem &elem) { return text.find(elem.text) == 0 && (text.length() == elem.text.length() || text[elem.text.length()] == ' '); });
if (textCmdIterator == TextCmdList.end()) {
InitDiabloMsg(StrCat(_("Command \""), text, "\" is unknown."));
return true;
}
TextCmdItem &textCmd = *textCmdIterator;
string_view parameter = "";
if (text.length() > (textCmd.text.length() + 1))
parameter = text.substr(textCmd.text.length() + 1);
const std::string result = textCmd.actionProc(parameter);
if (result != "")
InitDiabloMsg(result);
return true;
}
void ResetTalkMsg()
{
#ifdef _DEBUG
if (CheckDebugTextCommand(TalkMessage))
return;
#endif
if (CheckTextCommand(TalkMessage))
return;
uint32_t pmask = 0;
@ -1294,6 +1396,8 @@ void DiabloHotkeyMsg(uint32_t dwMsg)
if (CheckDebugTextCommand(msg))
continue;
#endif
if (CheckTextCommand(msg))
continue;
char charMsg[MAX_SEND_STR_LEN];
CopyUtf8(charMsg, msg, sizeof(charMsg));
NetSendCmdString(0xFFFFFF, charMsg);

47
Source/interfac.cpp

@ -48,6 +48,28 @@ OptionalOwnedClxSpriteList ArtCutsceneWidescreen;
uint32_t CustomEventsBegin = SDL_USEREVENT;
constexpr uint32_t NumCustomEvents = WM_LAST - WM_FIRST + 1;
Cutscenes GetCutSceneFromLevelType(dungeon_type type)
{
switch (type) {
case DTYPE_TOWN:
return CutTown;
case DTYPE_CATHEDRAL:
return CutLevel1;
case DTYPE_CATACOMBS:
return CutLevel2;
case DTYPE_CAVES:
return CutLevel3;
case DTYPE_HELL:
return CutLevel4;
case DTYPE_NEST:
return CutLevel6;
case DTYPE_CRYPT:
return CutLevel5;
default:
return CutLevel1;
}
}
Cutscenes PickCutscene(interface_mode uMsg)
{
switch (uMsg) {
@ -65,25 +87,7 @@ Cutscenes PickCutscene(interface_mode uMsg)
return CutTown;
if (lvl == 16 && uMsg == WM_DIABNEXTLVL)
return CutGate;
switch (GetLevelType(lvl)) {
case DTYPE_TOWN:
return CutTown;
case DTYPE_CATHEDRAL:
return CutLevel1;
case DTYPE_CATACOMBS:
return CutLevel2;
case DTYPE_CAVES:
return CutLevel3;
case DTYPE_HELL:
return CutLevel4;
case DTYPE_NEST:
return CutLevel6;
case DTYPE_CRYPT:
return CutLevel5;
default:
return CutLevel1;
}
return GetCutSceneFromLevelType(GetLevelType(lvl));
}
case WM_DIABWARPLVL:
return CutPortal;
@ -93,6 +97,11 @@ Cutscenes PickCutscene(interface_mode uMsg)
return CutLevel2;
if (setlvlnum == SL_VILEBETRAYER)
return CutPortalRed;
if (IsArenaLevel(setlvlnum)) {
if (uMsg == WM_DIABSETLVL)
return GetCutSceneFromLevelType(setlvltype);
return CutTown;
}
return CutLevel1;
default:
app_fatal("Unknown progress mode");

19
Source/levels/gendung.h

@ -37,9 +37,26 @@ enum _setlevels : int8_t {
SL_POISONWATER,
SL_VILEBETRAYER,
SL_LAST = SL_VILEBETRAYER,
SL_ARENA_CHURCH,
SL_ARENA_HELL,
SL_ARENA_CIRCLE_OF_LIFE,
SL_FIRST_ARENA = SL_ARENA_CHURCH,
SL_LAST = SL_ARENA_CIRCLE_OF_LIFE,
};
inline bool IsArenaLevel(_setlevels setLevel)
{
switch (setLevel) {
case SL_ARENA_CHURCH:
case SL_ARENA_HELL:
case SL_ARENA_CIRCLE_OF_LIFE:
return true;
default:
return false;
}
}
enum dungeon_type : int8_t {
DTYPE_TOWN,
DTYPE_CATHEDRAL,

66
Source/levels/setmaps.cpp

@ -26,6 +26,9 @@ const char *const QuestLevelNames[] = {
N_("Maze"),
N_("Poisoned Water Supply"),
N_("Archbishop Lazarus' Lair"),
N_("Church Arena"),
N_("Hell Arena"),
N_("Circle of Life Arena"),
};
namespace {
@ -64,6 +67,39 @@ void SetMapTransparency(const char *path)
LoadTransparency(dunData.get());
}
void LoadCustomMap(const char *path, Point viewPosition)
{
switch (setlvltype) {
case DTYPE_CATHEDRAL:
case DTYPE_CRYPT:
LoadL1Dungeon(path, viewPosition);
break;
case DTYPE_CATACOMBS:
LoadL2Dungeon(path, viewPosition);
break;
case DTYPE_CAVES:
case DTYPE_NEST:
LoadL3Dungeon(path, viewPosition);
break;
case DTYPE_HELL:
LoadL4Dungeon(path, viewPosition);
break;
case DTYPE_TOWN:
case DTYPE_NONE:
break;
}
LoadRndLvlPal(setlvltype);
}
void LoadArenaMap(const char *path, Point viewPosition, Point exitTrigger)
{
LoadCustomMap(path, viewPosition);
trigflag = false;
numtrigs = 1;
trigs[0].position = exitTrigger;
trigs[0]._tmsg = WM_DIABRTNLVL;
}
} // namespace
void LoadSetMap()
@ -111,28 +147,18 @@ void LoadSetMap()
AddVileObjs();
InitNoTriggers();
break;
case SL_ARENA_CHURCH:
LoadArenaMap("arena\\church.dun", { 37, 22 }, { 36, 20 });
break;
case SL_ARENA_HELL:
LoadArenaMap("arena\\hell.dun", { 44, 32 }, { 43, 32 });
break;
case SL_ARENA_CIRCLE_OF_LIFE:
LoadArenaMap("arena\\circle_of_death.dun", { 48, 34 }, { 47, 34 });
break;
case SL_NONE:
#ifdef _DEBUG
switch (setlvltype) {
case DTYPE_CATHEDRAL:
case DTYPE_CRYPT:
LoadL1Dungeon(TestMapPath.c_str(), ViewPosition);
break;
case DTYPE_CATACOMBS:
LoadL2Dungeon(TestMapPath.c_str(), ViewPosition);
break;
case DTYPE_CAVES:
case DTYPE_NEST:
LoadL3Dungeon(TestMapPath.c_str(), ViewPosition);
break;
case DTYPE_HELL:
LoadL4Dungeon(TestMapPath.c_str(), ViewPosition);
break;
case DTYPE_TOWN:
case DTYPE_NONE:
break;
}
LoadRndLvlPal(setlvltype);
LoadCustomMap(TestMapPath.c_str(), ViewPosition);
InitNoTriggers();
#endif
break;

41
Source/levels/trigs.cpp

@ -758,6 +758,45 @@ bool ForcePWaterTrig()
return false;
}
bool ForceArenaTrig()
{
int *checkList = nullptr;
switch (setlvltype) {
case DTYPE_TOWN:
checkList = TownWarp1List;
break;
case DTYPE_CATHEDRAL:
checkList = L1UpList;
break;
case DTYPE_CATACOMBS:
checkList = L2TWarpUpList;
break;
case DTYPE_CAVES:
checkList = L3TWarpUpList;
break;
case DTYPE_HELL:
checkList = L4TWarpUpList;
break;
case DTYPE_NEST:
checkList = L5TWarpUpList;
break;
case DTYPE_CRYPT:
checkList = L6TWarpUpList;
break;
default:
return false;
}
for (int i = 0; checkList[i] != -1; i++) {
if (dPiece[cursPosition.x][cursPosition.y] == checkList[i]) {
InfoString = _("Up to town");
cursPosition = trigs[0].position;
return true;
}
}
return false;
}
void CheckTrigForce()
{
trigflag = false;
@ -807,6 +846,8 @@ void CheckTrigForce()
trigflag = ForcePWaterTrig();
break;
default:
if (IsArenaLevel(setlvlnum))
trigflag = ForceArenaTrig();
break;
}
}

3
Source/missiles.cpp

@ -273,6 +273,9 @@ bool Plr2PlrMHit(const Player &player, int p, int mindam, int maxdam, int dist,
*blocked = false;
if (target.isOnArenaLevel() && target._pmode == PM_WALK_SIDEWAYS)
return false;
if (target._pInvincible) {
return false;
}

2
Source/player.cpp

@ -2949,7 +2949,7 @@ StartPlayerKill(Player &player, int earflag)
NetSendCmdParam1(true, CMD_PLRDEAD, earflag);
}
bool diablolevel = gbIsMultiplayer && player.plrlevel == 16;
bool diablolevel = gbIsMultiplayer && (player.isOnLevel(16) || player.isOnArenaLevel());
player.Say(HeroSpeech::AuughUh);

5
Source/player.h

@ -742,6 +742,11 @@ struct Player {
{
return this->plrIsOnSetLevel && this->plrlevel == static_cast<uint8_t>(level);
}
/** @brief Checks if the player is on a arena level. */
bool isOnArenaLevel() const
{
return plrIsOnSetLevel && IsArenaLevel(static_cast<_setlevels>(plrlevel));
}
void setLevel(uint8_t level)
{
this->plrlevel = level;

7
Source/quests.cpp

@ -514,6 +514,13 @@ void SetReturnLvlPos()
break;
case SL_NONE:
break;
default:
if (IsArenaLevel(setlvlnum)) {
ReturnLvlPosition = Towners[TOWN_DRUNK].position + Displacement { 1, 0 };
ReturnLevel = 0;
ReturnLevelType = DTYPE_TOWN;
}
break;
}
}

Loading…
Cancel
Save