From 0e0922d29e885ba09af15ac8104ddff5e39fb1df Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 6 Mar 2024 20:22:59 +1100 Subject: [PATCH 001/244] * increase engine flags from 16 to 24 * player dither invisibility effects * remove some unused invis code for hudweapons --- src/acthudweapon.cpp | 33 ----------- src/actplayer.cpp | 125 +++++++++++++++++++++++++++++++----------- src/draw.cpp | 15 ++++- src/entity.cpp | 9 ++- src/entity.hpp | 3 +- src/entity_editor.cpp | 2 +- src/game.hpp | 2 +- src/net.cpp | 20 ++++++- 8 files changed, 133 insertions(+), 76 deletions(-) diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp index 2ea67e348..53b7aad8f 100644 --- a/src/acthudweapon.cpp +++ b/src/acthudweapon.cpp @@ -328,7 +328,6 @@ enum CrossbowHudweaponChop : int void actHudWeapon(Entity* my) { double result = 0; - bool wearingring = false; Player::HUD_t& playerHud = players[HUDWEAPON_PLAYERNUM]->hud; @@ -491,21 +490,6 @@ void actHudWeapon(Entity* my) } } - // select model - if ( stats[HUDWEAPON_PLAYERNUM]->ring != nullptr ) - { - if ( stats[HUDWEAPON_PLAYERNUM]->ring->type == RING_INVISIBILITY ) - { - wearingring = true; - } - } - if ( stats[HUDWEAPON_PLAYERNUM]->cloak != nullptr ) - { - if ( stats[HUDWEAPON_PLAYERNUM]->cloak->type == CLOAK_INVISIBILITY ) - { - wearingring = true; - } - } if ( players[HUDWEAPON_PLAYERNUM]->entity->skill[3] == 1 || players[HUDWEAPON_PLAYERNUM]->entity->isInvisible() ) // debug cam or player invisible { my->flags[INVISIBLE] = true; @@ -3277,23 +3261,6 @@ void actHudShield(Entity* my) return; } - // select model - bool wearingring = false; - if ( stats[HUDSHIELD_PLAYERNUM]->ring != nullptr ) - { - if ( stats[HUDSHIELD_PLAYERNUM]->ring->type == RING_INVISIBILITY ) - { - wearingring = true; - } - } - if ( stats[HUDSHIELD_PLAYERNUM]->cloak != nullptr ) - { - if ( stats[HUDSHIELD_PLAYERNUM]->cloak->type == CLOAK_INVISIBILITY ) - { - wearingring = true; - } - } - Monster playerRace = players[HUDSHIELD_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HUDSHIELD_PLAYERNUM]->playerRace); if ( players[HUDSHIELD_PLAYERNUM]->entity->effectShapeshift != NOTHING ) { diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 3b424f6a5..4e7b9f235 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -5931,6 +5931,11 @@ void actPlayer(Entity* my) my->flags[INVISIBLE] = true; serverUpdateEntityFlag(my, INVISIBLE); } + if ( !my->flags[INVISIBLE_DITHER] ) + { + my->flags[INVISIBLE_DITHER] = true; + serverUpdateEntityFlag(my, INVISIBLE_DITHER); + } my->flags[BLOCKSIGHT] = false; if ( multiplayer != CLIENT ) { @@ -5948,6 +5953,7 @@ void actPlayer(Entity* my) if ( !entity->flags[INVISIBLE] ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; serverUpdateEntityBodypart(my, i); } } @@ -5960,6 +5966,11 @@ void actPlayer(Entity* my) my->flags[INVISIBLE] = false; serverUpdateEntityFlag(my, INVISIBLE); } + if ( my->flags[INVISIBLE_DITHER] ) + { + my->flags[INVISIBLE_DITHER] = false; + serverUpdateEntityFlag(my, INVISIBLE_DITHER); + } my->flags[BLOCKSIGHT] = true; if ( multiplayer != CLIENT ) { @@ -5984,6 +5995,7 @@ void actPlayer(Entity* my) if ( entity->flags[INVISIBLE] ) { entity->flags[INVISIBLE] = false; + entity->flags[INVISIBLE_DITHER] = false; serverUpdateEntityBodypart(my, i); } } @@ -5991,12 +6003,14 @@ void actPlayer(Entity* my) else if ( stats[PLAYER_NUM]->type == SPIDER ) { // do nothing, these limbs are invisible for the spider so don't unhide. + entity->flags[INVISIBLE_DITHER] = false; } else { if ( entity->flags[INVISIBLE] ) { entity->flags[INVISIBLE] = false; + entity->flags[INVISIBLE_DITHER] = false; serverUpdateEntityBodypart(my, i); } } @@ -8102,6 +8116,7 @@ void actPlayer(Entity* my) if ( bodypart > 12 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; continue; } @@ -8706,6 +8721,7 @@ void actPlayer(Entity* my) entity->focalx = limbs[playerRace][1][0]; entity->focaly = limbs[playerRace][1][1]; entity->focalz = limbs[playerRace][1][2]; + entity->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; if ( multiplayer != CLIENT ) { if ( stats[PLAYER_NUM]->breastplate == NULL || !showEquipment ) @@ -8737,6 +8753,7 @@ void actPlayer(Entity* my) entity->focalx = limbs[playerRace][2][0]; entity->focaly = limbs[playerRace][2][1]; entity->focalz = limbs[playerRace][2][2]; + entity->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; if ( multiplayer != CLIENT ) { if ( stats[PLAYER_NUM]->shoes == NULL || !showEquipment ) @@ -8768,6 +8785,7 @@ void actPlayer(Entity* my) entity->focalx = limbs[playerRace][3][0]; entity->focaly = limbs[playerRace][3][1]; entity->focalz = limbs[playerRace][3][2]; + entity->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; if ( multiplayer != CLIENT ) { if ( stats[PLAYER_NUM]->shoes == NULL || !showEquipment ) @@ -8797,6 +8815,7 @@ void actPlayer(Entity* my) // right arm case 4: { + entity->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; if ( multiplayer != CLIENT ) { if ( stats[PLAYER_NUM]->gloves == NULL || !showEquipment ) @@ -8882,6 +8901,7 @@ void actPlayer(Entity* my) // left arm case 5: { + entity->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; if ( multiplayer != CLIENT ) { if ( stats[PLAYER_NUM]->gloves == NULL || !showEquipment ) @@ -9028,15 +9048,17 @@ void actPlayer(Entity* my) case 6: if ( multiplayer != CLIENT ) { + entity->flags[INVISIBLE_DITHER] = false; if ( swimming ) { entity->flags[INVISIBLE] = true; } else { - if ( stats[PLAYER_NUM]->weapon == NULL || my->isInvisible() ) + if ( stats[PLAYER_NUM]->weapon == NULL ) { entity->flags[INVISIBLE] = true; + //entity->sprite = 0; } else { @@ -9050,7 +9072,14 @@ void actPlayer(Entity* my) entity->flags[INVISIBLE] = false; } } + + if ( my->isInvisible() && stats[PLAYER_NUM]->weapon ) + { + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; + } } + if ( multiplayer == SERVER ) { // update sprites for clients @@ -9059,9 +9088,9 @@ void actPlayer(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -9083,6 +9112,7 @@ void actPlayer(Entity* my) case 7: if ( multiplayer != CLIENT ) { + entity->flags[INVISIBLE_DITHER] = false; if ( swimming ) { entity->flags[INVISIBLE] = true; @@ -9106,9 +9136,11 @@ void actPlayer(Entity* my) } } } - if ( my->isInvisible() ) + + if ( my->isInvisible() && stats[PLAYER_NUM]->shield ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; } } if ( multiplayer == SERVER ) @@ -9119,9 +9151,9 @@ void actPlayer(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -9148,7 +9180,8 @@ void actPlayer(Entity* my) entity->scaley = 1.01; if ( multiplayer != CLIENT ) { - if ( stats[PLAYER_NUM]->cloak == NULL || my->isInvisible() ) + entity->flags[INVISIBLE_DITHER] = false; + if ( stats[PLAYER_NUM]->cloak == NULL ) { entity->flags[INVISIBLE] = true; } @@ -9157,6 +9190,13 @@ void actPlayer(Entity* my) entity->flags[INVISIBLE] = false; entity->sprite = itemModel(stats[PLAYER_NUM]->cloak); } + + if ( my->isInvisible() && stats[PLAYER_NUM]->cloak ) + { + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; + } + if ( multiplayer == SERVER ) { // update sprites for clients @@ -9165,9 +9205,9 @@ void actPlayer(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0))) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -9232,7 +9272,8 @@ void actPlayer(Entity* my) if ( multiplayer != CLIENT ) { entity->sprite = itemModel(stats[PLAYER_NUM]->helmet); - if ( stats[PLAYER_NUM]->helmet == NULL || my->isInvisible() ) + entity->flags[INVISIBLE_DITHER] = false; + if ( stats[PLAYER_NUM]->helmet == NULL ) { entity->flags[INVISIBLE] = true; } @@ -9240,6 +9281,13 @@ void actPlayer(Entity* my) { entity->flags[INVISIBLE] = false; } + + if ( my->isInvisible() && stats[PLAYER_NUM]->helmet ) + { + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; + } + if ( multiplayer == SERVER ) { // update sprites for clients @@ -9248,9 +9296,9 @@ void actPlayer(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -9277,17 +9325,8 @@ void actPlayer(Entity* my) entity->roll = PI / 2; if ( multiplayer != CLIENT ) { - bool hasSteelHelm = false; - /*if ( stats[PLAYER_NUM]->helmet ) - { - if ( stats[PLAYER_NUM]->helmet->type == STEEL_HELM - || stats[PLAYER_NUM]->helmet->type == CRYSTAL_HELM - || stats[PLAYER_NUM]->helmet->type == ARTIFACT_HELM ) - { - hasSteelHelm = true; - } - }*/ - if ( stats[PLAYER_NUM]->mask == NULL || my->isInvisible() || hasSteelHelm ) + entity->flags[INVISIBLE_DITHER] = false; + if ( stats[PLAYER_NUM]->mask == NULL ) { entity->flags[INVISIBLE] = true; } @@ -9295,6 +9334,13 @@ void actPlayer(Entity* my) { entity->flags[INVISIBLE] = false; } + + if ( my->isInvisible() && stats[PLAYER_NUM]->mask ) + { + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; + } + if ( stats[PLAYER_NUM]->mask != NULL ) { if ( stats[PLAYER_NUM]->mask->type == TOOL_GLASSES ) @@ -9318,9 +9364,9 @@ void actPlayer(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -9419,9 +9465,11 @@ void actPlayer(Entity* my) entity->focaly = limbs[playerRace][11][1]; entity->focalz = limbs[playerRace][11][2]; entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; if ( playerRace == INSECTOID || playerRace == CREATURE_IMP ) { entity->flags[INVISIBLE] = my->flags[INVISIBLE]; + entity->flags[INVISIBLE_DITHER] = entity->flags[INVISIBLE]; if ( playerRace == INSECTOID ) { if ( stats[PLAYER_NUM]->sex == FEMALE ) @@ -9529,9 +9577,11 @@ void actPlayer(Entity* my) entity->focaly = limbs[playerRace][12][1]; entity->focalz = limbs[playerRace][12][2]; entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; if ( playerRace == INSECTOID || playerRace == CREATURE_IMP ) { entity->flags[INVISIBLE] = my->flags[INVISIBLE]; + entity->flags[INVISIBLE_DITHER] = entity->flags[INVISIBLE]; if ( playerRace == INSECTOID ) { if ( stats[PLAYER_NUM]->sex == FEMALE ) @@ -9580,23 +9630,27 @@ void actPlayer(Entity* my) if ( bodypart >= 6 && bodypart <= 10 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; if ( playerRace == CREATURE_IMP && bodypart == 7 ) { if ( entity->sprite >= items[SPELLBOOK_LIGHT].index && entity->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) ) { - entity->flags[INVISIBLE] = false; // show spellbooks + entity->flags[INVISIBLE] = my->flags[INVISIBLE]; // show spellbooks + entity->flags[INVISIBLE_DITHER] = entity->flags[INVISIBLE]; } } else if ( playerRace == CREATURE_IMP && bodypart == 6 ) { if ( entity->sprite >= 59 && entity->sprite < 64 ) { - entity->flags[INVISIBLE] = false; // show magicstaffs + entity->flags[INVISIBLE] = my->flags[INVISIBLE]; // show magicstaffs + entity->flags[INVISIBLE_DITHER] = entity->flags[INVISIBLE]; } if ( multiplayer != CLIENT && !stats[PLAYER_NUM]->weapon ) { - entity->flags[INVISIBLE] = true; // show magicstaffs + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } } @@ -9659,11 +9713,13 @@ void actPlayerLimb(Entity* my) else { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } } else { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } return; } @@ -10542,10 +10598,13 @@ void playerAnimateRat(Entity* my) } } } + + entity->flags[INVISIBLE_DITHER] = true; } else if ( bodypart > 1 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } if ( multiplayer == SERVER ) { @@ -10555,9 +10614,9 @@ void playerAnimateRat(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -10589,6 +10648,7 @@ void playerAnimateSpider(Entity* my) if ( bodypart < 11 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; if ( multiplayer == SERVER ) { // update sprites for clients @@ -10597,9 +10657,9 @@ void playerAnimateSpider(Entity* my) entity->skill[10] = entity->sprite; serverUpdateEntityBodypart(my, bodypart); } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) { - entity->skill[11] = entity->flags[INVISIBLE]; + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); serverUpdateEntityBodypart(my, bodypart); } if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) @@ -10701,6 +10761,7 @@ void playerAnimateSpider(Entity* my) } entity->flags[INVISIBLE] = my->flags[INVISIBLE]; + entity->flags[INVISIBLE_DITHER] = true; switch ( bodypart ) { diff --git a/src/draw.cpp b/src/draw.cpp index 0fa5e4f12..82244fc33 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2026,7 +2026,7 @@ void drawEntities3D(view_t* camera, int mode) } } - if ( entity->flags[INVISIBLE] ) + if ( entity->flags[INVISIBLE] && !entity->flags[INVISIBLE_DITHER] ) { continue; } @@ -2123,8 +2123,17 @@ void drawEntities3D(view_t* camera, int mode) if (ditheringDisabled) { dither.value = decrease ? 0 : Entity::Dither::MAX; } else { - dither.value = decrease ? std::max(0, dither.value - 2) : - std::min(Entity::Dither::MAX, dither.value + 2); + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + static ConsoleVariable cvar_dither_invisibility("/dither_invisibility", 5); + dither.value = decrease ? std::max(0, dither.value - 2) : + std::min(*cvar_dither_invisibility, dither.value + 1); + } + else + { + dither.value = decrease ? std::max(0, dither.value - 2) : + std::min(Entity::Dither::MAX, dither.value + 2); + } } } } diff --git a/src/entity.cpp b/src/entity.cpp index c1113dd2c..8f7ae59c2 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -462,7 +462,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli fskill[c] = 0; } skill[2] = -1; - for ( c = 0; c < 16; c++ ) + for ( c = 0; c < 24; c++ ) { flags[c] = false; } @@ -15078,7 +15078,7 @@ void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb) myAttack = this->skill[9]; } - if ( weaponLimb->flags[INVISIBLE] == false ) //TODO: isInvisible()? + if ( weaponLimb->flags[INVISIBLE] == false || weaponLimb->flags[INVISIBLE_DITHER] ) //TODO: isInvisible()? { if ( weaponLimb->sprite == items[SHORTBOW].index ) { @@ -20710,7 +20710,10 @@ void Entity::setHelmetLimbOffsetWithMask(Entity* helm, Entity* mask) return; } - if ( !mask->flags[INVISIBLE] && !helm->flags[INVISIBLE] ) + bool maskVisible = (!mask->flags[INVISIBLE]) || (mask->flags[INVISIBLE] && mask->flags[INVISIBLE_DITHER]); + bool helmVisible = (!helm->flags[INVISIBLE]) || (helm->flags[INVISIBLE] && helm->flags[INVISIBLE_DITHER]); + + if ( maskVisible && helmVisible ) { helm->scalex = 1.01; helm->scaley = 1.01; diff --git a/src/entity.hpp b/src/entity.hpp index 6f91f794d..008252180 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -32,6 +32,7 @@ #define PASSABLE 12 #define USERFLAG1 14 #define USERFLAG2 15 +#define INVISIBLE_DITHER 16 // number of entity skills and fskills static const int NUMENTITYSKILLS = 60; @@ -117,7 +118,7 @@ class Entity // entity attributes real_t fskill[NUMENTITYFSKILLS]; // floating point general purpose variables Sint32 skill[NUMENTITYSKILLS]; // general purpose variables - bool flags[16]; // engine flags + bool flags[24]; // engine flags char* string; // general purpose string light_t* light; // every entity has a specialized light pointer list_t children; // every entity has a list of child objects diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index 68e38d7f7..fa15a29b3 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -411,7 +411,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli fskill[c] = 0; } skill[2] = -1; - for (c = 0; c < 16; ++c) + for (c = 0; c < 24; ++c) { flags[c] = false; } diff --git a/src/game.hpp b/src/game.hpp index d0c1f3444..1ba2f05c7 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -34,7 +34,7 @@ static const char VERSION[] = "v4.2.0"; class Entity; #define DEBUG 1 -#define ENTITY_PACKET_LENGTH 46 +#define ENTITY_PACKET_LENGTH 47 #define NET_PACKET_SIZE 512 // impulses (bound keystrokes, mousestrokes, and joystick/game controller strokes) //TODO: Player-by-player basis. diff --git a/src/net.cpp b/src/net.cpp index 54198890a..9c31baa78 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -521,6 +521,13 @@ void sendEntityUDP(Entity* entity, int c, bool guarantee) SDLNet_Write16((Sint16)(entity->vel_x * 32), &net_packet->data[40]); SDLNet_Write16((Sint16)(entity->vel_y * 32), &net_packet->data[42]); SDLNet_Write16((Sint16)(entity->vel_z * 32), &net_packet->data[44]); + for ( j = 0; j < 8; j++ ) + { + if ( entity->flags[j + 16] ) + { + net_packet->data[46 + j / 8] |= power(2, j - (j / 8) * 8); + } + } net_packet->address.host = net_clients[c - 1].host; net_packet->address.port = net_clients[c - 1].port; net_packet->len = ENTITY_PACKET_LENGTH; @@ -649,7 +656,8 @@ void serverUpdateEntityBodypart(Entity* entity, int bodypart) } Entity* tempEntity = (Entity*)node->element; SDLNet_Write32(tempEntity->sprite, &net_packet->data[9]); - net_packet->data[13] = tempEntity->flags[INVISIBLE]; + net_packet->data[13] = (tempEntity->flags[INVISIBLE] ? 1 : 0); + net_packet->data[13] |= (tempEntity->flags[INVISIBLE_DITHER] ? (1 << 1) : 0); net_packet->address.host = net_clients[c - 1].host; net_packet->address.port = net_clients[c - 1].port; net_packet->len = 14; @@ -1757,6 +1765,13 @@ Entity* receiveEntity(Entity* entity) entity->flags[c] = true; } } + for ( c = 0; c < 8; ++c ) // new flags 16-23 + { + if ( net_packet->data[46 + c / 8] & power(2, c - (c / 8) * 8) ) + { + entity->flags[c + 16] = true; + } + } entity->vel_x = ((Sint16)SDLNet_Read16(&net_packet->data[40])) / 32.0; entity->vel_y = ((Sint16)SDLNet_Read16(&net_packet->data[42])) / 32.0; entity->vel_z = ((Sint16)SDLNet_Read16(&net_packet->data[44])) / 32.0; @@ -2471,7 +2486,8 @@ static std::unordered_map clientPacketHandlers = { Entity* tempEntity = (Entity*)childNode->element; tempEntity->sprite = SDLNet_Read32(&net_packet->data[9]); tempEntity->skill[7] = tempEntity->sprite; - tempEntity->flags[INVISIBLE] = net_packet->data[13]; + tempEntity->flags[INVISIBLE] = (net_packet->data[13] & (1 << 0)) > 0 ? true : false; + tempEntity->flags[INVISIBLE_DITHER] = (net_packet->data[13] & (1 << 1)) > 0 ? true : false; } } }}, From 23ab8539ef7971f909e34178a4418d344b60a50b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 12 Mar 2024 22:06:06 +1100 Subject: [PATCH 002/244] * num players > 4 fix baphy possible issue --- src/actmonster.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index c65cb8507..159124fc5 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -5618,6 +5618,7 @@ void actMonster(Entity* my) if ( numPlayers > 0 ) { difficulty /= numPlayers; // 40/20/13/10 - basically how long you get to wail on Baphy. Shorter is harder. + difficulty = std::max(10, difficulty); } if ( my->monsterSpecialTimer > 60 || (devilstate == 72 && my->monsterSpecialTimer > difficulty)) @@ -7534,10 +7535,10 @@ void actMonster(Entity* my) ++my->monsterSpecialTimer; if ( my->monsterSpecialTimer == 20 ) // start the spawn animations { - int numToSpawn = 3; - int numPlayers = 1; + int numToSpawn = 2; + int numPlayers = 0; std::vector alivePlayers; - for ( int c = 1; c < MAXPLAYERS; ++c ) + for ( int c = 0; c < MAXPLAYERS; ++c ) { if ( !client_disconnected[c] ) { From 1187b24c0644e511a69a492244c501451e1e2ac0 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 12 Mar 2024 22:35:10 +1100 Subject: [PATCH 003/244] * fix maybe crash erudyce --- src/actmonster.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 159124fc5..a09658971 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -2899,8 +2899,15 @@ void actMonster(Entity* my) { // chance to dodge away from target if distance is low enough. playSoundEntity(my, 180, 128); - tangent = atan2(target->y - my->y, target->x - my->x); - dir = tangent + PI; + if ( target ) + { + tangent = atan2(target->y - my->y, target->x - my->x); + dir = tangent + PI; + } + else + { + dir = my->yaw - (PI / 2) + PI * (local_rng.rand() % 2); + } while ( dir < 0 ) { dir += 2 * PI; From bbfa9c732a38edc9ef90963ec75192a68ca09b1a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 15 Mar 2024 11:29:22 +1100 Subject: [PATCH 004/244] * refactor monster animate into function --- src/actmonster.cpp | 118 +++++++++++++++++---------------------------- src/monster.hpp | 1 + 2 files changed, 45 insertions(+), 74 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index a09658971..059228484 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -2194,6 +2194,48 @@ void sentrybotPickSpotNoise(Entity* my, Stat* myStats) void mimicResetIdle(Entity* my); +void monsterAnimate(Entity* my, Stat* myStats, double dist) +{ + switch ( my->getMonsterTypeFromSprite() ) { + case HUMAN: humanMoveBodyparts(my, myStats, dist); break; + case RAT: ratAnimate(my, dist); break; + case GOBLIN: goblinMoveBodyparts(my, myStats, dist); break; + case SLIME: slimeAnimate(my, dist); break; + case TROLL: trollMoveBodyparts(my, myStats, dist); break; + case SPIDER: spiderMoveBodyparts(my, myStats, dist); break; + case GHOUL: ghoulMoveBodyparts(my, myStats, dist); break; + case SKELETON: skeletonMoveBodyparts(my, myStats, dist); break; + case SCORPION: scorpionAnimate(my, dist); break; + case CREATURE_IMP: impMoveBodyparts(my, myStats, dist); break; + case GNOME: gnomeMoveBodyparts(my, myStats, dist); break; + case DEMON: demonMoveBodyparts(my, myStats, dist); actDemonCeilingBuster(my); break; + case SUCCUBUS: succubusMoveBodyparts(my, myStats, dist); break; + case LICH: lichAnimate(my, dist); break; + case MINOTAUR: minotaurMoveBodyparts(my, myStats, dist); actMinotaurCeilingBuster(my); break; + case DEVIL: devilMoveBodyparts(my, myStats, dist); break; + case SHOPKEEPER: shopkeeperMoveBodyparts(my, myStats, dist); break; + case KOBOLD: koboldMoveBodyparts(my, myStats, dist); break; + case SCARAB: scarabAnimate(my, myStats, dist); break; + case CRYSTALGOLEM: crystalgolemMoveBodyparts(my, myStats, dist); break; + case INCUBUS: incubusMoveBodyparts(my, myStats, dist); break; + case VAMPIRE: vampireMoveBodyparts(my, myStats, dist); break; + case SHADOW: shadowMoveBodyparts(my, myStats, dist); break; + case COCKATRICE: cockatriceMoveBodyparts(my, myStats, dist); break; + case INSECTOID: insectoidMoveBodyparts(my, myStats, dist); break; + case GOATMAN: goatmanMoveBodyparts(my, myStats, dist); break; + case AUTOMATON: automatonMoveBodyparts(my, myStats, dist); break; + case LICH_ICE: lichIceAnimate(my, myStats, dist); break; + case LICH_FIRE: lichFireAnimate(my, myStats, dist); break; + case SENTRYBOT: sentryBotAnimate(my, myStats, dist); break; + case SPELLBOT: sentryBotAnimate(my, myStats, dist); break; + case GYROBOT: gyroBotAnimate(my, myStats, dist); break; + case DUMMYBOT: dummyBotAnimate(my, myStats, dist); break; + case MIMIC: mimicAnimate(my, myStats, dist); break; + default: + break; + } +} + void actMonster(Entity* my) { if (!my) @@ -2286,43 +2328,7 @@ void actMonster(Entity* my) else if (MONSTER_INIT) { const auto dist = sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY); - switch (my->getMonsterTypeFromSprite()) { - case HUMAN: humanMoveBodyparts(my, nullptr, dist); break; - case RAT: ratAnimate(my, dist); break; - case GOBLIN: goblinMoveBodyparts(my, nullptr, dist); break; - case SLIME: slimeAnimate(my, dist); break; - case TROLL: trollMoveBodyparts(my, nullptr, dist); break; - case SPIDER: spiderMoveBodyparts(my, nullptr, dist); break; - case GHOUL: ghoulMoveBodyparts(my, nullptr, dist); break; - case SKELETON: skeletonMoveBodyparts(my, nullptr, dist); break; - case SCORPION: scorpionAnimate(my, dist); break; - case CREATURE_IMP: impMoveBodyparts(my, nullptr, dist); break; - case GNOME: gnomeMoveBodyparts(my, nullptr, dist); break; - case DEMON: demonMoveBodyparts(my, nullptr, dist); actDemonCeilingBuster(my); break; - case SUCCUBUS: succubusMoveBodyparts(my, nullptr, dist); break; - case LICH: lichAnimate(my, dist); break; - case MINOTAUR: minotaurMoveBodyparts(my, nullptr, dist); actMinotaurCeilingBuster(my); break; - case DEVIL: devilMoveBodyparts(my, nullptr, dist); break; - case SHOPKEEPER: shopkeeperMoveBodyparts(my, nullptr, dist); break; - case KOBOLD: koboldMoveBodyparts(my, nullptr, dist); break; - case SCARAB: scarabAnimate(my, nullptr, dist); break; - case CRYSTALGOLEM: crystalgolemMoveBodyparts(my, nullptr, dist); break; - case INCUBUS: incubusMoveBodyparts(my, nullptr, dist); break; - case VAMPIRE: vampireMoveBodyparts(my, nullptr, dist); break; - case SHADOW: shadowMoveBodyparts(my, nullptr, dist); break; - case COCKATRICE: cockatriceMoveBodyparts(my, nullptr, dist); break; - case INSECTOID: insectoidMoveBodyparts(my, nullptr, dist); break; - case GOATMAN: goatmanMoveBodyparts(my, nullptr, dist); break; - case AUTOMATON: automatonMoveBodyparts(my, nullptr, dist); break; - case LICH_ICE: lichIceAnimate(my, nullptr, dist); break; - case LICH_FIRE: lichFireAnimate(my, nullptr, dist); break; - case SENTRYBOT: sentryBotAnimate(my, nullptr, dist); break; - case SPELLBOT: sentryBotAnimate(my, nullptr, dist); break; - case GYROBOT: gyroBotAnimate(my, nullptr, dist); break; - case DUMMYBOT: dummyBotAnimate(my, nullptr, dist); break; - case MIMIC: mimicAnimate(my, nullptr, dist); break; - default: break; - } + monsterAnimate(my, nullptr, dist); if ( !intro ) { @@ -8460,43 +8466,7 @@ void actMonster(Entity* my) if ( myStats != NULL ) { const auto dist = sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY); - switch (my->getMonsterTypeFromSprite()) { - case HUMAN: humanMoveBodyparts(my, myStats, dist); break; - case RAT: ratAnimate(my, dist); break; - case GOBLIN: goblinMoveBodyparts(my, myStats, dist); break; - case SLIME: slimeAnimate(my, dist); break; - case TROLL: trollMoveBodyparts(my, myStats, dist); break; - case SPIDER: spiderMoveBodyparts(my, myStats, dist); break; - case GHOUL: ghoulMoveBodyparts(my, myStats, dist); break; - case SKELETON: skeletonMoveBodyparts(my, myStats, dist); break; - case SCORPION: scorpionAnimate(my, dist); break; - case CREATURE_IMP: impMoveBodyparts(my, myStats, dist); break; - case GNOME: gnomeMoveBodyparts(my, myStats, dist); break; - case DEMON: demonMoveBodyparts(my, myStats, dist); actDemonCeilingBuster(my); break; - case SUCCUBUS: succubusMoveBodyparts(my, myStats, dist); break; - case LICH: lichAnimate(my, dist); break; - case MINOTAUR: minotaurMoveBodyparts(my, myStats, dist); actMinotaurCeilingBuster(my); break; - case DEVIL: devilMoveBodyparts(my, myStats, dist); break; - case SHOPKEEPER: shopkeeperMoveBodyparts(my, myStats, dist); break; - case KOBOLD: koboldMoveBodyparts(my, myStats, dist); break; - case SCARAB: scarabAnimate(my, myStats, dist); break; - case CRYSTALGOLEM: crystalgolemMoveBodyparts(my, myStats, dist); break; - case INCUBUS: incubusMoveBodyparts(my, myStats, dist); break; - case VAMPIRE: vampireMoveBodyparts(my, myStats, dist); break; - case SHADOW: shadowMoveBodyparts(my, myStats, dist); break; - case COCKATRICE: cockatriceMoveBodyparts(my, myStats, dist); break; - case INSECTOID: insectoidMoveBodyparts(my, myStats, dist); break; - case GOATMAN: goatmanMoveBodyparts(my, myStats, dist); break; - case AUTOMATON: automatonMoveBodyparts(my, myStats, dist); break; - case LICH_ICE: lichIceAnimate(my, myStats, dist); break; - case LICH_FIRE: lichFireAnimate(my, myStats, dist); break; - case SENTRYBOT: sentryBotAnimate(my, myStats, dist); break; - case SPELLBOT: sentryBotAnimate(my, myStats, dist); break; - case GYROBOT: gyroBotAnimate(my, myStats, dist); break; - case DUMMYBOT: dummyBotAnimate(my, myStats, dist); break; - case MIMIC: mimicAnimate(my, myStats, dist); break; - default: break; - } + monsterAnimate(my, myStats, dist); } } diff --git a/src/monster.hpp b/src/monster.hpp index 812e6939b..09f136ad4 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -763,6 +763,7 @@ void gyroBotDie(Entity* my); void dummyBotDie(Entity* my); void mimicDie(Entity* my); +void monsterAnimate(Entity* my, Stat* myStats, double dist); //--*MoveBodyparts functions-- void humanMoveBodyparts(Entity* my, Stat* myStats, double dist); void ratAnimate(Entity* my, double dist); From 432daf8fb4f0451ecb790c90654f48e3e6d49fbb Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 15 Mar 2024 11:29:37 +1100 Subject: [PATCH 005/244] * wip --- src/ui/GameUI.cpp | 97 +++++++++++++++++++++++++++++++++++++++++++++ src/ui/GameUI.hpp | 1 + src/ui/MainMenu.cpp | 96 ++++++++++++++++++++++---------------------- 3 files changed, 146 insertions(+), 48 deletions(-) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index f61ce29e0..9760ae933 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -21441,6 +21441,103 @@ void createInventoryTooltipFrame(const int player) } view_t playerPortraitView[MAXPLAYERS]; +view_t monsterPortaitView; +void drawMonsterPreview(SDL_Rect pos, int fov, real_t offsetyaw, bool dark) +{ + view_t& view = monsterPortaitView; + auto ofov = ::fov; + ::fov = fov; + + Entity* monster = nullptr; + for ( node_t* node = map.creatures->first; node; node = node->next ) + { + Entity* m = (Entity*)node->element; + if ( m ) + { + if ( auto myStats = m->getStats() ) + { + if ( myStats->getAttribute("monster_portrait") != "" ) + { + monster = m; + + monsterAnimate(monster, myStats, 0.5); + break; + } + } + } + } + + if ( !monster ) { return; } + + static ConsoleVariable cvar_char_portrait_static_angle("/char_portrait_static_angle", true); + view.x = monster->x / 16.0 + (.92 * cos(offsetyaw + + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + view.y = monster->y / 16.0 + (.92 * sin(offsetyaw + + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + view.z = monster->z * 2; + view.ang = (offsetyaw - PI + + (*cvar_char_portrait_static_angle ? monster->yaw : 0)); //5 * PI / 4; + view.vang = PI / 20; + + view.winx = pos.x; + // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 + view.winy = pos.y + (yres - Frame::virtualScreenY); + + view.winw = pos.w; + view.winh = pos.h; + glBeginCamera(&view, false); + bool b = monster->flags[BRIGHT]; + if ( !dark ) { monster->flags[BRIGHT] = true; } + if ( !monster->flags[INVISIBLE] ) + { + glDrawVoxel(&view, monster, REALCOLORS); + } + + monster->flags[BRIGHT] = b; + int c = 0; + + { + for ( node_t* node = monster->children.first; node != nullptr; node = node->next ) + { + if ( c == 0 ) + { + c++; + continue; + } + Entity* entity = (Entity*)node->element; + if ( !entity->flags[INVISIBLE] ) + { + bool b = entity->flags[BRIGHT]; + if ( !dark ) { entity->flags[BRIGHT] = true; } + glDrawVoxel(&view, entity, REALCOLORS); + entity->flags[BRIGHT] = b; + } + c++; + } + //for ( node_t* node = map.entities->first; node != NULL; node = node->next ) + //{ + // Entity* entity = (Entity*)node->element; + // if ( (Sint32)entity->getUID() == -4 ) // torch sprites + // { + // if ( (entity->skill[1] - 1) != player ) + // { + // continue; + // } + // bool b = entity->flags[BRIGHT]; + // if ( !dark ) { entity->flags[BRIGHT] = true; } + // glDrawSprite(&view, entity, REALCOLORS); + // entity->flags[BRIGHT] = b; + // } + //} + } + + if ( drawingGui ) { + // blending gets disabled after objects are drawn, so re-enable it. + GL_CHECK_ERR(glEnable(GL_BLEND)); + } + glEndCamera(&view, false); + ::fov = ofov; +} void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark) { diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index a87c5a5c7..39ecd3215 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -18,6 +18,7 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spe void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); +void drawMonsterPreview(SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); void loadHUDSettingsJSON(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 31965b54f..ef8a666dd 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -10081,6 +10081,8 @@ namespace MainMenu { genericSubwindowFinalizeBasic(*subwindow, y); } + static void openCompendium(); + /******************************************************************************/ static void archivesLeaderboards(Button& button) { @@ -10089,6 +10091,7 @@ namespace MainMenu { static void archivesDungeonCompendium(Button& button) { soundActivate(); + openCompendium(); } static void archivesAchievements(Button& button) { @@ -23933,7 +23936,7 @@ namespace MainMenu { void (*callback)(Button&); }; Option options[] = { - //{"Dungeon Compendium", Language::get(5612), archivesDungeonCompendium}, // TODO + {"Dungeon Compendium", Language::get(5612), archivesDungeonCompendium}, #ifndef STEAMWORKS #if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) {"Achievements", Language::get(5611), archivesAchievements}, @@ -32536,56 +32539,53 @@ namespace MainMenu { createPlayWindow(); }/*, SDL_Rect{ -4, -4, 0, 0 }*/); - /*auto continue_button = window->addButton("continue"); - continue_button->setSize(SDL_Rect{ 39 * 2, 36 * 2, 66 * 2, 50 * 2 }); - continue_button->setBackground("*images/ui/Main Menus/Play/UI_PlayMenu_Button_ContinueB00.png"); - continue_button->setTextColor(makeColor(180, 180, 180, 255)); - continue_button->setTextHighlightColor(makeColor(180, 133, 13, 255)); - continue_button->setText(Language::get(5561)); - continue_button->setFont(smallfont_outline); - if ( continueAvailable ) { - continue_button->setBackgroundHighlighted("*images/ui/Main Menus/Play/UI_PlayMenu_Button_ContinueA00.png"); - continue_button->setCallback([](Button& button) {soundActivate(); playContinue(button); }); - } - else { - continue_button->setCallback([](Button&) {soundError(); }); - } - continue_button->setWidgetSearchParent(window->getName()); - continue_button->setWidgetRight("new"); - continue_button->setWidgetDown("hall_of_trials"); - continue_button->setWidgetBack("back_button"); - continue_button->setGlyphPosition(Widget::glyph_position_t::CENTERED); - continue_button->setButtonsOffset(SDL_Rect{ 0, 29, 0, 0, }); - continue_button->setSelectorOffset(SDL_Rect{ -1, -1, 1, 1 }); + } +#endif - auto new_button = window->addButton("new"); - new_button->setSize(SDL_Rect{ 114 * 2, 36 * 2, 68 * 2, 56 * 2 }); - new_button->setBackground("*images/ui/Main Menus/Play/UI_PlayMenu_NewB00.png"); - new_button->setBackgroundHighlighted("*images/ui/Main Menus/Play/UI_PlayMenu_NewA00.png"); - new_button->setTextColor(makeColor(180, 180, 180, 255)); - new_button->setTextHighlightColor(makeColor(180, 133, 13, 255)); - new_button->setText(Language::get(5562)); - new_button->setFont(smallfont_outline); - new_button->setCallback(playNew); - new_button->setWidgetSearchParent(window->getName()); - new_button->setWidgetLeft("continue"); - new_button->setWidgetDown("hall_of_trials"); - new_button->setWidgetBack("back_button"); - new_button->setGlyphPosition(Widget::glyph_position_t::CENTERED); - new_button->setButtonsOffset(SDL_Rect{ 0, 29, 0, 0, }); - new_button->setSelectorOffset(SDL_Rect{ -1, -1, -3, -11 });*/ + static void openCompendium() { + auto dimmer = main_menu_frame->addFrame("dimmer"); + dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); + dimmer->setActualSize(dimmer->getSize()); + dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setBorder(0); - /*if ( !gameModeManager.Tutorial.FirstTimePrompt.showFirstTimePrompt ) { - if ( continueAvailable ) { - continue_button->select(); - } - else { - new_button->select(); + auto window = dimmer->addFrame("compendium"); + window->setSize(SDL_Rect{ + (Frame::virtualScreenX - 1222) / 2, + (Frame::virtualScreenY - 598) / 2, + 1222, + 598 }); + window->setColor(0); + window->setBorder(0); + + auto background = window->addImage( + SDL_Rect{ 0, 0, window->getSize().w, window->getSize().h }, + 0xffffffff, + "*images/ui/Main Menus/AdventureArchives/A_Window_BG_00.png", + "background" + ); + + auto model_viewer = window->addFrame("model_viewer"); + model_viewer->setSize(SDL_Rect{284, 32, 406, 406}); + model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + drawMonsterPreview(pos, 50, 0.0); + }); + + if ( auto monster = summonMonsterNoSmoke(SKELETON, 8, 8, true) ) + { + if ( auto myStats = monster->getStats() ) + { + myStats->setAttribute("monster_portrait", "true"); } } - else { - hall_of_trials_button->select(); - }*/ + + (void)createBackWidget(window, [](Button& button) { + soundCancel(); + auto frame = static_cast(button.getParent()); + frame = static_cast(frame->getParent()); + frame = static_cast(frame->getParent()); + frame->removeSelf(); + assert(main_menu_frame); + }/*, SDL_Rect{ -4, -4, 0, 0 }*/); } -#endif } From 027ffdf53a0b06801fe44938ad4651c175346455 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 15 Mar 2024 21:32:49 +1100 Subject: [PATCH 006/244] * tutorial_scores - fix for corrupted files, fix for nx writing not added extra '\0' --- src/mod_tools.cpp | 53 ++++++++++++++++++++++++++++++++++++++++------- src/mod_tools.hpp | 1 + 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 793b6722d..f4395e505 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -212,6 +212,24 @@ void GameModeManager_t::Tutorial_t::readFromFile() if ( !d.HasMember("version") || !d.HasMember("levels") ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + + // recreate this file, corrupted? + printlog("[JSON]: File %s corrupt, recreating...", inputPath.c_str()); + d.Clear(); + d.SetObject(); + CustomHelpers::addMemberToRoot(d, "version", rapidjson::Value(1)); + CustomHelpers::addMemberToRoot(d, "first_time_prompt", rapidjson::Value(FirstTimePrompt.showFirstTimePrompt)); + CustomHelpers::addMemberToRoot(d, "first_tutorial_complete", rapidjson::Value(firstTutorialCompleted)); + + rapidjson::Value levelsObj(rapidjson::kObjectType); + CustomHelpers::addMemberToRoot(d, "levels", levelsObj); + for ( auto it = levels.begin(); it != levels.end(); ++it ) + { + rapidjson::Value level(rapidjson::kObjectType); + level.AddMember("completion_time", rapidjson::Value(it->completionTime), d.GetAllocator()); + CustomHelpers::addMemberToSubkey(d, "levels", it->filename, level); + } + writeToFile(d); return; } int version = d["version"].GetInt(); @@ -288,16 +306,37 @@ void GameModeManager_t::Tutorial_t::writeToDocument() rapidjson::Document d; d.ParseStream(is); - d["first_time_prompt"].SetBool(this->FirstTimePrompt.showFirstTimePrompt); - if ( !d.HasMember("first_tutorial_complete") ) + if ( !d.HasMember("version") || !d.HasMember("levels") ) { - CustomHelpers::addMemberToRoot(d, "first_tutorial_complete", rapidjson::Value(false)); - } - d["first_tutorial_complete"].SetBool(this->firstTutorialCompleted); + printlog("[JSON]: File %s corrupt, recreating...", inputPath.c_str()); + d.Clear(); + d.SetObject(); + CustomHelpers::addMemberToRoot(d, "version", rapidjson::Value(1)); + CustomHelpers::addMemberToRoot(d, "first_time_prompt", rapidjson::Value(FirstTimePrompt.showFirstTimePrompt)); + CustomHelpers::addMemberToRoot(d, "first_tutorial_complete", rapidjson::Value(firstTutorialCompleted)); - for ( auto it = levels.begin(); it != levels.end(); ++it ) + rapidjson::Value levelsObj(rapidjson::kObjectType); + CustomHelpers::addMemberToRoot(d, "levels", levelsObj); + for ( auto it = levels.begin(); it != levels.end(); ++it ) + { + rapidjson::Value level(rapidjson::kObjectType); + level.AddMember("completion_time", rapidjson::Value(it->completionTime), d.GetAllocator()); + CustomHelpers::addMemberToSubkey(d, "levels", it->filename, level); + } + } + else { - d["levels"][it->filename.c_str()]["completion_time"].SetUint(it->completionTime); + d["first_time_prompt"].SetBool(this->FirstTimePrompt.showFirstTimePrompt); + if ( !d.HasMember("first_tutorial_complete") ) + { + CustomHelpers::addMemberToRoot(d, "first_tutorial_complete", rapidjson::Value(false)); + } + d["first_tutorial_complete"].SetBool(this->firstTutorialCompleted); + + for ( auto it = levels.begin(); it != levels.end(); ++it ) + { + d["levels"][it->filename.c_str()]["completion_time"].SetUint(it->completionTime); + } } writeToFile(d); diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index f511459ae..c746f98c9 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2658,6 +2658,7 @@ class GameModeManager_t rapidjson::PrettyWriter writer(os); d.Accept(writer); fp->write(os.GetString(), sizeof(char), os.GetSize()); + fp->write("", sizeof(char), 1); FileIO::close(fp); } From 9f08bd8e928adce29314a54ac011a11f62f1f7c3 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 17 Mar 2024 18:04:16 +1100 Subject: [PATCH 007/244] * dump cache before book reload as causes issues with alternate .ttf files --- src/mod_tools.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 4ff3c14a6..081f3dd2e 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -9417,6 +9417,7 @@ void Mods::unloadMods(bool force) // reload books if ( Mods::booksRequireReloadUnmodded ) { + consoleCommand("/dumpcache"); physfsReloadBooks(); Mods::booksRequireReloadUnmodded = false; } @@ -9637,11 +9638,13 @@ void Mods::loadMods() if ( physfsSearchBooksToUpdate() ) { + consoleCommand("/dumpcache"); physfsReloadBooks(); Mods::booksRequireReloadUnmodded = true; } else if ( Mods::booksRequireReloadUnmodded ) // clean revert if we had loaded mods but can't find any modded ones { + consoleCommand("/dumpcache"); physfsReloadBooks(); Mods::booksRequireReloadUnmodded = false; } From 415cb6b081b3913e9095f2dc8fb24dd9a822e2cc Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 18 Mar 2024 00:36:50 +1100 Subject: [PATCH 008/244] * fix left click bind closing signs immediately --- src/interface/bookgui.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/interface/bookgui.cpp b/src/interface/bookgui.cpp index 77cd0983c..3b7dfc0ea 100644 --- a/src/interface/bookgui.cpp +++ b/src/interface/bookgui.cpp @@ -598,6 +598,12 @@ void Player::SignGUI_t::openSign(std::string name, Uint32 uid) signWorldCoordX = entity->x; signWorldCoordY = entity->y; } + + // fix for binding left click to open sign + { + Input::inputs[player.playernum].consumeBinaryToggle("MenuLeftClick"); + Input::inputs[player.playernum].consumeBindingsSharedWithBinding("MenuLeftClick"); + } } void Player::SignGUI_t::closeSignGUI() From db226ae80f71d518ee31df905c41065a7bbdb44a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 18 Mar 2024 14:11:38 +1100 Subject: [PATCH 009/244] * don't gen mimic on vampire quest chest --- src/maps.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/maps.cpp b/src/maps.cpp index 06191530a..9e53e1a20 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -7195,6 +7195,11 @@ void assignActions(map_t* map) for ( auto chest : mimics ) { + if ( chest == vampireQuestChest ) + { + continue; + } + // mimic numMimics++; Entity* entity = newEntity(10, 1, map->entities, map->creatures); From 8b431cd0c763d921b68ed3d334009255267b4938 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 31 Mar 2024 01:53:17 +1100 Subject: [PATCH 010/244] * fix starvation not occurring for hunger multipliers --- src/entity.cpp | 79 +++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 8f7ae59c2..9e4d99061 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -395,9 +395,13 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli { mynode = list_AddNodeLast(entlist); } - mynode->element = this; - mynode->deconstructor = &entityDeconstructor; - mynode->size = sizeof(Entity); + + if ( mynode ) + { + mynode->element = this; + mynode->deconstructor = &entityDeconstructor; + mynode->size = sizeof(Entity); + } myCreatureListNode = nullptr; if ( creaturelist ) @@ -3427,51 +3431,54 @@ void Entity::handleEffects(Stat* myStats) } } } - else if ( ticks % hungerTickRate == 0 ) + else { - //messagePlayer(0, "hungertick %d, curr %d, players: %d", hungerTickRate, myStats->HUNGER, playerCount); if ( myStats->HUNGER > 0 && !playerAutomaton ) { - myStats->HUNGER--; - Sint32 noLongerFull = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_OVERSATIATED); - Sint32 youFeelHungry = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_HUNGRY); - Sint32 youFeelWeak = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_WEAK); - Sint32 youFeelFaint = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_STARVING); - - if ( myStats->HUNGER == noLongerFull ) + if ( ticks % hungerTickRate == 0 ) { - if ( !myStats->EFFECTS[EFF_VOMITING] ) + //messagePlayer(0, "hungertick %d, curr %d, players: %d", hungerTickRate, myStats->HUNGER, playerCount); + myStats->HUNGER--; + Sint32 noLongerFull = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_OVERSATIATED); + Sint32 youFeelHungry = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_HUNGRY); + Sint32 youFeelWeak = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_WEAK); + Sint32 youFeelFaint = getEntityHungerInterval(player, this, myStats, HUNGER_INTERVAL_STARVING); + + if ( myStats->HUNGER == noLongerFull ) { - messagePlayer(player, MESSAGE_STATUS, Language::get(629)); + if ( !myStats->EFFECTS[EFF_VOMITING] ) + { + messagePlayer(player, MESSAGE_STATUS, Language::get(629)); + } + serverUpdateHunger(player); } - serverUpdateHunger(player); - } - else if ( myStats->HUNGER == youFeelHungry ) - { - if ( !myStats->EFFECTS[EFF_VOMITING] ) + else if ( myStats->HUNGER == youFeelHungry ) { - messagePlayer(player, MESSAGE_STATUS, Language::get(630)); - playSoundPlayer(player, 32, 128); + if ( !myStats->EFFECTS[EFF_VOMITING] ) + { + messagePlayer(player, MESSAGE_STATUS, Language::get(630)); + playSoundPlayer(player, 32, 128); + } + serverUpdateHunger(player); } - serverUpdateHunger(player); - } - else if ( myStats->HUNGER == youFeelWeak ) - { - if ( !myStats->EFFECTS[EFF_VOMITING] ) + else if ( myStats->HUNGER == youFeelWeak ) { - messagePlayer(player, MESSAGE_STATUS, Language::get(631)); - playSoundPlayer(player, 32, 128); + if ( !myStats->EFFECTS[EFF_VOMITING] ) + { + messagePlayer(player, MESSAGE_STATUS, Language::get(631)); + playSoundPlayer(player, 32, 128); + } + serverUpdateHunger(player); } - serverUpdateHunger(player); - } - else if ( myStats->HUNGER == youFeelFaint ) - { - if ( !myStats->EFFECTS[EFF_VOMITING] ) + else if ( myStats->HUNGER == youFeelFaint ) { - messagePlayer(player, MESSAGE_STATUS, Language::get(632)); - playSoundPlayer(player, 32, 128); + if ( !myStats->EFFECTS[EFF_VOMITING] ) + { + messagePlayer(player, MESSAGE_STATUS, Language::get(632)); + playSoundPlayer(player, 32, 128); + } + serverUpdateHunger(player); } - serverUpdateHunger(player); } } else From 4d8439b67bdf206a612da6fb8d3bad38de83024d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 8 Apr 2024 02:35:12 +1000 Subject: [PATCH 011/244] * compedium stuff --- src/actmonster.cpp | 9 +- src/init_game.cpp | 3 + src/interface/consolecommand.cpp | 30 + src/mod_tools.cpp | 235 ++++++ src/mod_tools.hpp | 58 ++ src/ui/GameUI.cpp | 78 +- src/ui/GameUI.hpp | 2 +- src/ui/MainMenu.cpp | 1232 +++++++++++++++++++++++++++++- 8 files changed, 1601 insertions(+), 46 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 6b134964e..d63d8ce88 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -8504,7 +8504,14 @@ void actMonster(Entity* my) if ( myStats != NULL ) { const auto dist = sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY); - monsterAnimate(my, myStats, dist); + if ( myStats->getAttribute("monster_portrait") != "" ) + { + monsterAnimate(my, myStats, 0.2); + } + else + { + monsterAnimate(my, myStats, dist); + } } } diff --git a/src/init_game.cpp b/src/init_game.cpp index 15c73b40c..1a47bc9e5 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -91,6 +91,9 @@ void initGameDatafiles(bool moddedReload) { EquipmentModelOffsets.readFromFile(monstertypename[c], c); } + CompendiumEntries.readMonstersFromFile(); + CompendiumEntries.readWorldFromFile(); + CompendiumEntries.readCodexFromFile(); } void initGameDatafilesAsync(bool moddedReload) diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index d637d5404..8f214d4f5 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4188,6 +4188,24 @@ namespace ConsoleCommands { MonsterData_t::loadMonsterDataJSON(); }); + static ConsoleCommand ccmd_itemlevelcurve("/itemlevelcurve", "generate item level curve drop", []CCMD{ + if ( !(svFlags & SV_FLAG_CHEATS) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); + return; + } + + if ( argc < 2 ) + { + return; + } + + int cat = atoi(argv[1]); + cat = std::min(std::max(0, cat), NUMCATEGORIES - 1); + ItemType type = itemLevelCurve((Category)cat, 0, currentlevel, local_rng); + dropItem(newItem(type, EXCELLENT, 0, 1, local_rng.rand(), true, &stats[clientnum]->inventory), 0); + }); + static ConsoleCommand ccmd_spawnitem2("/spawnitem2", "spawn an item with beatitude and status (/spawnitem -2 5 wooden shield) (cheat)", []CCMD{ if ( !(svFlags & SV_FLAG_CHEATS) ) { @@ -4845,6 +4863,18 @@ namespace ConsoleCommands { } }); + static ConsoleCommand ccmd_reloadcompendiummonsters("/reloadcompendiummonsters", "reloads compendium entries", []CCMD{ + CompendiumEntries.readMonstersFromFile(); + }); + + static ConsoleCommand ccmd_reloadcompendiumworld("/reloadcompendiumworld", "reloads compendium entries", []CCMD{ + CompendiumEntries.readWorldFromFile(); + }); + + static ConsoleCommand ccmd_reloadcompendiumcodex("/reloadcompendiumcodex", "reloads compendium entries", []CCMD{ + CompendiumEntries.readCodexFromFile(); + }); + static ConsoleCommand ccmd_mapdebugfixedmonsters("/mapdebugfixedmonsters", "prints fixed monster spawns", []CCMD{ #ifndef NINTENDO if ( !(svFlags & SV_FLAG_CHEATS) ) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 081f3dd2e..90af0d185 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10603,3 +10603,238 @@ bool GameModeManager_t::CurrentSession_t::ChallengeRun_t::loadScenario() return true; } #endif + +void jsonVecToVec(rapidjson::Value& val, std::vector& vec ) +{ + for ( auto itr = val.Begin(); itr != val.End(); ++itr ) + { + if ( itr->IsString() ) + { + vec.push_back(itr->GetString()); + } + } +} + +void jsonVecToVec(rapidjson::Value& val, std::vector& vec) +{ + for ( auto itr = val.Begin(); itr != val.End(); ++itr ) + { + if ( itr->IsInt() ) + { + vec.push_back(itr->GetInt()); + } + } +} + +Compendium_t CompendiumEntries; +std::vector> Compendium_t::CompendiumMonsters_t::contents; +std::map Compendium_t::CompendiumMonsters_t::contentsMap; +std::vector> Compendium_t::CompendiumWorld_t::contents; +std::map Compendium_t::CompendiumWorld_t::contentsMap; +std::vector> Compendium_t::CompendiumCodex_t::contents; +std::map Compendium_t::CompendiumCodex_t::contentsMap; + +void Compendium_t::readCodexFromFile() +{ + const std::string filename = "data/compendium/codex.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("codex") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + codex.clear(); + CompendiumCodex_t::contents.clear(); + CompendiumCodex_t::contentsMap.clear(); + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumCodex_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumCodex_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + } + } + + auto& entries = d["codex"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + auto& w = itr->value; + auto& obj = codex[name]; + + jsonVecToVec(w["blurb"], obj.blurb); + jsonVecToVec(w["details"], obj.details); + obj.imagePath = w["img"].GetString(); + } +} + +void Compendium_t::readWorldFromFile() +{ + const std::string filename = "data/compendium/world.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("world") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + worldObjects.clear(); + CompendiumWorld_t::contents.clear(); + CompendiumWorld_t::contentsMap.clear(); + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumWorld_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumWorld_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + } + } + + auto& entries = d["world"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + auto& w = itr->value; + auto& obj = worldObjects[name]; + + jsonVecToVec(w["blurb"], obj.blurb); + jsonVecToVec(w["details"], obj.details); + obj.imagePath = w["img"].GetString(); + } +} + +void Compendium_t::readMonstersFromFile() +{ + const std::string filename = "data/compendium/monsters.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("monsters") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + monsters.clear(); + CompendiumMonsters_t::contents.clear(); + CompendiumMonsters_t::contentsMap.clear(); + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumMonsters_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumMonsters_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + } + } + + auto& entries = d["monsters"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + int monsterType = NOTHING; + for ( int i = 0; i < NUMMONSTERS; ++i ) + { + if ( name == monstertypename[i] ) + { + monsterType = i; + break; + } + } + + if ( monsterType == NOTHING ) { continue; } + + auto& m = itr->value; + auto& monster = monsters[name]; + monster.monsterType = monsterType; + jsonVecToVec(m["blurb"], monster.blurb); + auto& stats = m["stats"]; + jsonVecToVec(stats["hp"], monster.hp); + jsonVecToVec(stats["ac"], monster.ac); + jsonVecToVec(stats["spd"], monster.spd); + jsonVecToVec(stats["atk"], monster.atk); + jsonVecToVec(stats["rangeatk"], monster.rangeatk); + jsonVecToVec(stats["pwr"], monster.pwr); + + jsonVecToVec(m["abilities"], monster.abilities); + { + int i = 0; + for ( auto itr = m["resistances"].Begin(); itr != m["resistances"].End(); ++itr ) + { + monster.resistances[i] = itr->GetInt(); + ++i; + } + } + jsonVecToVec(m["inventory"], monster.inventory); + } +} \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 93e0a0ba2..e62e77739 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3448,3 +3448,61 @@ struct EquipmentModelOffsets_t ModelOffset_t& getModelOffset(int monster, int sprite); }; extern EquipmentModelOffsets_t EquipmentModelOffsets; + +struct Compendium_t +{ + struct CompendiumMonsters_t + { + struct Monster_t + { + int monsterType = NOTHING; + std::vector blurb; + std::vector hp; + std::vector spd; + std::vector ac; + std::vector atk; + std::vector rangeatk; + std::vector pwr; + std::array resistances; + std::vector abilities; + std::vector inventory; + std::string imagePath = ""; + }; + static std::vector> contents; + static std::map contentsMap; + }; + std::map monsters; + void readMonstersFromFile(); + + struct CompendiumWorld_t + { + struct World_t + { + int modelIndex = -1; + std::string imagePath = ""; + std::vector blurb; + std::vector details; + }; + static std::vector> contents; + static std::map contentsMap; + }; + std::map worldObjects; + void readWorldFromFile(); + + struct CompendiumCodex_t + { + struct Codex_t + { + int modelIndex = -1; + std::string imagePath = ""; + std::vector blurb; + std::vector details; + }; + static std::vector> contents; + static std::map contentsMap; + }; + std::map codex; + void readCodexFromFile(); +}; + +extern Compendium_t CompendiumEntries; \ No newline at end of file diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 9760ae933..c8c2d41db 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -21442,42 +21442,62 @@ void createInventoryTooltipFrame(const int player) view_t playerPortraitView[MAXPLAYERS]; view_t monsterPortaitView; -void drawMonsterPreview(SDL_Rect pos, int fov, real_t offsetyaw, bool dark) +void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark) { - view_t& view = monsterPortaitView; - auto ofov = ::fov; - ::fov = fov; + if ( !monster ) { return; } - Entity* monster = nullptr; - for ( node_t* node = map.creatures->first; node; node = node->next ) + static int fov = 50; + static real_t ang = 0.0; + static real_t vang = 0.0; + static real_t zoom = 0.0; + static real_t z = 0.0; + if ( keystatus[SDLK_KP_1] ) { - Entity* m = (Entity*)node->element; - if ( m ) - { - if ( auto myStats = m->getStats() ) - { - if ( myStats->getAttribute("monster_portrait") != "" ) - { - monster = m; - - monsterAnimate(monster, myStats, 0.5); - break; - } - } - } + vang -= 0.01; + } + if ( keystatus[SDLK_KP_3] ) + { + vang += 0.01; + } + if ( keystatus[SDLK_KP_4] ) + { + ang -= 0.01; + } + if ( keystatus[SDLK_KP_6] ) + { + ang += 0.01; + } + if ( keystatus[SDLK_KP_8] ) + { + z += 0.5; + } + if ( keystatus[SDLK_KP_2] ) + { + z -= 0.5; + } + if ( keystatus[SDLK_KP_7] ) + { + zoom -= 0.01; + } + if ( keystatus[SDLK_KP_9] ) + { + zoom += 0.01; } - if ( !monster ) { return; } - static ConsoleVariable cvar_char_portrait_static_angle("/char_portrait_static_angle", true); - view.x = monster->x / 16.0 + (.92 * cos(offsetyaw - + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); - view.y = monster->y / 16.0 + (.92 * sin(offsetyaw - + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); - view.z = monster->z * 2; + view_t& view = monsterPortaitView; + auto ofov = ::fov; + ::fov = fov; + + static ConsoleVariable cvar_char_portrait_static_angle("/compendium_portrait_static_angle", true); + view.x = monster->x / 16.0 + ((.92 + zoom) * cos(offsetyaw + + ang + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + view.y = monster->y / 16.0 + ((.92 + zoom) * sin(offsetyaw + + ang + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + view.z = monster->z * 2 + z; view.ang = (offsetyaw - PI - + (*cvar_char_portrait_static_angle ? monster->yaw : 0)); //5 * PI / 4; - view.vang = PI / 20; + + (*cvar_char_portrait_static_angle ? monster->yaw : 0) + ang); //5 * PI / 4; + view.vang = PI / 20 + vang; view.winx = pos.x; // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index 39ecd3215..f3d6f7243 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -18,7 +18,7 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spe void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); -void drawMonsterPreview(SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); +void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); void loadHUDSettingsJSON(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 42d31c68a..9c863cc67 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32542,6 +32542,894 @@ namespace MainMenu { } #endif + static Entity* compendiumMonster = nullptr; + static Entity* createCompendiumMonster(Monster creature, real_t x, real_t y) + { + if ( compendiumMonster ) + { + list_RemoveNode(compendiumMonster->mynode); + compendiumMonster = nullptr; + } + + Entity* entity = newEntity(-1, 1, map.entities, map.creatures); //Monster entity. + compendiumMonster = entity; + //Set the monster's variables. + entity->sizex = 4; + entity->sizey = 4; + entity->x = x; + entity->y = y; + entity->z = 6; + entity->yaw = (local_rng.rand() % 360) * PI / 180.0; + entity->behavior = &actMonster; + entity->flags[UPDATENEEDED] = false; + entity->flags[INVISIBLE] = true; + entity->ranbehavior = true; + + Stat* myStats = nullptr; + if ( multiplayer != CLIENT ) + { + // Need to give the entity its list stuff. + // create an empty first node for traversal purposes + node_t* node = nullptr; + node = list_AddNodeFirst(&entity->children); + node->element = nullptr; + node->deconstructor = &emptyDeconstructor; + + myStats = new Stat(creature + 1000); + node = list_AddNodeLast(&entity->children); //ASSUMING THIS ALREADY EXISTS WHEN THIS FUNCTION IS CALLED. + node->element = myStats; + node->size = sizeof(myStats); + node->deconstructor = &statDeconstructor; + if ( entity->parent ) + { + myStats->leader_uid = entity->parent; + entity->parent = 0; + } + myStats->type = creature; + } + else + { + //Give dummy stats. + entity->clientStats = new Stat(creature + 1000); + } + + return compendiumMonster; + } + + static ConsoleVariable compendiumMonsterSectionPadY("/compendiumMonsterSectionPadY", 32); + + static void refreshCompendiumEntryWorld(std::string name, Frame* parent) + { + if ( CompendiumEntries.worldObjects.find(name) == CompendiumEntries.worldObjects.end() ) + { + return; + } + auto& entry = CompendiumEntries.worldObjects[name]; + + if ( Frame* page_left = parent->findFrame("page_left") ) + { + if ( auto blurb = page_left->findField("blurb") ) + { + std::string txt = ""; + for ( auto& str : entry.blurb ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + blurb->setText(txt.c_str()); + } + } + + if ( Frame* page_right = parent->findFrame("page_right") ) + { + if ( page_right = page_right->findFrame("page_right_inner") ) + { + if ( auto details = page_right->findField("details") ) + { + std::string txt = ""; + for ( auto& str : entry.details ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + details->setText(txt.c_str()); + } + } + } + } + + static void refreshCompendiumEntryCodex(std::string name, Frame* parent) + { + if ( CompendiumEntries.codex.find(name) == CompendiumEntries.codex.end() ) + { + return; + } + auto& entry = CompendiumEntries.codex[name]; + + if ( Frame* page_left = parent->findFrame("page_left") ) + { + if ( auto blurb = page_left->findField("blurb") ) + { + std::string txt = ""; + for ( auto& str : entry.blurb ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + blurb->setText(txt.c_str()); + } + } + + if ( Frame* page_right = parent->findFrame("page_right") ) + { + if ( page_right = page_right->findFrame("page_right_inner") ) + { + if ( auto details = page_right->findField("details") ) + { + std::string txt = ""; + for ( auto& str : entry.details ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + details->setText(txt.c_str()); + } + } + } + } + + static void refreshCompendiumEntryMonster(std::string name, Frame* parent) + { + if ( CompendiumEntries.monsters.find(name) == CompendiumEntries.monsters.end() ) + { + return; + } + auto& entry = CompendiumEntries.monsters[name]; + if ( compendiumMonster ) + { + if ( auto myStats = compendiumMonster->getStats() ) + { + if ( Frame* page_left = parent->findFrame("page_left") ) + { + if ( auto blurb = page_left->findField("blurb") ) + { + std::string txt = ""; + for ( auto& str : entry.blurb ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + blurb->setText(txt.c_str()); + } + } + if ( Frame* page_right = parent->findFrame("page_right") ) + { + if ( page_right = page_right->findFrame("page_right_inner") ) + { + if ( auto txt = page_right->findField("hp") ) + { + char buf[32] = "-"; + auto& stat = entry.hp; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "HP %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "HP %d", stat[0]); + } + txt->setText(buf); + } + if ( auto txt = page_right->findField("ac") ) + { + char buf[32] = "-"; + auto& stat = entry.ac; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "AC %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "AC %d", stat[0]); + } + txt->setText(buf); + } + if ( auto txt = page_right->findField("spd") ) + { + char buf[32] = "-"; + auto& stat = entry.spd; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "SPD %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "SPD %d", stat[0]); + } + txt->setText(buf); + } + if ( auto txt = page_right->findField("atk") ) + { + char buf[32] = "-"; + auto& stat = entry.atk; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "ATK %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "ATK %d", stat[0]); + } + txt->setText(buf); + } + if ( auto txt = page_right->findField("rangeatk") ) + { + char buf[32] = "-"; + auto& stat = entry.rangeatk; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "ATK %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "ATK %d", stat[0]); + } + txt->setText(buf); + } + if ( auto txt = page_right->findField("pwr") ) + { + char buf[32] = "-"; + auto& stat = entry.pwr; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "PWR %d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "PWR %d", stat[0]); + } + txt->setText(buf); + } + + const char* res_neutral = "*images/ui/Main Menus/AdventureArchives/res_neutral.png"; + const char* res_hi1 = "*images/ui/Main Menus/AdventureArchives/res_hi1.png"; + const char* res_hi2 = "*images/ui/Main Menus/AdventureArchives/res_hi2.png"; + const char* res_lo1 = "*images/ui/Main Menus/AdventureArchives/res_lo1.png"; + const char* res_lo2 = "*images/ui/Main Menus/AdventureArchives/res_lo2.png"; + + std::vector, int>> res_strs = { + {{"res_txt_unarmed", "res_unarmed"}, DAMAGE_TABLE_UNARMED}, + {{"res_txt_sword", "res_sword"}, DAMAGE_TABLE_SWORD}, + {{"res_txt_axe", "res_axe"}, DAMAGE_TABLE_AXE}, + {{"res_txt_polearm", "res_polearm"}, DAMAGE_TABLE_POLEARM}, + {{"res_txt_mace", "res_mace"}, DAMAGE_TABLE_MACE}, + {{"res_txt_ranged", "res_ranged"}, DAMAGE_TABLE_RANGED}, + {{"res_txt_magic", "res_magic"}, DAMAGE_TABLE_MAGIC} + }; + + for ( auto& pair : res_strs ) + { + if ( auto field = page_right->findField(pair.first.first) ) + { + char buf[32] = ""; + if ( false ) + { + real_t val = (100.0 * Entity::getDamageTableMultiplier(nullptr, *myStats, (DamageTableType)pair.second)); + if ( val > 100.01 ) + { + field->setColor(hudColors.characterSheetGreen); + snprintf(buf, sizeof(buf), "%.f%%", val); + field->setText(buf); + } + else if ( val < 99.99 ) + { + field->setColor(hudColors.characterSheetRed); + snprintf(buf, sizeof(buf), "%.f%%", val); + field->setText(buf); + } + else + { + field->setColor(hudColors.characterSheetNeutral); + field->setText("100%"); + } + } + else + { + real_t val = (100.0 * Entity::getDamageTableMultiplier(nullptr, *myStats, (DamageTableType)pair.second)); + if ( val > 100.01 ) + { + field->setColor(hudColors.characterSheetGreen); + snprintf(buf, sizeof(buf), "%+.f%%", val - 100.0); + field->setText(buf); + } + else if ( val < 99.99 ) + { + field->setColor(hudColors.characterSheetRed); + snprintf(buf, sizeof(buf), "%+.f%%", val - 100.0); + field->setText(buf); + } + else + { + field->setColor(hudColors.characterSheetNeutral); + field->setText("-"); + } + } + } + if ( auto img = page_right->findImage(pair.first.second) ) + { + switch ( entry.resistances[pair.second] ) + { + case -1: + img->path = res_hi1; + break; + case -2: + img->path = res_hi2; + break; + case 1: + img->path = res_lo1; + break; + case 2: + img->path = res_lo2; + break; + default: + img->path = res_neutral; + break; + } + } + } + + if ( auto abilities = page_right->findField("abilities") ) + { + std::string txt = ""; + for ( auto& str : entry.abilities ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + if ( txt == "" ) + { + txt = "-"; + } + else + { + size_t index = 0; + for ( char& c : txt ) + { + if ( c == '-' ) + { + if ( index == 0 || txt[index - 1] == '\n' ) + { + c = '\x1E'; + } + } + ++index; + } + } + abilities->setText(txt.c_str()); + const int numAbilitiesLines = abilities->getNumTextLines(); + SDL_Rect abilityPos = abilities->getSize(); + + auto actualFont = Font::get(abilities->getFont()); + if ( actualFont ) + { + abilityPos.h = std::max(24, 24 + (numAbilitiesLines - 1) * actualFont->height(true)); + abilities->setSize(abilityPos); + } + + if ( auto inventory = page_right->findField("inventory txt") ) + { + SDL_Rect pos = inventory->getSize(); + pos.y = abilityPos.y + abilityPos.h + *compendiumMonsterSectionPadY - 24; + inventory->setSize(pos); + + if ( auto inv = page_right->findField("inventory") ) + { + std::string txt = ""; + for ( auto& str : entry.inventory ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + if ( txt == "" ) + { + txt = "-"; + } + else + { + size_t index = 0; + for ( char& c : txt ) + { + if ( c == '-' ) + { + if ( index == 0 || txt[index - 1] == '\n' ) + { + c = '\x1E'; + } + } + ++index; + } + } + inv->setText(txt.c_str()); + const int numInvLines = inv->getNumTextLines(); + SDL_Rect invPos = inv->getSize(); + invPos.y = pos.y + 26; + if ( actualFont ) + { + invPos.h = std::max(24, 24 + (numInvLines - 1) * actualFont->height(true)); + } + inv->setSize(invPos); + + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(412, invPos.y + invPos.h); + page_right->setActualSize(actualPos); + } + } + } + + } + } + } + } + } + + static std::vector compendiumCategories = { + "monsters", + "items", + "magic", + "world", + "codex" + }; + static std::string compendium_current = "monsters"; + static std::string compendium_contents_current = ""; + constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); + constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); + + static auto contents_activate_fn = [](Frame::entry_t& entry) + { + assert(main_menu_frame); + if ( !main_menu_frame ) { return; } + auto compendiumFrame = main_menu_frame->findFrame("compendium"); + if ( !compendiumFrame ) { return; } + + if ( auto contentsFrame = compendiumFrame->findFrame("contents") ) + { + for ( auto& e : contentsFrame->getEntries() ) + { + e->color = compendiumContentsDefaultColor; + } + } + + entry.color = compendiumContentsSelectedColor; + + if ( auto page_right = compendiumFrame->findFrame("page_right") ) + { + if ( auto slider = page_right->findSlider("right_slider") ) + { + slider->setValue(0); + slider->getCallback()(*slider); + } + } + + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents : nullptr)); + + std::string content = ""; + if ( entries ) + { + auto id = (intptr_t)entry.data; + if ( id >= entries->size() ) + { + return; + } + auto& entry = (*entries)[id]; + compendium_contents_current = entry.first; + if ( auto page_left_title = compendiumFrame->findField("page_left_title") ) + { + page_left_title->setText(entry.first.c_str()); + } + content = entry.second; + } + + if ( compendium_current == "codex" ) + { + refreshCompendiumEntryCodex(content, compendiumFrame); + } + else if ( compendium_current == "world" ) + { + refreshCompendiumEntryWorld(content, compendiumFrame); + } + else if ( compendium_current == "monsters" ) + { + if ( auto monster = createCompendiumMonster((Monster)CompendiumEntries.monsters[content].monsterType, 8, 8) ) + { + if ( auto myStats = monster->getStats() ) + { + myStats->setAttribute("monster_portrait", "true"); + } + refreshCompendiumEntryMonster(content, compendiumFrame); + } + } + }; + + static void compendiumPopulateContents(Frame* frame) + { + if ( auto contents = frame->findFrame("contents") ) + { + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents : nullptr)); + + auto toRemove = contents->getEntries(); + for ( auto r : toRemove ) + { + contents->removeEntry(r->name.c_str(), false); + } + + if ( entries ) + { + for ( int i = 0; i < entries->size(); ++i ) + { + auto& data = (*entries)[i]; + auto entry = contents->addEntry(data.second.c_str(), true); + entry->click = contents_activate_fn; + entry->ctrlClick = contents_activate_fn; + //entry->highlight = selection_fn; + //entry->selected = selection_fn; + entry->color = compendiumContentsDefaultColor; + entry->text = data.first; + entry->clickable = true; + memcpy(&entry->data, &i, sizeof(i)); + } + contents->setSelection(0); + contents->scrollToSelection(true); + contents->activateSelection(); + } + } + } + + static void compendiumPopulatePageRight(Frame* page_right) + { + if ( !page_right ) { return; } + + Frame* page_right_inner = page_right->findFrame("page_right_inner"); + if ( !page_right_inner ) + { + page_right_inner = page_right->addFrame("page_right_inner"); + page_right_inner->setSize(SDL_Rect{ 10, 28, 362, 412 }); + page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, 412 }); + } + + int padx = 10; + int pady = 0; + + constexpr Uint32 statValColor = makeColorRGB(159, 145, 127); + constexpr Uint32 detailsValColor = makeColorRGB(135, 94, 45); + + if ( compendium_current == "codex" ) + { + auto charTxt = page_right_inner->addField("tips txt", 64); + charTxt->setFont(menu_option_font); + charTxt->setText("USAGE TIPS"); + charTxt->setHJustify(Field::justify_t::LEFT); + charTxt->setVJustify(Field::justify_t::TOP); + charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); + charTxt->setColor(makeColor(198, 190, 179, 255)); + + int statx = padx + 4; + int staty = pady + 18 + 9; + auto statsTxt = page_right_inner->addField("details", 1024); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, page_right_inner->getSize().w - statx - 26, 400 }); + statsTxt->setColor(detailsValColor); + } + else if ( compendium_current == "world" ) + { + auto charTxt = page_right_inner->addField("tips txt", 1024); + charTxt->setFont(menu_option_font); + charTxt->setText("DETAILS"); + charTxt->setHJustify(Field::justify_t::LEFT); + charTxt->setVJustify(Field::justify_t::TOP); + charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); + charTxt->setColor(makeColor(198, 190, 179, 255)); + + int statx = padx + 4; + int staty = pady + 18 + 9; + auto statsTxt = page_right_inner->addField("details", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, page_right_inner->getSize().w - statx - 26, 400 }); + statsTxt->setColor(detailsValColor); + } + else if ( compendium_current == "monsters" ) + { + { + auto charTxt = page_right_inner->addField("characteristics txt", 64); + charTxt->setFont(menu_option_font); + charTxt->setText("CHARACTERISTICS"); + charTxt->setHJustify(Field::justify_t::LEFT); + charTxt->setVJustify(Field::justify_t::TOP); + charTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); + charTxt->setColor(makeColor(198, 190, 179, 255)); + + padx += 4; + pady += 23; + + Field* statsTxt = page_right_inner->addField("stats txt", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText("STATS"); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); + statsTxt->setColor(makeColor(135, 94, 45, 255)); + + int statx = padx + 30; + int staty = pady + 26; + statsTxt = page_right_inner->addField("hp", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 8, staty + 11 - 8, 16, 16 }, + 0xFFFFFFFF, "*images/ui/Inventory/potions/healing.png"); + + staty += 28; + statsTxt = page_right_inner->addField("ac", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_AC_00.png"); + + staty += 28; + statsTxt = page_right_inner->addField("spd", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_DEX_00.png"); + + statx = padx + 30 + 166; + staty = pady + 26; + statsTxt = page_right_inner->addField("atk", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_ATT_00.png"); + + staty += 28; + statsTxt = page_right_inner->addField("rangeatk", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Ranged01.png"); + + staty += 28; + statsTxt = page_right_inner->addField("pwr", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); + statsTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_SPWR_00.png"); + + pady = staty + *compendiumMonsterSectionPadY; + } + + + { + Field* resTxt = page_right_inner->addField("res txt", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText("WEAKNESSES"); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); + resTxt->setColor(makeColor(135, 94, 45, 255)); + + int resx = padx + 30; + int resy = pady + 26; + + const int padIconx = -6; + const int padIcony = -2; + const int padTextx = 0; + const int padTexty = 0; + const bool useIcons = false; + Frame::image_t* icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_unarmed"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_unarmed", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Unarmed01.png", "res_unarmed_img"); + + resy += 40; + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_ranged"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_ranged", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Ranged01.png", "res_ranged_img"); + + resy = pady + 26; + resx += 84; + + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_sword"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_sword", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Swords01.png", "res_sword_img"); + + resy += 40; + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_mace"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_mace", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Maces01.png", "res_mace_img"); + + resy = pady + 26; + resx += 82; + + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_polearm"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_polearm", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Polearms01.png", "res_polearm_img"); + + resy += 40; + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_axe"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_axe", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Axes01.png", "res_axe_img"); + + resy = pady + 26; + resx += 84; + + icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, + 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_magic"); + icon->disabled = !useIcons; + + resTxt = page_right_inner->addField("res_txt_magic", 64); + resTxt->setFont(smallfont_outline); + resTxt->setText(""); + resTxt->setHJustify(Field::justify_t::LEFT); + resTxt->setVJustify(Field::justify_t::TOP); + resTxt->setSize(SDL_Rect{ resx + padTextx, resy + padTexty, 132, 24 }); + resTxt->setColor(statValColor); + + page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, + 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Magic01.png", "res_magic_img"); + + resy += 40; + pady = resy + *compendiumMonsterSectionPadY; + } + + { + Field* abilitiesTxt = page_right_inner->addField("abilities txt", 64); + abilitiesTxt->setFont(smallfont_outline); + abilitiesTxt->setText("ABILITIES"); + abilitiesTxt->setHJustify(Field::justify_t::LEFT); + abilitiesTxt->setVJustify(Field::justify_t::TOP); + abilitiesTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); + abilitiesTxt->setColor(makeColor(135, 94, 45, 255)); + + int abilityx = padx + 8; + int abilityy = pady + 26; + + abilitiesTxt = page_right_inner->addField("abilities", 1024); + abilitiesTxt->setFont(smallfont_outline); + abilitiesTxt->setText(""); + abilitiesTxt->setHJustify(Field::justify_t::LEFT); + abilitiesTxt->setVJustify(Field::justify_t::TOP); + abilitiesTxt->setSize(SDL_Rect{ abilityx, abilityy, 300, 128 }); + abilitiesTxt->setColor(statValColor); + + pady = abilityy + *compendiumMonsterSectionPadY; + } + + { + Field* invTxt = page_right_inner->addField("inventory txt", 64); + invTxt->setFont(smallfont_outline); + invTxt->setText("INVENTORY"); + invTxt->setHJustify(Field::justify_t::LEFT); + invTxt->setVJustify(Field::justify_t::TOP); + invTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); + invTxt->setColor(makeColor(135, 94, 45, 255)); + + int invx = padx + 8; + int invy = pady + 26; + + invTxt = page_right_inner->addField("inventory", 1024); + invTxt->setFont(smallfont_outline); + invTxt->setText(""); + invTxt->setHJustify(Field::justify_t::LEFT); + invTxt->setVJustify(Field::justify_t::TOP); + invTxt->setSize(SDL_Rect{ invx, invy, 300, 128 }); + invTxt->setColor(statValColor); + } + } + } + static void openCompendium() { auto dimmer = main_menu_frame->addFrame("dimmer"); dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); @@ -32549,35 +33437,349 @@ namespace MainMenu { dimmer->setColor(makeColor(0, 0, 0, 63)); dimmer->setBorder(0); + static ConsoleVariable cvar_compendium_book_x("/compendium_book_x", 119); + auto window = dimmer->addFrame("compendium"); - window->setSize(SDL_Rect{ - (Frame::virtualScreenX - 1222) / 2, - (Frame::virtualScreenY - 598) / 2, - 1222, - 598 }); + window->setSize(dimmer->getSize()); window->setColor(0); window->setBorder(0); auto background = window->addImage( - SDL_Rect{ 0, 0, window->getSize().w, window->getSize().h }, + SDL_Rect{ + *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, + (Frame::virtualScreenY - 598) / 2, + 958, + 598 }, 0xffffffff, - "*images/ui/Main Menus/AdventureArchives/A_Window_BG_00.png", + "*images/ui/Main Menus/AdventureArchives/C_Window_BG_01.png", "background" ); - auto model_viewer = window->addFrame("model_viewer"); - model_viewer->setSize(SDL_Rect{284, 32, 406, 406}); + { + Button* tab = window->addButton(compendiumCategories[0].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 86, background->pos.y + background->pos.h - 30, 96, 66 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Denizens_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); + tab->setCallback([](Button& button) { + compendium_current = "monsters"; + + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + compendiumPopulatePageRight(page_right); + } + compendiumPopulateContents(frame); + } + }); + + tab = window->addButton(compendiumCategories[1].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 190, background->pos.y + background->pos.h - 30, 88, 76 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Items_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); + tab->setCallback([](Button& button) { + compendium_current = "items"; + }); + + tab = window->addButton(compendiumCategories[2].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 286, background->pos.y + background->pos.h - 30, 96, 64 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Magic_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicHi_00.png"); + tab->setCallback([](Button& button) { + compendium_current = "magic"; + }); + + tab = window->addButton(compendiumCategories[3].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 580, background->pos.y + background->pos.h - 30, 78, 80 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_World_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldHi_00.png"); + tab->setCallback([](Button& button) { + compendium_current = "world"; + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + compendiumPopulatePageRight(page_right); + } + compendiumPopulateContents(frame); + } + }); + + tab = window->addButton(compendiumCategories[4].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 672, background->pos.y + background->pos.h - 30, 84, 80 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Codex_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexHi_00.png"); + tab->setCallback([](Button& button) { + compendium_current = "codex"; + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + compendiumPopulatePageRight(page_right); + } + compendiumPopulateContents(frame); + } + }); + } + + auto navigation = window->addFrame("nav"); + navigation->setSize(SDL_Rect{ background->pos.x - 244 - 2, background->pos.y + 50, 244, 528 }); + auto nav_bg = navigation->addImage( + SDL_Rect{ 0, 0, 244, 528 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Contents_Panel_00.png", + "nav_bg" + ); + + auto nav_title = navigation->addField("nav_title", 128); + nav_title->setFont("fonts/kongtext.ttf#16#0"); + nav_title->setText("CONTENTS"); + nav_title->setHJustify(Field::justify_t::CENTER); + nav_title->setVJustify(Field::justify_t::TOP); + nav_title->setSize(SDL_Rect{ 0, 12, navigation->getSize().w, 28 }); + nav_title->setColor(makeColor(42, 22, 18, 255)); + + auto nav_contents_bg = navigation->addImage( + SDL_Rect{8, 32, 228, 464}, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Contents_Frame_00.png", + "nav_contents_bg" + ); + + auto contents = navigation->addFrame("contents"); + contents->setSize(SDL_Rect{ nav_contents_bg->pos.x + 10, nav_contents_bg->pos.y + 14, 184, 436 }); + contents->setActualSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getSize().h }); + contents->setEntrySize(18); + contents->setFont(smallfont_no_outline); + contents->setSelectedEntryColor(makeColor(71, 41, 27, 255)); + contents->setListOffset(SDL_Rect{ 32, 1, 0, 0 }); + contents->setScrollWithLeftControls(false); + contents->setClickable(true); + contents->setSelectorOffset(SDL_Rect{ -10, -14, 28, 28 }); + contents->setTickCallback([](Widget& widget) { + auto frame = static_cast(&widget); + if ( frame->isSelected() ) + { + frame->setAllowScrollBinds(true); + } + else + { + frame->setAllowScrollBinds(false); + } + }); + + auto page_left = window->addFrame("page_left"); + page_left->setSize(SDL_Rect{ background->pos.x + 460 - 22 - 382, background->pos.y + 46, 382, 472 }); + auto left_top_img = page_left->addImage(SDL_Rect{ 0, 0, 382, 330 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Subject_Frame_00.png", "page left top img"); + auto blurbImg = page_left->addImage(SDL_Rect{ left_top_img->pos.x, left_top_img->pos.y + left_top_img->pos.h + 12, 382, 122 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Lore_Frame_00.png", "page left bottom img"); + + auto page_left_title = window->addField("page_left_title", 128); + page_left_title->setFont("fonts/kongtext.ttf#16#0"); + page_left_title->setText(""); + page_left_title->setHJustify(Field::justify_t::CENTER); + page_left_title->setVJustify(Field::justify_t::TOP); + page_left_title->setSize(SDL_Rect{ page_left->getSize().x, page_left->getSize().y - 26, page_left->getSize().w, 28}); + page_left_title->setColor(makeColor(42, 22, 18, 255)); + + auto blurb = page_left->addField("blurb", 1024); + blurb->setFont(smallfont_no_outline); + blurb->setHJustify(Field::justify_t::CENTER); + blurb->setVJustify(Field::justify_t::CENTER); + blurb->setSize(SDL_Rect{ blurbImg->pos.x + 8, blurbImg->pos.y + 8, blurbImg->pos.w - 16, blurbImg->pos.h - 16}); + blurb->setColor(makeColor(42, 22, 18, 255)); + blurb->setText(""); + + auto model_viewer = page_left->addFrame("model_viewer"); + SDL_Rect modelPos = left_top_img->pos; + modelPos.x += 8; + modelPos.y += 8; + modelPos.w -= 16; + modelPos.h -= 16; + model_viewer->setSize(modelPos); model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { - drawMonsterPreview(pos, 50, 0.0); + if ( compendium_current == "monsters" ) + { + drawMonsterPreview(compendiumMonster, pos, 0.0); + } }); + model_viewer->setTickCallback([](Widget& widget) { + /*if ( ticks % TICKS_PER_SECOND == 0 ) + { + compendiumMonster->attack(compendiumMonster->getAttackPose(), 0, nullptr); + }*/ + if ( ticks % TICKS_PER_SECOND/2 == 0 ) + { + if ( compendium_current == "monsters" ) + { + CompendiumEntries.readMonstersFromFile(); + refreshCompendiumEntryMonster(Compendium_t::CompendiumMonsters_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + else if ( compendium_current == "world" ) + { + CompendiumEntries.readWorldFromFile(); + refreshCompendiumEntryWorld(Compendium_t::CompendiumWorld_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + else if ( compendium_current == "codex" ) + { + CompendiumEntries.readCodexFromFile(); + refreshCompendiumEntryCodex(Compendium_t::CompendiumCodex_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + } + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + if ( compendiumMonster ) + { + if ( auto myStats = compendiumMonster->getStats() ) + { + int type = myStats->type; + if ( keystatus[SDLK_LCTRL] ) + { + type -= 1; + if ( type >= NUMMONSTERS ) + { + type = NUMMONSTERS - 1; + } + while ( type == NOTHING || type == OCTOPUS || type == CRAB ) + { + type--; + } + if ( type < 0 ) + { + type = NUMMONSTERS - 1; + } + } + else if ( !keystatus[SDLK_LSHIFT] ) + { + type += 1; + if ( type >= NUMMONSTERS ) + { + type = HUMAN; + } + while ( type == NOTHING || type == OCTOPUS || type == CRAB ) + { + type++; + } + } + if ( auto monster = createCompendiumMonster((Monster)type, 8, 8) ) + { + if ( auto myStats = monster->getStats() ) + { + myStats->setAttribute("monster_portrait", "true"); + } + refreshCompendiumEntryMonster(main_menu_frame->findFrame("compendium")); + } + } + } + }*/ - if ( auto monster = summonMonsterNoSmoke(SKELETON, 8, 8, true) ) - { - if ( auto myStats = monster->getStats() ) + }); + + auto page_right = window->addFrame("page_right"); + page_right->setSize(SDL_Rect{ background->pos.x + 480 + 40, background->pos.y + 38, 386, 472}); + page_right->addImage(SDL_Rect{ 0, 0, 386, 472 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Details_Frame_00.png", "page right img"); + + auto page_right_inner = page_right->addFrame("page_right_inner"); + page_right_inner->setSize(SDL_Rect{ 10, 28, 362, 412 }); + page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, 412 }); + + auto page_right_title = window->addField("page_right_title", 128); + page_right_title->setFont("fonts/kongtext.ttf#16#0"); + page_right_title->setText("DETAILS"); + page_right_title->setHJustify(Field::justify_t::CENTER); + page_right_title->setVJustify(Field::justify_t::TOP); + page_right_title->setSize(SDL_Rect{ page_right->getSize().x, page_right->getSize().y - 18, page_right->getSize().w, 28 }); + page_right_title->setColor(makeColor(42, 22, 18, 255)); + + auto right_slider_bg = page_right->addImage(SDL_Rect{ + page_right_inner->getSize().x + page_right_inner->getSize().w - 18, + page_right_inner->getSize().y + 16, + 16, 412 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Details_ScrollBar_01.png", + "right_slider_bg"); + + auto right_slider = page_right->addSlider("right_slider"); + right_slider->setRailSize(SDL_Rect{ + page_right_inner->getSize().x + page_right_inner->getSize().w - 20, + page_right_inner->getSize().y + 16 + 4, 20, 404 }); + //right_slider->setRailImage("*images/ui/Main Menus/AdventureArchives/C_Details_ScrollBar_00.png"); + right_slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 }); + right_slider->setBorder(16); + right_slider->setOntop(true); + //right_slider->setHandleImage("*images/ui/Main Menus/AdventureArchives/C_Details_ScrollButton_00.png"); + right_slider->setHandleImage("*#images/ui/Sliders/HUD_Magic_Slider_Emerald_01.png"); + right_slider->setValue(0); + right_slider->setMinValue(0); + right_slider->setMaxValue(100); + right_slider->setOrientation(Slider::SLIDER_VERTICAL); + right_slider->setHideGlyphs(true); + right_slider->setHideKeyboardGlyphs(true); + right_slider->setHideSelectors(true); + right_slider->setMenuConfirmControlType(0); + right_slider->setCallback([](Slider& slider) { + if ( auto frame = static_cast(slider.getParent()) ) + { + if ( frame = frame->findFrame("page_right_inner") ) + { + if ( frame->getActualSize().h > frame->getSize().h ) + { + SDL_Rect pos = frame->getActualSize(); + const int diff = frame->getActualSize().h - frame->getSize().h; + pos.y = static_cast(diff * slider.getValue() / 100.0); + frame->setActualSize(pos); + } + else + { + slider.setValue(0); + } + } + } + }); + right_slider->setTickCallback([](Widget& widget) { + if ( auto frame = static_cast(widget.getParent()) ) { - myStats->setAttribute("monster_portrait", "true"); + if ( frame = frame->findFrame("page_right_inner") ) + { + if ( frame->getActualSize().h > frame->getSize().h ) + { + widget.setInvisible(false); + + const int diff = frame->getActualSize().h - frame->getSize().h; + auto slider = static_cast(&widget); + slider->setValue(100.0 * frame->getActualSize().y / diff); + } + else + { + widget.setInvisible(true); + auto slider = static_cast(&widget); + slider->setValue(0); + if ( widget.isSelected() ) + { + widget.deselect(); + } + } + } } - } + }); + + compendiumPopulatePageRight(page_right); + compendiumPopulateContents(window); (void)createBackWidget(window, [](Button& button) { soundCancel(); From d842509801a64c5730774f03b5bd1b60b5933616 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 8 Apr 2024 02:35:21 +1000 Subject: [PATCH 012/244] * fix list node nullptr crash --- src/list.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/list.cpp b/src/list.cpp index 569f4c352..6391844fe 100644 --- a/src/list.cpp +++ b/src/list.cpp @@ -196,6 +196,11 @@ void list_RemoveNode(node_t* node) node_t* list_AddNodeFirst(list_t* list) { + if ( !list ) + { + return nullptr; + } + node_t* node; // allocate memory for node @@ -242,6 +247,11 @@ node_t* list_AddNodeFirst(list_t* list) node_t* list_AddNodeLast(list_t* list) { + if ( !list ) + { + return nullptr; + } + node_t* node; // allocate memory for node From 1753792f55e5598e9c3cd962933f0d2551e1c8ab Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 21 Apr 2024 00:24:58 +1000 Subject: [PATCH 013/244] * item compendium stuff --- VS.2015/Barony/Barony.vcxproj | 2 +- src/init_game.cpp | 3 + src/interface/consolecommand.cpp | 8 + src/interface/playerinventory.cpp | 135 +++++++----- src/items.hpp | 2 +- src/mod_tools.cpp | 348 +++++++++++++++++++++++++++++ src/mod_tools.hpp | 99 +++++++++ src/player.hpp | 6 +- src/ui/GameUI.cpp | 272 ++++++++++++++++++++--- src/ui/GameUI.hpp | 9 +- src/ui/MainMenu.cpp | 353 +++++++++++++++++++++++++++++- 11 files changed, 1140 insertions(+), 97 deletions(-) diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index 73e4602fe..18dd60ac4 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -651,7 +651,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% MaxSpeed true true - _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions);USE_EOS;STEAMWORKS;USE_THEORA_VIDEO;USE_IMGUI;CURL_STATICLIB;USE_LIBCURL;USE_PLAYFAB + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions);USE_EOS;STEAMWORKS;USE_THEORA_VIDEO;USE_IMGUI;CURL_STATICLIB;USE_LIBCURL;USE_PLAYFAB; "$(SolutionDir)\..\VS-includes" true diff --git a/src/init_game.cpp b/src/init_game.cpp index 1a47bc9e5..26945e7c0 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -94,6 +94,9 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readMonstersFromFile(); CompendiumEntries.readWorldFromFile(); CompendiumEntries.readCodexFromFile(); + Compendium_t::Events_t::readEventsFromFile(); + CompendiumEntries.readItemsFromFile(); + Compendium_t::Events_t::loadItemsSaveData(); } void initGameDatafilesAsync(bool moddedReload) diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 8f214d4f5..b67cf761c 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4875,6 +4875,14 @@ namespace ConsoleCommands { CompendiumEntries.readCodexFromFile(); }); + static ConsoleCommand ccmd_reloadcompendiumitems("/reloadcompendiumitems", "reloads compendium entries", []CCMD{ + CompendiumEntries.readItemsFromFile(); + }); + + static ConsoleCommand ccmd_reloadcompendiumevents("/reloadcompendiumevents", "reloads compendium entries", []CCMD{ + Compendium_t::Events_t::readEventsFromFile(); + }); + static ConsoleCommand ccmd_mapdebugfixedmonsters("/mapdebugfixedmonsters", "prints fixed monster spawns", []CCMD{ #ifndef NINTENDO if ( !(svFlags & SV_FLAG_CHEATS) ) diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index ce77d8668..85523d1e4 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -4067,23 +4067,62 @@ int getContextMenuOptionOrder(const int player, ItemContextMenuPrompts prompt) return 5; } -void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int justify) +void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int justify, Frame* parentFrame) { - if ( player.inventoryUI.tooltipContainerFrame ) - { - player.inventoryUI.tooltipContainerFrame->setSize( - SDL_Rect{player.camera_virtualx1(), - player.camera_virtualy1(), - player.camera_virtualWidth(), - player.camera_virtualHeight()}); - } - const int player = this->player.playernum; if ( !item ) { return; } + Frame* tooltipContainerFrame = nullptr; + Frame* frameMain = nullptr; + Frame* frameInventory = nullptr; + Frame* frameInteract = nullptr; + Frame* frameTooltipPrompt = nullptr; + Frame* titleOnlyFrame = nullptr; + if ( parentFrame != nullptr ) + { + // hacks for compendium tooltips + char name[32]; + snprintf(name, sizeof(name), "player tooltip container %d", 0); + if ( tooltipContainerFrame = parentFrame->findFrame(name) ) + { + snprintf(name, sizeof(name), "player title only tooltip %d", 0); + titleOnlyFrame = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player tooltip %d", 0); + frameMain = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player interact %d", 0); + frameInteract = parentFrame->findFrame(name); + snprintf(name, sizeof(name), "player item prompt %d", 0); + frameTooltipPrompt = tooltipContainerFrame->findFrame(name); + } + } + else + { + tooltipContainerFrame = this->player.inventoryUI.tooltipContainerFrame; + frameMain = this->player.inventoryUI.tooltipFrame; + frameInventory = this->player.inventoryUI.frame; + frameInteract = this->player.inventoryUI.interactFrame; + frameTooltipPrompt = this->player.inventoryUI.tooltipPromptFrame; + titleOnlyFrame = this->player.inventoryUI.titleOnlyTooltipFrame; + if ( !frameInventory ) + { + return; + } + } + + + if ( tooltipContainerFrame ) + { + tooltipContainerFrame->setSize( + SDL_Rect{ this->player.camera_virtualx1(), + this->player.camera_virtualy1(), + this->player.camera_virtualWidth(), + this->player.camera_virtualHeight()}); + } + + players[player]->inventoryUI.miscTooltipOpacitySetpoint = 0; players[player]->inventoryUI.miscTooltipOpacityAnimate = 0.0; @@ -4092,32 +4131,21 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int bool bUpdateDisplayedTooltip = (!tooltipDisplayedSettings.isItemSameAsCurrent(player, item) || ItemTooltips.itemDebug); - auto frameMain = this->player.inventoryUI.tooltipFrame; if ( !frameMain ) { return; } - auto frameInventory = this->player.inventoryUI.frame; - if ( !frameInventory ) - { - return; - } - - auto frameInteract = this->player.inventoryUI.interactFrame; if ( !frameInteract ) { return; } - auto frameTooltipPrompt = this->player.inventoryUI.tooltipPromptFrame; if ( !frameTooltipPrompt ) { return; } - auto titleOnlyFrame = this->player.inventoryUI.titleOnlyTooltipFrame; - static const char* bigfont = "fonts/pixelmix.ttf#18"; auto frameAttr = frameMain->findFrame("inventory mouse tooltip attributes frame"); @@ -6382,41 +6410,50 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int gradientTop->pos.w = imgMiddleBackground->pos.w + 20; } - finalizeFrameTooltip(item, x, y, justify); + finalizeFrameTooltip(item, x, y, justify, parentFrame); } -void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, int justify) +void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, int justify, Frame* parentFrame) { const int player = this->player.playernum; const bool doTitleOnlyTooltip = players[player]->shootmode && !(enableDebugKeys && keystatus[SDLK_g]); auto& tooltipDisplayedSettings = this->player.inventoryUI.itemTooltipDisplay; - auto frameMain = this->player.inventoryUI.tooltipFrame; - if ( !frameMain ) - { - return; - } - - auto frameInventory = this->player.inventoryUI.frame; - if ( !frameInventory ) - { - return; - } - - auto frameInteract = this->player.inventoryUI.interactFrame; - if ( !frameInteract ) - { - return; - } - - auto frameTooltipPrompt = this->player.inventoryUI.tooltipPromptFrame; - if ( !frameTooltipPrompt ) - { - return; - } - - auto titleOnlyFrame = this->player.inventoryUI.titleOnlyTooltipFrame; - + Frame* tooltipContainerFrame = nullptr; + Frame* frameMain = nullptr; + Frame* frameInventory = nullptr; + Frame* frameInteract = nullptr; + Frame* frameTooltipPrompt = nullptr; + Frame* titleOnlyFrame = nullptr; + if ( parentFrame != nullptr ) + { + // hacks for compendium tooltips + char name[32]; + snprintf(name, sizeof(name), "player tooltip container %d", 0); + tooltipContainerFrame = parentFrame->findFrame(name); + snprintf(name, sizeof(name), "player title only tooltip %d", 0); + titleOnlyFrame = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player tooltip %d", 0); + frameMain = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player interact %d", 0); + frameInteract = parentFrame->findFrame(name); + snprintf(name, sizeof(name), "player item prompt %d", 0); + frameTooltipPrompt = tooltipContainerFrame->findFrame(name); + } + else + { + tooltipContainerFrame = this->player.inventoryUI.tooltipContainerFrame; + frameMain = this->player.inventoryUI.tooltipFrame; + frameInventory = this->player.inventoryUI.frame; + frameInteract = this->player.inventoryUI.interactFrame; + frameTooltipPrompt = this->player.inventoryUI.tooltipPromptFrame; + titleOnlyFrame = this->player.inventoryUI.titleOnlyTooltipFrame; + if ( !frameInventory ) + { + return; + } + } + if ( !doTitleOnlyTooltip ) { tooltipDisplayedSettings.opacitySetpoint = 0; diff --git a/src/items.hpp b/src/items.hpp index 54cbdd071..006512cb3 100644 --- a/src/items.hpp +++ b/src/items.hpp @@ -460,7 +460,7 @@ class Item // weight, category and other generic info reported by function calls - node_t* node; + node_t* node = nullptr; /* * Gems use this to store information about what sort of creature they contain. diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 90af0d185..af199cc5d 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10633,6 +10633,105 @@ std::vector> Compendium_t::CompendiumWorld_t std::map Compendium_t::CompendiumWorld_t::contentsMap; std::vector> Compendium_t::CompendiumCodex_t::contents; std::map Compendium_t::CompendiumCodex_t::contentsMap; +std::vector> Compendium_t::CompendiumItems_t::contents; +std::map Compendium_t::CompendiumItems_t::contentsMap; + +void Compendium_t::readItemsFromFile() +{ + const std::string filename = "data/compendium/items.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("items") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + items.clear(); + CompendiumItems_t::contents.clear(); + CompendiumItems_t::contentsMap.clear(); + Compendium_t::Events_t::itemEventLookup.clear(); + Compendium_t::Events_t::eventItemLookup.clear(); + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumItems_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumItems_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + } + } + + auto& entries = d["items"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + auto& w = itr->value; + auto& obj = items[name]; + + jsonVecToVec(w["blurb"], obj.blurb); + for ( auto itr = w["items"].Begin(); itr != w["items"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumItems_t::Codex_t::CodexItem_t item; + item.name = itr2->name.GetString(); + item.rotation = 0; + if ( itr2->value.HasMember("rotation") ) + { + item.rotation = itr2->value["rotation"].GetInt(); + } + obj.items_in_category.push_back(item); + } + } + if ( w.HasMember("events") ) + { + for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); + } + } + } + } + } + } + } +} void Compendium_t::readCodexFromFile() { @@ -10837,4 +10936,253 @@ void Compendium_t::readMonstersFromFile() } jsonVecToVec(m["inventory"], monster.inventory); } +} + +std::map Compendium_t::Events_t::events; +std::map Compendium_t::Events_t::eventIdLookup; +std::map> Compendium_t::Events_t::itemEventLookup; +std::map> Compendium_t::Events_t::eventItemLookup; +std::map> Compendium_t::Events_t::playerEvents[MAXPLAYERS]; + +void Compendium_t::Events_t::readEventsFromFile() +{ + const std::string filename = "data/compendium/events.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("tags") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + events.clear(); + eventIdLookup.clear(); + int index = -1; + for ( auto itr = d["tags"].Begin(); itr != d["tags"].End(); ++itr ) + { + ++index; + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + const EventTags id = (EventTags)std::min(index, (int)CPDM_EVENT_TAGS_MAX); + auto& entry = events[id]; + entry.id = id; + entry.name = itr2->name.GetString(); + eventIdLookup[entry.name] = id; + + + if ( itr2->value.HasMember("type") ) + { + std::string type = itr2->value["type"].GetString(); + if ( type == "sum" ) + { + entry.type = SUM; + } + else if ( type == "max" ) + { + entry.type = MAX; + } + } + entry.id = index; + if ( itr2->value.HasMember("client") ) + { + entry.client = itr2->value["client"].GetBool(); + } + } + } +} + +void Compendium_t::Events_t::loadItemsSaveData() +{ + const std::string filename = "savegames/compendium_items.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Warning: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("items") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + playerEvents[i].clear(); + } + for ( auto itr = d["items"].MemberBegin(); itr != d["items"].MemberEnd(); ++itr ) + { + auto find = eventIdLookup.find(itr->name.GetString()); + if ( find == eventIdLookup.end() ) + { + continue; + } + const EventTags id = (EventTags)std::min((int)find->second, (int)CPDM_EVENT_TAGS_MAX); + for ( auto itr2 = itr->value.MemberBegin(); itr2 != itr->value.MemberEnd(); ++itr2 ) + { + int itemType = std::stoi(itr2->name.GetString()); + if ( itemType < 0 || itemType >= NUMITEMS ) + { + continue; + } + Sint32 value = itr2->value.GetInt(); + eventUpdate(0, id, (ItemType)itemType, value, true); + } + } +} + +void Compendium_t::Events_t::writeItemsSaveData() +{ + char path[PATH_MAX] = ""; + completePath(path, "savegames/compendium_items.json", outputdir); + + rapidjson::Document exportDocument; + exportDocument.SetObject(); + + const int VERSION = 1; + + CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(VERSION)); + rapidjson::Value itemsObj(rapidjson::kObjectType); + for ( auto& pair : playerEvents[0] ) + { + const std::string& key = events[pair.first].name; + rapidjson::Value namekey(key.c_str(), exportDocument.GetAllocator()); + itemsObj.AddMember(namekey, rapidjson::Value(rapidjson::kObjectType), exportDocument.GetAllocator()); + auto& obj = itemsObj[key.c_str()]; + for ( auto& itemsData : pair.second ) + { + rapidjson::Value itemKey(std::to_string(itemsData.first).c_str(), exportDocument.GetAllocator()); + obj.AddMember(itemKey, itemsData.second.value, exportDocument.GetAllocator()); + } + } + CustomHelpers::addMemberToRoot(exportDocument, "items", itemsObj); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + exportDocument.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + + printlog("[JSON]: Successfully wrote json file %s", path); + return; +} + +void Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) +{ + if ( type == SUM ) + { + value += val; + if ( (Uint32)value >= 0x7FFFFFFF ) + { + value = 0x7FFFFFFF; + } + } + else if ( type == MAX ) + { + value = std::max(val, value); + } +} + +void Compendium_t::Events_t::eventUpdate(const int playernum, const EventTags tag, const ItemType type, + const Sint32 value, const bool forceValue) +{ + if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } + + if ( multiplayer == SINGLE && playernum != 0 ) { return; } + + auto find = events.find(tag); + if ( find == events.end() ) + { + return; + } + auto& def = find->second; + + if ( def.client ) + { + if ( multiplayer != SINGLE ) + { + if ( playernum != clientnum ) + { + return; + } + } + } + else + { + if ( multiplayer == CLIENT ) + { + return; + } + } + + auto find2 = eventItemLookup[tag].find(type); + if ( find2 == eventItemLookup[tag].end() ) + { + return; + } + + auto& e = playerEvents[(multiplayer == CLIENT) ? 0 : playernum][tag]; + if ( e.find(type) == e.end() ) + { + e[type] = EventVal_t(tag); + } + + auto& val = e[type]; + if ( forceValue ) + { + val.value = value; + } + else + { + val.applyValue(value); + writeItemsSaveData(); + } } \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index e62e77739..ad9b8a646 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3503,6 +3503,105 @@ struct Compendium_t }; std::map codex; void readCodexFromFile(); + + struct CompendiumItems_t + { + struct Codex_t + { + struct CodexItem_t + { + std::string name = ""; + int rotation = 0; + }; + int modelIndex = -1; + std::string imagePath = ""; + std::vector blurb; + std::vector items_in_category; + }; + static std::vector> contents; + static std::map contentsMap; + }; + std::map items; + void readItemsFromFile(); + + enum EventTags + { + CPDM_HOLD_TIME = 0, + CPDM_SCRAPPED, + CPDM_SHIELD_REFLECT, + CPDM_BLOCKED_ATTACKS, + CPDM_BLESSED_TOTAL, + CPDM_INSPIRATION_XP, + CPDM_BLINDFOLDED_TIME, + CPDM_MASKED_ROBBERIES, + CPDM_CARRY_WGT_MAX, + CPDM_BROKEN, + CPDM_VICTORIES_WITH, + CPDM_BROKEN_BY_BLOCKING, + CPDM_PWR_MAX, + CPDM_ROSES_OFFERED, + CPDM_GOLD_MAX_NULLIFIED, + CPDM_BLESSED_MAX_DOUBLET, + CPDM_PREFERRED_CLASS, + CPDM_BLESSED_MAX, + CPDM_TORCH_WALLS, + CPDM_SHIELD_REFLECT_KILLS, + CPDM_BLESSED_MAX_CLOAK, + CPDM_INVENTORY_SLOTS_AVG, + CPDM_COLLECTED_RUN, + CPDM_TORCH_BURNT_OUT, + CPDM_STEALTH_ATTACKS_WITH, + CPDM_MIRROR_TELEPORTS, + CPDM_BLOCKED_HIGHEST_DMG, + CPDM_BOULDER_DMG_BLOCKED, + CPDM_HUNGER_PACIFIED, + CPDM_DISORIENT_EFFECT_APPLIED, + CPDM_CLOAK_BURNED, + CPDM_INVENTORY_QTY_MAX, + CPDM_DEATHS_WEARING, + CPDM_EVENT_TAGS_MAX + }; + + struct Events_t + { + enum Type + { + SUM, + MAX, + AVERAGE_RANGE + }; + struct Event_t + { + Type type = SUM; + bool client = true; + std::string name = ""; + int id = CPDM_EVENT_TAGS_MAX; + }; + struct EventVal_t + { + Type type = SUM; + int id = CPDM_EVENT_TAGS_MAX; + Sint32 value = 0; + void applyValue(const Sint32 val); + EventVal_t() = default; + EventVal_t(EventTags tag) + { + auto& def = events[tag]; + type = def.type; + id = def.id; + value = 0; + } + }; + static std::map events; + static std::map eventIdLookup; + static std::map> itemEventLookup; + static std::map> eventItemLookup; + static void readEventsFromFile(); + static void writeItemsSaveData(); + static void loadItemsSaveData(); + static void eventUpdate(const int playernum, const EventTags tag, const ItemType type, const Sint32 val, const bool forceValue = false); + static std::map> playerEvents[MAXPLAYERS]; + }; }; extern Compendium_t CompendiumEntries; \ No newline at end of file diff --git a/src/player.hpp b/src/player.hpp index 781cba6c1..087d7dcfa 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -1042,7 +1042,7 @@ class Player void updateInventory(); void updateCursor(); void updateItemContextMenuClickFrame(); - void updateInventoryItemTooltip(); + void updateInventoryItemTooltip(Frame* parentFrame = nullptr); void updateSelectedItemAnimation(); void updateItemContextMenu(); void cycleInventoryTab(); @@ -1655,8 +1655,8 @@ class Player void updateEnemyBar(Frame* whichFrame); void updateEnemyBar2(Frame* whichFrame, void* enemyHPDetails); void resetBars(); - void updateFrameTooltip(Item* item, const int x, const int y, int justify); - void finalizeFrameTooltip(Item* item, const int x, const int y, int justify); + void updateFrameTooltip(Item* item, const int x, const int y, int justify, Frame* parentFrame = nullptr); + void finalizeFrameTooltip(Item* item, const int x, const int y, int justify, Frame* parentFrame = nullptr); void updateStatusEffectTooltip(); void updateCursor(); void updateActionPrompts(); diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index c8c2d41db..fa5a057a0 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -20784,7 +20784,13 @@ void updateSlotFrameFromItem(Frame* slotFrame, void* itemPtr, bool forceUnusable } } -void createInventoryTooltipFrame(const int player) +void createInventoryTooltipFrame(const int player, + Frame* parentFrame, + Frame*& tooltipContainerFrame, + Frame*& titleOnlyTooltipFrame, + Frame*& tooltipFrame, + Frame*& interactFrame, + Frame*& promptFrame) { if ( !gui ) { @@ -20796,26 +20802,34 @@ void createInventoryTooltipFrame(const int player) const std::string headerFont = "fonts/pixel_maz_multiline.ttf#16#2"; const std::string bodyFont = "fonts/pixel_maz_multiline.ttf#16#2"; - if ( !players[player]->inventoryUI.tooltipContainerFrame ) + if ( !tooltipContainerFrame ) { char name[32]; snprintf(name, sizeof(name), "player tooltip container %d", player); - players[player]->inventoryUI.tooltipContainerFrame = gameUIFrame[player]->addFrame(name); - players[player]->inventoryUI.tooltipContainerFrame->setSize( + if ( parentFrame ) + { + tooltipContainerFrame = parentFrame->addFrame(name); + } + else + { + tooltipContainerFrame = gameUIFrame[player]->addFrame(name); + } + tooltipContainerFrame->setSize( SDL_Rect{ players[player]->camera_virtualx1(), players[player]->camera_virtualy1(), players[player]->camera_virtualWidth(), players[player]->camera_virtualHeight() }); - players[player]->inventoryUI.tooltipContainerFrame->setHollow(true); - players[player]->inventoryUI.tooltipContainerFrame->setDisabled(false); - players[player]->inventoryUI.tooltipContainerFrame->setInheritParentFrameOpacity(false); + tooltipContainerFrame->setHollow(true); + tooltipContainerFrame->setDisabled(false); + tooltipContainerFrame->setInheritParentFrameOpacity(false); } - if ( !players[player]->inventoryUI.titleOnlyTooltipFrame ) + + if ( !titleOnlyTooltipFrame ) { char name[32]; snprintf(name, sizeof(name), "player title only tooltip %d", player); - players[player]->inventoryUI.titleOnlyTooltipFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name); - auto tooltipFrame = players[player]->inventoryUI.titleOnlyTooltipFrame; + titleOnlyTooltipFrame = tooltipContainerFrame->addFrame(name); + auto tooltipFrame = titleOnlyTooltipFrame; tooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 }); tooltipFrame->setHollow(true); tooltipFrame->setDisabled(true); @@ -20838,12 +20852,11 @@ void createInventoryTooltipFrame(const int player) tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 28 }, color, "*#images/ui/Inventory/tooltips/Hover_TR00_TitleOnly.png", "tooltip top right"); } - if ( !players[player]->inventoryUI.tooltipFrame ) + if ( !tooltipFrame ) { char name[32]; snprintf(name, sizeof(name), "player tooltip %d", player); - players[player]->inventoryUI.tooltipFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name); - auto tooltipFrame = players[player]->inventoryUI.tooltipFrame; + tooltipFrame = tooltipContainerFrame->addFrame(name); tooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 }); tooltipFrame->setHollow(true); tooltipFrame->setDisabled(true); @@ -20854,8 +20867,6 @@ void createInventoryTooltipFrame(const int player) return; } - auto tooltipFrame = players[player]->inventoryUI.tooltipFrame; - Uint32 color = makeColor( 255, 255, 255, 255); tooltipFrame->addImage(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, 28 }, color, "*#images/ui/Inventory/tooltips/Hover_T00.png", "tooltip top background"); @@ -21154,9 +21165,8 @@ void createInventoryTooltipFrame(const int player) char name[32]; snprintf(name, sizeof(name), "player interact %d", player); - if ( auto interactFrame = gameUIFrame[player]->addFrame(name) ) + if ( interactFrame = (parentFrame ? parentFrame->addFrame(name) : gameUIFrame[player]->addFrame(name)) ) { - players[player]->inventoryUI.interactFrame = interactFrame; const int interactWidth = 106; interactFrame->setSize(SDL_Rect{ 0, 0, interactWidth + 6 * 2, 100 }); interactFrame->setDisabled(true); @@ -21308,9 +21318,8 @@ void createInventoryTooltipFrame(const int player) } snprintf(name, sizeof(name), "player item prompt %d", player); - if ( auto promptFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name) ) + if ( promptFrame = tooltipContainerFrame->addFrame(name) ) { - players[player]->inventoryUI.tooltipPromptFrame = promptFrame; const int interactWidth = 0; SDL_Rect promptSize{ 0, 0, interactWidth + 6 * 2, 100 }; promptFrame->setDisabled(true); @@ -21441,7 +21450,161 @@ void createInventoryTooltipFrame(const int player) } view_t playerPortraitView[MAXPLAYERS]; -view_t monsterPortaitView; +view_t monsterPortraitView; +view_t itemPortraitView; +static ConsoleVariable cvar_compendium_portrait_static_angle("/compendium_portrait_static_angle", true); +void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) +{ + if ( !item ) { return; } + if ( item->sprite < 0 ) { return; } + + static int fov = 50; + static real_t ang = 0.0; + static real_t vang = 0.0; + static real_t zoom = 0.0; + static real_t z = 0.0; + static int idleAngRotationDir = 0; + static real_t idleAngRotation = 0.0; + if ( keystatus[SDLK_KP_1] ) + { + vang -= 0.01; + } + if ( keystatus[SDLK_KP_3] ) + { + vang += 0.01; + } + if ( keystatus[SDLK_KP_4] ) + { + ang -= 0.01; + } + if ( keystatus[SDLK_KP_6] ) + { + ang += 0.01; + } + if ( keystatus[SDLK_KP_8] ) + { + z += 0.5; + } + if ( keystatus[SDLK_KP_2] ) + { + z -= 0.5; + } + if ( keystatus[SDLK_KP_7] ) + { + zoom -= 0.01; + } + if ( keystatus[SDLK_KP_9] ) + { + zoom += 0.01; + } + if ( keystatus[SDLK_KP_0] ) + { + keystatus[SDLK_KP_0] = 0; + item->roll += PI / 4; + } + if ( keystatus[SDLK_1] ) + { + keystatus[SDLK_1] = 0; + if ( keystatus[SDLK_LSHIFT] ) + { + item->focalx -= 0.125; + } + else if ( keystatus[SDLK_LCTRL] ) + { + item->focalx = 0.0; + } + else + { + item->focalx += 0.125; + } + } + if ( keystatus[SDLK_2] ) + { + keystatus[SDLK_2] = 0; + if ( keystatus[SDLK_LSHIFT] ) + { + item->focaly -= 0.125; + } + else if ( keystatus[SDLK_LCTRL] ) + { + item->focaly = 0.0; + } + else + { + item->focaly += 0.125; + } + } + if ( keystatus[SDLK_3] ) + { + keystatus[SDLK_3] = 0; + if ( keystatus[SDLK_LSHIFT] ) + { + item->focalz -= 0.125; + } + else if ( keystatus[SDLK_LCTRL] ) + { + item->focalz = 0.0; + } + else + { + item->focalz += 0.125; + } + } + + if ( idleAngRotationDir == 0 ) + { + idleAngRotation += 0.005; + /*if ( idleAngRotation >= PI / 8 ) + { + idleAngRotationDir = 1; + }*/ + } + else + { + idleAngRotation -= 0.01; + if ( idleAngRotation <= -PI / 8 ) + { + idleAngRotationDir = 0; + } + } + + view_t& view = monsterPortraitView; + auto ofov = ::fov; + ::fov = fov; + + view.x = item->x / 16.0 + ((.92 + zoom) * cos(offsetyaw + + ang + idleAngRotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); + view.y = item->y / 16.0 + ((.92 + zoom) * sin(offsetyaw + + ang + idleAngRotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); + view.z = item->z * 2 + z; + view.ang = (offsetyaw - PI + + (*cvar_compendium_portrait_static_angle ? item->yaw : 0) + ang + idleAngRotation); //5 * PI / 4; + view.vang = PI / 20 + vang; + + view.winx = pos.x; + // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 + view.winy = pos.y + (yres - Frame::virtualScreenY); + + view.winw = pos.w; + view.winh = pos.h; + glBeginCamera(&view, false); + bool b = item->flags[BRIGHT]; + if ( !dark ) { item->flags[BRIGHT] = true; } + if ( !item->flags[INVISIBLE] ) + { + glDrawVoxel(&view, item, REALCOLORS); + } + + item->flags[BRIGHT] = b; + + if ( drawingGui ) { + // blending gets disabled after objects are drawn, so re-enable it. + GL_CHECK_ERR(glEnable(GL_BLEND)); + } + glEndCamera(&view, false); + ::fov = ofov; +} + void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark) { if ( !monster ) { return; } @@ -21485,18 +21648,17 @@ void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool da } - view_t& view = monsterPortaitView; + view_t& view = monsterPortraitView; auto ofov = ::fov; ::fov = fov; - static ConsoleVariable cvar_char_portrait_static_angle("/compendium_portrait_static_angle", true); view.x = monster->x / 16.0 + ((.92 + zoom) * cos(offsetyaw - + ang + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + + ang + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0))); view.y = monster->y / 16.0 + ((.92 + zoom) * sin(offsetyaw - + ang + (*cvar_char_portrait_static_angle ? monster->yaw : 0))); + + ang + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0))); view.z = monster->z * 2 + z; view.ang = (offsetyaw - PI - + (*cvar_char_portrait_static_angle ? monster->yaw : 0) + ang); //5 * PI / 4; + + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0) + ang); //5 * PI / 4; view.vang = PI / 20 + vang; view.winx = pos.x; @@ -25890,16 +26052,48 @@ void Player::Inventory_t::updateSelectedItemAnimation() } } -void Player::Inventory_t::updateInventoryItemTooltip() +void Player::Inventory_t::updateInventoryItemTooltip(Frame* parentFrame) { - if ( !tooltipFrame || !frame || !titleOnlyTooltipFrame ) + Frame* tooltipContainerFrame = nullptr; + Frame* frameMain = nullptr; + Frame* frameInventory = nullptr; + Frame* titleOnlyFrame = nullptr; + Frame* frameTooltipPrompt = nullptr; + if ( parentFrame ) + { + // hacks for compendium tooltips + char name[32]; + snprintf(name, sizeof(name), "player tooltip container %d", 0); + if ( Frame* tooltipContainerFrame = parentFrame->findFrame(name) ) + { + snprintf(name, sizeof(name), "player title only tooltip %d", 0); + titleOnlyFrame = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player tooltip %d", 0); + frameMain = tooltipContainerFrame->findFrame(name); + snprintf(name, sizeof(name), "player item prompt %d", 0); + frameTooltipPrompt = tooltipContainerFrame->findFrame(name); + } + } + else + { + frameMain = this->player.inventoryUI.tooltipFrame; + frameInventory = this->player.inventoryUI.frame; + frameTooltipPrompt = this->player.inventoryUI.tooltipPromptFrame; + titleOnlyFrame = this->player.inventoryUI.titleOnlyTooltipFrame; + if ( !frameInventory ) + { + return; + } + } + + if ( !frameMain || !titleOnlyFrame ) { return; } auto& tooltipDisplay = this->itemTooltipDisplay; - if ( static_cast(tooltipFrame->getOpacity()) != tooltipDisplay.opacitySetpoint ) + if ( static_cast(frameMain->getOpacity()) != tooltipDisplay.opacitySetpoint ) { const real_t fpsScale = getFPSScale(144.0); if ( tooltipDisplay.opacitySetpoint == 0 ) @@ -25914,14 +26108,14 @@ void Player::Inventory_t::updateInventoryItemTooltip() tooltipDisplay.opacityAnimate += setpointDiff; tooltipDisplay.opacityAnimate = std::min(1.0, tooltipDisplay.opacityAnimate); } - tooltipFrame->setOpacity(tooltipDisplay.opacityAnimate * 100); + frameMain->setOpacity(tooltipDisplay.opacityAnimate * 100); } else { - tooltipFrame->setOpacity(tooltipDisplay.opacitySetpoint); + frameMain->setOpacity(tooltipDisplay.opacitySetpoint); } - if ( static_cast(titleOnlyTooltipFrame->getOpacity()) != tooltipDisplay.titleOnlyOpacitySetpoint ) + if ( static_cast(titleOnlyFrame->getOpacity()) != tooltipDisplay.titleOnlyOpacitySetpoint ) { const real_t fpsScale = getFPSScale(144.0); if ( tooltipDisplay.titleOnlyOpacitySetpoint == 0 ) @@ -25936,16 +26130,16 @@ void Player::Inventory_t::updateInventoryItemTooltip() tooltipDisplay.titleOnlyOpacityAnimate += setpointDiff; tooltipDisplay.titleOnlyOpacityAnimate = std::min(1.0, tooltipDisplay.titleOnlyOpacityAnimate); } - titleOnlyTooltipFrame->setOpacity(tooltipDisplay.titleOnlyOpacityAnimate * 100); + titleOnlyFrame->setOpacity(tooltipDisplay.titleOnlyOpacityAnimate * 100); } else { - titleOnlyTooltipFrame->setOpacity(tooltipDisplay.titleOnlyOpacitySetpoint); + titleOnlyFrame->setOpacity(tooltipDisplay.titleOnlyOpacitySetpoint); } - if ( tooltipPromptFrame ) + if ( frameTooltipPrompt ) { - tooltipPromptFrame->setOpacity(tooltipFrame->getOpacity()); + frameTooltipPrompt->setOpacity(frameMain->getOpacity()); } tooltipDisplay.expandSetpoint = tooltipDisplay.expanded ? 100 : 0; @@ -26715,7 +26909,13 @@ void Player::Inventory_t::processInventory() } if ( !tooltipFrame ) { - createInventoryTooltipFrame(player.playernum); + createInventoryTooltipFrame(player.playernum, + nullptr, + tooltipContainerFrame, + titleOnlyTooltipFrame, + tooltipFrame, + interactFrame, + tooltipPromptFrame); } frame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(), diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index f3d6f7243..c33fdc3c9 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -13,12 +13,19 @@ void doSharedMinimap(); extern Frame* gameUIFrame[MAXPLAYERS]; void addMessageToLogWindow(int player, string_t* string); void updateSlotFrameFromItem(Frame* slotFrame, void* itemPtr, bool forceUnusable = false); -void createInventoryTooltipFrame(const int player); +void createInventoryTooltipFrame(const int player, + Frame* parentFrame, + Frame*& tooltipContainerFrame, + Frame*& titleOnlyTooltipFrame, + Frame*& tooltipFrame, + Frame*& interactFrame, + Frame*& promptFrame); bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spells); void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark = false); +void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); void loadHUDSettingsJSON(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 9c863cc67..76ba660bc 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32638,6 +32638,302 @@ namespace MainMenu { } } + constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); + constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); + + static auto compendium_items_activate_fn = [](Frame::entry_t& entry) + { + assert(main_menu_frame); + if ( !main_menu_frame ) { return; } + auto compendiumFrame = main_menu_frame->findFrame("compendium"); + if ( !compendiumFrame ) { return; } + + if ( auto page_right = compendiumFrame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + for ( auto& e : page_right_inner->getEntries() ) + { + e->color = compendiumContentsDefaultColor; + } + } + } + + entry.color = compendiumContentsSelectedColor; + //if ( auto page_right = compendiumFrame->findFrame("page_right") ) + //{ + // if ( auto slider = page_right->findSlider("right_slider") ) + // { + // slider->setValue(0); + // slider->getCallback()(*slider); + // } + //} + }; + + struct CompendiumTooltipFrames_t + { + Frame* tooltipContainerFrame = nullptr; + Frame* titleOnlyTooltipFrame = nullptr; + Frame* tooltipFrame = nullptr; + Frame* interactFrame = nullptr; + Frame* tooltipPromptFrame = nullptr; + void clear() + { + tooltipContainerFrame = nullptr; + titleOnlyTooltipFrame = nullptr; + tooltipFrame = nullptr; + interactFrame = nullptr; + tooltipPromptFrame = nullptr; + } + }; + static CompendiumTooltipFrames_t compendiumItemTooltip; + + static void refreshCompendiumEntryItemsBlurb(std::string name, Frame* parent) + { + if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + { + return; + } + auto& entry = CompendiumEntries.items[name]; + + if ( Frame* page_left = parent->findFrame("page_left") ) + { + if ( auto blurb = page_left->findField("blurb") ) + { + std::string txt = ""; + for ( auto& str : entry.blurb ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + blurb->setText(txt.c_str()); + } + } + } + + static Entity compendiumItemModel(-1, 0, nullptr, nullptr); + static Item compendiumItem; + static std::string compendium_contents_current = ""; + static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner) + { + compendiumItemModel.sprite = -1; + compendiumItemModel.focalz = 0.5; + compendiumItemModel.roll = 0.0; + for ( auto f : page_right_inner.getFrames() ) + { + if ( auto txt = f->findField("item name") ) + { + if ( f == &toSelect ) + { + txt->setColor(compendiumContentsSelectedColor); + const int itemType = ItemTooltips.itemNameStringToItemID[toSelect.getName()]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + int appearance = 0; + if ( toSelect.getUserData() ) + { + appearance = (reinterpret_cast(toSelect.getUserData()) & 0x7F); + } + for ( auto& i : CompendiumEntries.items[Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current]].items_in_category ) + { + if ( i.name == toSelect.getName() ) + { + compendiumItemModel.roll = (i.rotation * PI / 180.0); + } + } + compendiumItemModel.sprite = items[itemType].index + appearance; + compendiumItemModel.skill[10] = itemType; + } + } + else + { + txt->setColor(compendiumContentsDefaultColor); + } + } + } + } + + static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) + { + if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + { + return; + } + auto& entry = CompendiumEntries.items[name]; + if ( Frame* page_right = parent->findFrame("page_right") ) + { + if ( page_right = page_right->findFrame("page_right_inner") ) + { + for ( auto f : page_right->getFrames() ) + { + f->removeSelf(); + } + + const int entrySize = 64; + compendiumItem.status = EXCELLENT; + compendiumItem.count = 1; + compendiumItem.identified = true; + + if ( !compendiumItemTooltip.tooltipContainerFrame ) + { + createInventoryTooltipFrame(0, + parent, + compendiumItemTooltip.tooltipContainerFrame, + compendiumItemTooltip.titleOnlyTooltipFrame, + compendiumItemTooltip.tooltipFrame, + compendiumItemTooltip.interactFrame, + compendiumItemTooltip.tooltipPromptFrame); + compendiumItemTooltip.tooltipFrame->setDisabled(false); + } + compendiumItemTooltip.clear(); + + page_right->setTickCallback([](Widget& widget) { + auto frame = static_cast(&widget); + if ( auto parent = frame->getParent() ) + { + if ( parent = parent->getParent() ) + { + players[0]->inventoryUI.updateInventoryItemTooltip(parent); + } + } + }); + + for ( int i = 0; i < entry.items_in_category.size(); ++i ) + { + auto& data = entry.items_in_category[i]; + auto entry = page_right->addFrame(data.name.c_str()); + entry->setHollow(true); + entry->setClickable(false); + entry->setSize(SDL_Rect{ 8, 8 + i * entrySize, page_right->getSize().w - 32, entrySize }); + /*Button* btn = entry->addButton("item btn"); + btn->setSize(entry->getSize()); + btn->setText("click");*/ + + if ( i == 0 ) + { + auto selector_bg = page_right->addImage(SDL_Rect{ entry->getSize().x, 0, entry->getSize().w, entry->getSize().h}, + makeColorRGB(71, 41, 27), "images/system/white.png", "selector_bg"); + selector_bg->disabled = true; + } + + entry->setTickCallback([](Widget& widget) { + auto frame = static_cast(&widget); + if ( Frame* page_right_inner = frame->getParent() ) + { + auto selector_bg = page_right_inner->findImage("selector_bg"); + if ( frame->getUserData() ) + { + bool first = (reinterpret_cast(frame->getUserData()) >> 7) & 1; + if ( first ) + { + if ( selector_bg ) + { + selector_bg->disabled = true; + } + } + } + auto txt = frame->findField("item name"); + /*if ( parent = parent->getParent() )*/ + { + if ( Frame* parent = page_right_inner->getParent() ) + { + if ( parent = parent->getParent() ) + { + + SDL_Rect absolutePos = frame->getAbsoluteSize(); + if ( frame->capturesMouseInRealtimeCoords() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) + { + selectCompendiumItemInList(*frame, *page_right_inner); + } + + SDL_Rect pos = frame->getSize(); + bool hovered = false; + if ( selector_bg ) + { + selector_bg->disabled = false; + selector_bg->pos.y = frame->getSize().y; + } + auto itemBg = frame->findImage("item bg"); + if ( itemBg ) + { + SDL_Rect tmp = frame->getSize(); + tmp.x += itemBg->pos.x; + tmp.y += itemBg->pos.y; + tmp.w = itemBg->pos.w; + tmp.h = itemBg->pos.h; + frame->setSize(tmp); + hovered = frame->capturesMouseInRealtimeCoords(); + } + frame->setSize(pos); + + if ( hovered ) + { + //frame->select(); + + const int itemType = ItemTooltips.itemNameStringToItemID[widget.getName()]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + compendiumItem.type = (ItemType)itemType; + if ( frame->getUserData() ) + { + compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); + } + players[0]->hud.updateFrameTooltip(&compendiumItem, absolutePos.x - 16, absolutePos.y, Player::PANEL_JUSTIFY_RIGHT, parent); + } + } + } + } + } + } + } + }); + const int id = ItemTooltips.itemNameStringToItemID[data.name]; + auto itemBg = entry->addImage(SDL_Rect{ 8, 8, 48, 48 }, + 0xFFFFFFFF, + "*images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_02.png", "item bg"); + auto itemImg = entry->addImage(SDL_Rect{ itemBg->pos.x + 5, itemBg->pos.y + 5, 36, 36 }, + 0xFFFFFFFF, "", "item img"); + + auto itemName = entry->addField("item name", 128); + itemName->setFont(smallfont_outline); + itemName->setSize(SDL_Rect{ itemBg->pos.x + itemBg->pos.w + 4, 8, entry->getSize().w - 8, 24 }); + std::string name = items[id].getIdentifiedName(); + camelCaseString(name); + itemName->setText(name.c_str()); + + compendiumItem.type = (ItemType)id; + compendiumItem.appearance = local_rng.rand() % items[id].variations; + if ( id == TOOL_PLAYER_LOOT_BAG ) + { + compendiumItem.appearance = local_rng.rand() % 4; + } + itemImg->path = getItemSpritePath(0, compendiumItem); + Uint8 userData = std::min((Uint8)127, (Uint8)(0x7F & compendiumItem.appearance)); + if ( i == 0 ) + { + userData |= 1 << 7; + entry->setUserData((void*)(intptr_t)userData); + selectCompendiumItemInList(*entry, *page_right); + } + else + { + entry->setUserData((void*)(intptr_t)userData); + itemName->setColor(compendiumContentsDefaultColor); + } + + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(412, entry->getSize().y + entry->getSize().h); + page_right->setActualSize(actualPos); + } + //page_right->setSelection(0); + //page_right->scrollToSelection(true); + //page_right->activateSelection(); + } + } + } + static void refreshCompendiumEntryCodex(std::string name, Frame* parent) { if ( CompendiumEntries.codex.find(name) == CompendiumEntries.codex.end() ) @@ -32980,9 +33276,6 @@ namespace MainMenu { "codex" }; static std::string compendium_current = "monsters"; - static std::string compendium_contents_current = ""; - constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); - constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); static auto contents_activate_fn = [](Frame::entry_t& entry) { @@ -33012,7 +33305,8 @@ namespace MainMenu { auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents : nullptr)); + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents : nullptr))); std::string content = ""; if ( entries ) @@ -33035,6 +33329,11 @@ namespace MainMenu { { refreshCompendiumEntryCodex(content, compendiumFrame); } + else if ( compendium_current == "items" ) + { + refreshCompendiumEntryItemsBlurb(content, compendiumFrame); + refreshCompendiumEntryItemsList(content, compendiumFrame); + } else if ( compendium_current == "world" ) { refreshCompendiumEntryWorld(content, compendiumFrame); @@ -33058,7 +33357,8 @@ namespace MainMenu { { auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents : nullptr)); + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents : nullptr))); auto toRemove = contents->getEntries(); for ( auto r : toRemove ) @@ -33106,7 +33406,27 @@ namespace MainMenu { constexpr Uint32 statValColor = makeColorRGB(159, 145, 127); constexpr Uint32 detailsValColor = makeColorRGB(135, 94, 45); - if ( compendium_current == "codex" ) + if ( compendium_current == "items" ) + { + /*auto charTxt = page_right_inner->addField("items txt", 64); + charTxt->setFont(menu_option_font); + charTxt->setText("Items"); + charTxt->setHJustify(Field::justify_t::LEFT); + charTxt->setVJustify(Field::justify_t::TOP); + charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); + charTxt->setColor(makeColor(198, 190, 179, 255));*/ + + /*int statx = padx + 4; + int staty = pady + 18 + 9; + auto statsTxt = page_right_inner->addField("details", 1024); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx, staty, page_right_inner->getSize().w - statx - 26, 400 }); + statsTxt->setColor(detailsValColor);*/ + } + else if ( compendium_current == "codex" ) { auto charTxt = page_right_inner->addField("tips txt", 64); charTxt->setFont(menu_option_font); @@ -33483,6 +33803,18 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); tab->setCallback([](Button& button) { compendium_current = "items"; + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + compendiumPopulatePageRight(page_right); + } + compendiumPopulateContents(frame); + } }); tab = window->addButton(compendiumCategories[2].c_str()); @@ -33615,6 +33947,10 @@ namespace MainMenu { { drawMonsterPreview(compendiumMonster, pos, 0.0); } + else if ( compendium_current == "items" ) + { + drawItemPreview(&compendiumItemModel, pos, 0.0); + } }); model_viewer->setTickCallback([](Widget& widget) { /*if ( ticks % TICKS_PER_SECOND == 0 ) @@ -33638,6 +33974,11 @@ namespace MainMenu { CompendiumEntries.readCodexFromFile(); refreshCompendiumEntryCodex(Compendium_t::CompendiumCodex_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); } + else if ( compendium_current == "items" ) + { + CompendiumEntries.readItemsFromFile(); + refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } } /*if ( keystatus[SDLK_g] ) { From 8da42c3438ce97764669e04f6c9fb39827185277 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 22 Apr 2024 21:00:25 +1000 Subject: [PATCH 014/244] * fix insectoid seed event text not updating --- src/ui/MainMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 76ba660bc..c8bdafb51 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -31645,7 +31645,7 @@ namespace MainMenu { txt->setText(Language::get(6161)); txt->setColor(makeColor(255, 255, 255, 255)); } - else if ( e.scenarioInfo.race >= RACE_HUMAN && e.scenarioInfo.race < RACE_INSECTOID ) + else if ( e.scenarioInfo.race >= RACE_HUMAN && e.scenarioInfo.race <= RACE_INSECTOID ) { std::string s = getMonsterLocalizedName(getMonsterFromPlayerRace(e.scenarioInfo.race)); camelCaseString(s); From 792be364c00e2d6b7b0765a68704e40e08fee33c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 7 May 2024 09:53:23 +1000 Subject: [PATCH 015/244] * wip items --- src/actarrow.cpp | 19 ++ src/actchest.cpp | 8 + src/actfountain.cpp | 12 ++ src/acthudweapon.cpp | 9 + src/actplayer.cpp | 16 +- src/actthrown.cpp | 14 ++ src/acttorch.cpp | 3 + src/entity.cpp | 193 +++++++++++++++-- src/game.cpp | 21 ++ src/interface/identify_and_appraise.cpp | 5 + src/interface/interface.cpp | 7 + src/item_usage_funcs.cpp | 47 ++++- src/items.cpp | 62 +++++- src/items.hpp | 1 + src/magic/actmagic.cpp | 97 ++++++--- src/menu.cpp | 16 ++ src/mod_tools.cpp | 263 +++++++++++++++++++++--- src/mod_tools.hpp | 86 +++++--- src/net.cpp | 101 ++++++++- src/player.cpp | 3 +- src/player.hpp | 10 + src/scores.cpp | 23 +++ src/scores.hpp | 2 + src/shops.cpp | 7 + 24 files changed, 909 insertions(+), 116 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index 6e120f8f2..0953be2da 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -23,6 +23,7 @@ #include "scores.hpp" #include "player.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -662,6 +663,24 @@ void actArrow(Entity* my) parent->killedByMonsterObituary(hit.entity); } + if ( parent->behavior == &actPlayer ) + { + if ( itemTypeIsQuiver((ItemType)my->arrowQuiverType) ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_MAX, (ItemType)my->arrowQuiverType, damage); + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_AMMO_HIT, (ItemType)my->arrowQuiverType, 1); + } + if ( isRangedWeapon((ItemType)my->arrowShotByWeapon) ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_MAX, (ItemType)my->arrowShotByWeapon, damage); + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SHOTS_HIT, (ItemType)my->arrowShotByWeapon, 1); + } + if ( my->arrowShotByWeapon == SLING && damage == 0 ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_0, (ItemType)my->arrowShotByWeapon, 1); + } + } + if ( hit.entity->behavior == &actMonster && parent->behavior == &actPlayer ) { if ( damage >= 80 && hitstats->type != HUMAN && !parent->checkFriend(hit.entity) ) diff --git a/src/actchest.cpp b/src/actchest.cpp index 1bbfa53f8..b51c438b0 100644 --- a/src/actchest.cpp +++ b/src/actchest.cpp @@ -19,6 +19,7 @@ #include "net.hpp" #include "player.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /* * Chest theme ideas: @@ -1173,6 +1174,13 @@ Item* Entity::addItemToChestFromInventory(int player, Item* item, int amount, bo messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1087)); } playSoundPlayer(player, 90, 64); + if ( !item->identified ) + { + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, item->type, 1); + } + } item->identified = true; return nullptr; } diff --git a/src/actfountain.cpp b/src/actfountain.cpp index b5d780d6e..28f9558ca 100644 --- a/src/actfountain.cpp +++ b/src/actfountain.cpp @@ -23,6 +23,7 @@ #include "colors.hpp" #include "scores.hpp" #include "prng.hpp" +#include "mod_tools.hpp" //Fountain functions. const std::vector fountainPotionDropChances = @@ -399,6 +400,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->helmet->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->helmet->type, 1); } if ( stats[i]->breastplate ) { @@ -407,6 +409,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->breastplate->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->breastplate->type, 1); } if ( stats[i]->gloves ) { @@ -415,6 +418,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->gloves->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->gloves->type, 1); } if ( stats[i]->shoes ) { @@ -423,6 +427,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->shoes->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->shoes->type, 1); } if ( stats[i]->shield ) { @@ -431,6 +436,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->shield->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->shield->type, 1); } if ( stats[i]->weapon ) { @@ -441,6 +447,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->weapon->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->weapon->type, 1); } } if ( stats[i]->cloak ) @@ -450,6 +457,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->cloak->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->cloak->type, 1); } if ( stats[i]->amulet ) { @@ -458,6 +466,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->amulet->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->amulet->type, 1); } if ( stats[i]->ring ) { @@ -466,6 +475,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->ring->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->ring->type, 1); } if ( stats[i]->mask ) { @@ -474,6 +484,7 @@ void actFountain(Entity* my) stuckOnYouSuccess = true; } stats[i]->mask->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, stats[i]->mask->type, 1); } if ( multiplayer == SERVER && i > 0 && !players[i]->isLocalPlayer() ) { @@ -559,6 +570,7 @@ void actFountain(Entity* my) } } chosen.first->beatitude++; + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_BLESSED_TOTAL, chosen.first->type, 1); if ( multiplayer == SERVER && i > 0 && !players[i]->isLocalPlayer() ) { diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp index 53b7aad8f..c1e531b0e 100644 --- a/src/acthudweapon.cpp +++ b/src/acthudweapon.cpp @@ -22,6 +22,7 @@ #include "scores.hpp" #include "ui/MainMenu.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -1074,6 +1075,10 @@ void actHudWeapon(Entity* my) { Item* quiver = stats[HUDWEAPON_PLAYERNUM]->shield; quiver->count--; + + Compendium_t::Events_t::eventUpdate(HUDWEAPON_PLAYERNUM, Compendium_t::CPDM_AMMO_FIRED, + quiver->type, 1); + if ( quiver->count <= 0 ) { if ( quiver->node ) @@ -1225,6 +1230,10 @@ void actHudWeapon(Entity* my) { Item* quiver = stats[HUDWEAPON_PLAYERNUM]->shield; quiver->count--; + + Compendium_t::Events_t::eventUpdate(HUDWEAPON_PLAYERNUM, Compendium_t::CPDM_AMMO_FIRED, + quiver->type, 1); + if ( quiver->count <= 0 ) { if ( quiver->node ) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 4e7b9f235..4c0a4d277 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -3210,8 +3210,8 @@ void Player::PlayerMovement_t::handlePlayerMovement(bool useRefreshRateDelta) } // calculate weight - int weight = getCharacterModifiedWeight(); - real_t weightratio = getWeightRatio(weight, statGetSTR(stats[PLAYER_NUM], players[PLAYER_NUM]->entity)); + const int weight = getCharacterModifiedWeight(); + const real_t weightratio = getWeightRatio(weight, statGetSTR(stats[PLAYER_NUM], players[PLAYER_NUM]->entity)); // calculate movement forces @@ -5428,6 +5428,10 @@ void actPlayer(Entity* my) else if ( tempItem->type == GEM_ROCK ) { //Auto-succeed on rocks. + if ( !tempItem->identified ) + { + Compendium_t::Events_t::eventUpdate(PLAYER_NUM, Compendium_t::CPDM_APPRAISED, tempItem->type, 1); + } tempItem->identified = true; tempItem->notifyIcon = true; messagePlayer(PLAYER_NUM, MESSAGE_INVENTORY, Language::get(570), tempItem->description()); @@ -5566,6 +5570,10 @@ void actPlayer(Entity* my) } if ( success ) { + if ( !tempItem->identified ) + { + Compendium_t::Events_t::eventUpdate(PLAYER_NUM, Compendium_t::CPDM_APPRAISED, tempItem->type, 1); + } tempItem->identified = true; tempItem->notifyIcon = true; messagePlayer(PLAYER_NUM, MESSAGE_INVENTORY, Language::get(570), tempItem->description()); @@ -7722,6 +7730,10 @@ void actPlayer(Entity* my) { nextnode = node->next; Item* item = (Item*)node->element; + if ( itemCategory(item) == SPELL_CAT ) + { + continue; + } if ( item->type == ARTIFACT_ORB_PURPLE ) { int c = item->count; diff --git a/src/actthrown.cpp b/src/actthrown.cpp index cd5d2aa54..cfbcbdb7a 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -23,6 +23,7 @@ #include "magic/magic.hpp" #include "paths.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -812,7 +813,20 @@ void actThrown(Entity* my) if ( !friendlyHit ) { hit.entity->modHP(-damage); + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_DMG_MAX, item->type, damage); + } } + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_THROWN_HITS, item->type, 1); + } + // set the obituary snprintf(whatever, 255, Language::get(1508), itemname); hit.entity->setObituary(whatever); diff --git a/src/acttorch.cpp b/src/acttorch.cpp index 074fd50ea..6150583a0 100644 --- a/src/acttorch.cpp +++ b/src/acttorch.cpp @@ -19,6 +19,7 @@ #include "player.hpp" #include "interface/interface.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -159,6 +160,7 @@ void actTorch(Entity* my) else { messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(589)); + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_TORCH_WALLS, TOOL_TORCH, 1); list_RemoveNode(my->light->node); list_RemoveNode(my->mynode); itemPickup(i, item); @@ -311,6 +313,7 @@ void actCrystalShard(Entity* my) else { messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(589)); + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_TORCH_WALLS, TOOL_CRYSTALSHARD, 1); list_RemoveNode(my->light->node); list_RemoveNode(my->mynode); itemPickup(i, item); diff --git a/src/entity.cpp b/src/entity.cpp index 9e4d99061..d79936a5a 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -596,15 +596,18 @@ Entity::~Entity() // set appropriate player pointer to NULL for ( i = 0; i < MAXPLAYERS; ++i ) { - if ( this == players[i]->entity ) + if ( players[i] ) { - players[i]->entity = nullptr; //TODO: PLAYERSWAP VERIFY. Should this do anything to the player itself? - players[i]->cleanUpOnEntityRemoval(); - } - if ( this == players[i]->ghost.my ) - { - players[i]->ghost.my = nullptr; - players[i]->ghost.reset(); + if ( this == players[i]->entity ) + { + players[i]->entity = nullptr; //TODO: PLAYERSWAP VERIFY. Should this do anything to the player itself? + players[i]->cleanUpOnEntityRemoval(); + } + if ( this == players[i]->ghost.my ) + { + players[i]->ghost.my = nullptr; + players[i]->ghost.reset(); + } } } // destroy my children @@ -4608,6 +4611,7 @@ void Entity::handleEffects(Stat* myStats) else { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(646), myStats->cloak->getName()); // "Your %s burns to ash!" + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CLOAK_BURNED, myStats->cloak->type, 1); } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -7121,6 +7125,12 @@ void Entity::attack(int pose, int charge, Entity* target) // ranged weapons (bows) else if ( isRangedWeapon(*myStats->weapon) ) { + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_SHOTS_FIRED, + stats[skill[2]]->weapon->type, 1); + } + // damage weapon if applicable int bowDegradeChance = 50; if ( behavior == &actPlayer ) @@ -7234,6 +7244,12 @@ void Entity::attack(int pose, int charge, Entity* target) //TODO: Refactor this so that we don't have to copy paste this check a million times whenever some-one uses up an item. if ( behavior == &actPlayer && pose != MONSTER_POSE_RANGED_SHOOT2 ) { + if ( players[skill[2]]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_AMMO_FIRED, + myStats->shield->type, 1); + } + myStats->shield->count--; if ( myStats->shield->count <= 0 ) { @@ -7458,6 +7474,12 @@ void Entity::attack(int pose, int charge, Entity* target) } } + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_THROWN, + stats[skill[2]]->weapon->type, 1); + } + //TODO: Refactor this so that we don't have to copy paste this check a million times whenever some-one uses up an item. myStats->weapon->count--; if ( myStats->weapon->count <= 0 ) @@ -7638,6 +7660,14 @@ void Entity::attack(int pose, int charge, Entity* target) { messagePlayer(player, MESSAGE_COMBAT, Language::get(664)); playSoundEntity(this, 76, 64); + + if ( behavior == &actPlayer ) + { + if ( myStats->weapon && myStats->weapon->type == TOOL_PICKAXE ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_BROKEN, myStats->weapon->type, 1); + } + } } else { @@ -7795,6 +7825,14 @@ void Entity::attack(int pose, int charge, Entity* target) { messagePlayer(player, MESSAGE_COMBAT, Language::get(664)); playSoundEntity(this, 76, 64); + + if ( behavior == &actPlayer ) + { + if ( myStats->weapon && myStats->weapon->type == TOOL_PICKAXE ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_BROKEN, myStats->weapon->type, 1); + } + } } else { @@ -8284,7 +8322,7 @@ void Entity::attack(int pose, int charge, Entity* target) damagePreMultiplier = 2; } - int myAttack = std::max(0, (Entity::getAttack(this, myStats, behavior == &actPlayer) * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats)); + const int myAttack = std::max(0, (Entity::getAttack(this, myStats, behavior == &actPlayer) * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats)); int enemyAC = AC(hitstats); if ( weaponskill == PRO_POLEARM && myStats->weapon && myStats->weapon->type == ARTIFACT_SPEAR && !shapeshifted ) { @@ -8473,6 +8511,15 @@ void Entity::attack(int pose, int charge, Entity* target) } } + if ( hitstats->defending && damage == 0 && hitstats->shield ) + { + if ( playerhit >= 0 ) + { + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BLOCKED_ATTACKS, hitstats->shield->type, 1); + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BLOCKED_HIGHEST_DMG, hitstats->shield->type, myAttack); + } + } + hit.entity->modHP(-damage); // do the damage bool skillIncreased = false; // skill increase @@ -8632,6 +8679,14 @@ void Entity::attack(int pose, int charge, Entity* target) if ( weaponToBreak != nullptr && !shapeshifted ) { weaponType = (*weaponToBreak)->type; + + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS, + weaponType, 1); + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_DMG_MAX, weaponType, damage); + } + if ( weaponType == ARTIFACT_AXE || weaponType == ARTIFACT_MACE || weaponType == ARTIFACT_SPEAR || weaponType == ARTIFACT_SWORD ) { artifactWeapon = true; @@ -10939,6 +10994,14 @@ void Entity::attack(int pose, int charge, Entity* target) steamAchievementClient(player, "BARONY_ACH_BAD_REVIEW"); } + if ( behavior == &actPlayer ) + { + if ( myStats->weapon && myStats->weapon->type == TOOL_PICKAXE ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_PICKAXE_WALLS_DUG, myStats->weapon->type, 1); + } + } + map.tiles[OBSTACLELAYER + hit.mapy * MAPLAYERS + hit.mapx * MAPLAYERS * map.height] = 0; // send wall destroy info to clients if ( multiplayer == SERVER ) @@ -10962,13 +11025,24 @@ void Entity::attack(int pose, int charge, Entity* target) generatePathMaps(); } int chance = 2 + (myStats->type == GOBLIN ? 2 : 0); - if ( local_rng.rand() % chance && degradePickaxe ) + if ( local_rng.rand() % chance && degradePickaxe && myStats->weapon ) { - myStats->weapon->status = static_cast(myStats->weapon->status - 1); + if ( myStats->weapon->status > BROKEN ) + { + myStats->weapon->status = static_cast(myStats->weapon->status - 1); + } if ( myStats->weapon->status == BROKEN ) { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(704)); playSoundEntity(this, 76, 64); + + if ( behavior == &actPlayer ) + { + if ( myStats->weapon && myStats->weapon->type == TOOL_PICKAXE ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_BROKEN, myStats->weapon->type, 1); + } + } } else { @@ -10996,6 +11070,12 @@ void Entity::attack(int pose, int charge, Entity* target) { // bang spawnBang(hit.x - cos(yaw) * 2, hit.y - sin(yaw) * 2, 0); + + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS_MISSES, + myStats->weapon->type, 1); + } } } else @@ -11003,6 +11083,59 @@ void Entity::attack(int pose, int charge, Entity* target) // bang //spawnBang(hit.x - cos(my->yaw)*2,hit.y - sin(my->yaw)*2,0); playSoundPos(hit.x, hit.y, 183, 64); + + if ( !myStats->weapon && !shapeshifted ) + { + if ( myStats->gloves ) + { + switch ( myStats->gloves->type ) + { + case BRASS_KNUCKLES: + case IRON_KNUCKLES: + case SPIKED_GAUNTLETS: + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS_MISSES, + myStats->gloves->type, 1); + } + break; + default: + break; + } + } + } + } + } + else + { + // hit nothing + if ( myStats->weapon != NULL && !shapeshifted ) + { + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS_MISSES, + myStats->weapon->type, 1); + } + } + else if ( !myStats->weapon && !shapeshifted ) + { + if ( myStats->gloves ) + { + switch ( myStats->gloves->type ) + { + case BRASS_KNUCKLES: + case IRON_KNUCKLES: + case SPIKED_GAUNTLETS: + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS_MISSES, + myStats->gloves->type, 1); + } + break; + default: + break; + } + } } } @@ -11915,12 +12048,18 @@ void Entity::awardXP(Entity* src, bool share, bool root) int gain = (xpGain * numshares); // summoned monsters aren't penalised XP. if ( inspiration ) { + int oldGain = gain; gain *= inspirationMult; if ( ((followerStats->EXP + gain) >= 100) && ((followerStats->EXP + (xpGain * numshares)) < 100) ) { // inspiration caused us to level steamAchievementEntity(this, "BARONY_ACH_BY_EXAMPLE"); } + + if ( gain > oldGain ) + { + Compendium_t::Events_t::eventUpdate(this->skill[2], Compendium_t::CPDM_INSPIRATION_XP, HAT_CROWN, gain - oldGain); + } } followerStats->EXP += gain; } @@ -11929,12 +12068,18 @@ void Entity::awardXP(Entity* src, bool share, bool root) int gain = xpGain; if ( inspiration ) { + int oldGain = gain; gain *= inspirationMult; if ( ((followerStats->EXP + gain) >= 100) && ((followerStats->EXP + xpGain) < 100) ) { // inspiration caused us to level steamAchievementEntity(this, "BARONY_ACH_BY_EXAMPLE"); } + + if ( gain > oldGain ) + { + Compendium_t::Events_t::eventUpdate(this->skill[2], Compendium_t::CPDM_INSPIRATION_XP, HAT_CROWN, gain - oldGain); + } } followerStats->EXP += gain; } @@ -11956,6 +12101,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) int gain = xpGain; if ( inspiration ) { + int oldGain = gain; gain *= inspirationMult; if ( ((destStats->EXP + gain) >= 100) && ((destStats->EXP + xpGain) < 100) ) { @@ -11968,6 +12114,17 @@ void Entity::awardXP(Entity* src, bool share, bool root) } } } + + if ( gain > oldGain ) + { + if ( behavior == &actMonster ) + { + if ( auto leader = monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdate(leader->skill[2], Compendium_t::CPDM_INSPIRATION_XP, HAT_CROWN, gain - oldGain); + } + } + } } destStats->EXP += gain; } @@ -17422,7 +17579,11 @@ void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum) net_packet->len = 8; sendPacketSafe(net_sock, -1, net_packet, playerhit - 1); } - + if ( hitstats.defending ) + { + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN_BY_BLOCKING, armor.type, 1); + } + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN, armor.type, 1); return; } @@ -17463,6 +17624,14 @@ void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum) playSoundEntity(this, 76, 64); messagePlayer(playerhit, MESSAGE_EQUIPMENT, Language::get(682), armor.getName()); } + if ( playerhit >= 0 ) + { + if ( hitstats.defending ) + { + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN_BY_BLOCKING, armor.type, 1); + } + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN, armor.type, 1); + } } if ( playerhit > 0 && multiplayer == SERVER && !players[playerhit]->isLocalPlayer() ) { diff --git a/src/game.cpp b/src/game.cpp index 611081c07..e74dd29eb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2484,6 +2484,11 @@ void gameLogic(void) list_FreeAll(&tempFollowers[c]); } + for ( c = 0; c < MAXPLAYERS; c++ ) + { + Compendium_t::Events_t::onLevelChangeEvent(c); + } + // save at end of level change if ( gameModeManager.allowsSaves() ) { @@ -2739,6 +2744,14 @@ void gameLogic(void) break; } + if ( ticks % TICKS_PER_SECOND == 25 ) + { + if ( item->identified ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + } + } + if ( item->type == FOOD_BLOOD && stats[player]->playerRace == RACE_VAMPIRE && stats[player]->appearance == 0 ) { bloodCount += item->count; @@ -3377,6 +3390,14 @@ void gameLogic(void) break; } + if ( ticks % TICKS_PER_SECOND == 25 ) + { + if ( item->identified ) + { + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + } + } + if ( itemCategory(item) == WEAPON ) { if ( item->beatitude >= 10 ) diff --git a/src/interface/identify_and_appraise.cpp b/src/interface/identify_and_appraise.cpp index d8f313c80..0f83b425a 100644 --- a/src/interface/identify_and_appraise.cpp +++ b/src/interface/identify_and_appraise.cpp @@ -18,6 +18,7 @@ #include "../player.hpp" #include "interface.hpp" #include "../scores.hpp" +#include "../mod_tools.hpp" //Identify GUI definitions. SDL_Surface* identifyGUI_img; @@ -432,6 +433,10 @@ void Player::Inventory_t::Appraisal_t::appraiseItem(Item* item) //If appraisal skill >= LEGENDARY, then auto-complete appraisal. Else, do the normal routine. if ( stats[player.playernum]->getProficiency(PRO_APPRAISAL) >= CAPSTONE_UNLOCK_LEVEL[PRO_APPRAISAL] ) { + if ( !item->identified ) + { + Compendium_t::Events_t::eventUpdate(player.playernum, Compendium_t::CPDM_APPRAISED, item->type, 1); + } item->identified = true; item->notifyIcon = true; messagePlayer(player.playernum, MESSAGE_INVENTORY, Language::get(320), item->description()); diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index 40f802559..167993c68 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -6161,6 +6161,13 @@ void GenericGUIMenu::identifyItem(Item* item) return; } + if ( gui_player >= 0 && gui_player < MAXPLAYERS && players[gui_player]->isLocalPlayer() ) + { + if ( !item->identified ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_APPRAISED, item->type, 1); + } + } item->identified = true; messagePlayer(gui_player, MESSAGE_MISC, Language::get(320), item->description()); closeGUI(); diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index bfbeb1997..9fff05343 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -25,6 +25,7 @@ #include "collision.hpp" #include "scores.hpp" #include "prng.hpp" +#include "mod_tools.hpp" bool item_PotionWater(Item*& item, Entity* entity, Entity* usedBy) { @@ -2712,6 +2713,7 @@ void item_ScrollEnchantWeapon(Item* item, int player) } } (*toEnchant)->beatitude += 1 + item->beatitude; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BLESSED_TOTAL, (*toEnchant)->type, item->beatitude); } if ( multiplayer == CLIENT ) @@ -2878,6 +2880,7 @@ void item_ScrollEnchantArmor(Item* item, int player) messagePlayer(player, MESSAGE_HINT, Language::get(860), armor->getName()); } armor->beatitude += 1 + item->beatitude; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BLESSED_TOTAL, armor->type, item->beatitude); } if ( multiplayer == CLIENT ) @@ -3631,6 +3634,13 @@ void item_ScrollDestroyArmor(Item* item, int player) { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(875), armor->getName()); + if ( armor->status > BROKEN ) + { + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, armor->type, 1); + } + } armor->status = static_cast(0); if ( multiplayer == CLIENT ) @@ -3923,6 +3933,10 @@ void item_ScrollSummon(Item* item, int player) void item_ToolTowel(Item*& item, int player) { + if ( !item ) + { + return; + } if ( players[player]->isLocalPlayer() ) { messagePlayer(player, MESSAGE_STATUS, Language::get(883)); @@ -3934,6 +3948,19 @@ void item_ToolTowel(Item*& item, int player) || stats[player]->EFFECTS[EFF_BLEEDING] ) { steamAchievementClient(player, "BARONY_ACH_BRING_A_TOWEL"); + if ( stats[player]->EFFECTS[EFF_GREASY] ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TOWEL_GREASY, item->type, 1); + } + else if ( stats[player]->EFFECTS[EFF_MESSY] ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TOWEL_MESSY, item->type, 1); + } + else if ( stats[player]->EFFECTS[EFF_BLEEDING] ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TOWEL_BLEEDING, item->type, 1); + } + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TOWEL_USES, item->type, 1); } stats[player]->EFFECTS[EFF_GREASY] = false; stats[player]->EFFECTS[EFF_MESSY] = false; @@ -3976,6 +4003,10 @@ void item_ToolTinOpener(Item* item, int player) void item_ToolMirror(Item*& item, int player) { + if ( !item ) + { + return; + } Sint16 beatitude = item->beatitude; if ( !players[player]->isLocalPlayer() ) { @@ -4015,7 +4046,10 @@ void item_ToolMirror(Item*& item, int player) } else if ( beatitude > 0 ) { - players[player]->entity->teleportRandom(); + if ( players[player]->entity->teleportRandom() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_MIRROR_TELEPORTS, TOOL_MIRROR, 1); + } messagePlayer(player, MESSAGE_HINT, Language::get(890)); } else if ( beatitude < 0 ) @@ -4144,6 +4178,7 @@ void item_ToolMirror(Item*& item, int player) { consumeItem(item, player); item = nullptr; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, TOOL_MIRROR, 1); } } } @@ -4296,6 +4331,10 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) void item_Food(Item*& item, int player) { + if ( !item ) + { + return; + } int oldcount; int pukeChance; @@ -4365,6 +4404,7 @@ void item_Food(Item*& item, int player) { steamAchievement("BARONY_ACH_MUSCLE_MEMORY"); } + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CONSUMED, item->type, 1); } if ( multiplayer == CLIENT ) @@ -4608,6 +4648,10 @@ void item_Food(Item*& item, int player) void item_FoodTin(Item*& item, int player) { + if ( !item ) + { + return; + } int oldcount; int pukeChance; bool slippery = false; @@ -4674,6 +4718,7 @@ void item_FoodTin(Item*& item, int player) { steamStatisticUpdate(STEAM_STAT_IRON_GUT, STEAM_STAT_INT, 1); } + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CONSUMED, item->type, 1); } if ( multiplayer == CLIENT ) diff --git a/src/items.cpp b/src/items.cpp index 62c35579e..5d3fa4321 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1754,6 +1754,10 @@ EquipItemResult equipItem(Item* const item, Item** const slot, const int player, messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1089), (*slot)->getName()); } playSoundPlayer(player, 90, 64); + if ( !((*slot)->identified) ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, (*slot)->type, 1); + } } (*slot)->identified = true; return EQUIP_ITEM_FAIL_CANT_UNEQUIP; @@ -1862,6 +1866,10 @@ EquipItemResult equipItem(Item* const item, Item** const slot, const int player, messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1089), (*slot)->getName()); } playSoundPlayer(player, 90, 64); + if ( !((*slot)->identified) ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, (*slot)->type, 1); + } } (*slot)->identified = true; return EQUIP_ITEM_FAIL_CANT_UNEQUIP; @@ -2786,6 +2794,7 @@ void useItem(Item* item, const int player, Entity* usedBy, bool unequipForDroppi && (players[player] && players[player]->entity) && players[player]->entity == usedBy ) { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CONSUMED, potionType, 1); if ( tryLearnPotionRecipe ) { GenericGUI[player].alchemyLearnRecipe(potionType, true); @@ -2821,6 +2830,7 @@ void useItem(Item* item, const int player, Entity* usedBy, bool unequipForDroppi if ( tryEmptyBottle && local_rng.rand() % 100 < std::min(80, (60 + skillLVL * 10)) ) // 60 - 80% chance { Item* emptyBottle = newItem(POTION_EMPTY, SERVICABLE, 0, 1, 0, true, nullptr); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BOTTLES_FROM_CONSUME, POTION_EMPTY, 1); itemPickup(player, emptyBottle); messagePlayer(player, MESSAGE_INTERACTION, Language::get(3351), items[POTION_EMPTY].getIdentifiedName()); free(emptyBottle); @@ -2994,6 +3004,7 @@ Item* itemPickup(const int player, Item* const item, Item* addToSpecificInventor { if ( !item->identified ) { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, item->type, 1); item->identified = true; item->notifyIcon = true; if ( item->type == GEM_GLASS ) @@ -4676,6 +4687,16 @@ bool Item::canUnequip(const Stat* const wielder) return true; }*/ + int player = -1; + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( stats[i] == wielder ) + { + player = i; + break; + } + } + if ( wielder ) { if ( wielder->type == AUTOMATON ) @@ -4686,6 +4707,13 @@ bool Item::canUnequip(const Stat* const wielder) { if ( beatitude > 0 ) { + if ( !identified ) + { + if ( player >= 0 && players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, type, 1); + } + } identified = true; return false; } @@ -4698,6 +4726,13 @@ bool Item::canUnequip(const Stat* const wielder) if (beatitude < 0) { + if ( !identified ) + { + if ( player >= 0 && players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_APPRAISED, type, 1); + } + } identified = true; return false; } @@ -5266,18 +5301,23 @@ node_t* getMeleeWeaponItemNodeInInventory(const Stat* const myStats) bool isRangedWeapon(const Item& item) { - switch ( item.type ) + return isRangedWeapon(item.type); +} + +bool isRangedWeapon(const ItemType type) +{ + switch ( type ) { - case SLING: - case SHORTBOW: - case CROSSBOW: - case ARTIFACT_BOW: - case LONGBOW: - case COMPOUND_BOW: - case HEAVY_CROSSBOW: - return true; - default: - return false; + case SLING: + case SHORTBOW: + case CROSSBOW: + case ARTIFACT_BOW: + case LONGBOW: + case COMPOUND_BOW: + case HEAVY_CROSSBOW: + return true; + default: + return false; } } diff --git a/src/items.hpp b/src/items.hpp index 006512cb3..8508434ed 100644 --- a/src/items.hpp +++ b/src/items.hpp @@ -755,6 +755,7 @@ int itemCompare(const Item* item1, const Item* item2, bool checkAppearance, bool */ bool isPotionBad(const Item& potion); bool isRangedWeapon(const Item& item); +bool isRangedWeapon(const ItemType type); bool isMeleeWeapon(const Item& item); bool itemIsThrowableTinkerTool(const Item* item); diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 14db63562..506876a41 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -834,6 +834,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. my->actmagicMirrorReflected = 1; my->actmagicMirrorReflectedCaster = parent->getUID(); } + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SHIELD_REFLECT, hitstats->shield->type, 1); } } } @@ -1014,66 +1015,96 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { if ( reflection == 1 ) { - if ( hitstats->cloak->count > 1 ) + if ( hitstats->cloak ) { - newItem(hitstats->cloak->type, hitstats->cloak->status, hitstats->cloak->beatitude, hitstats->cloak->count - 1, hitstats->cloak->appearance, hitstats->cloak->identified, &hitstats->inventory); + if ( hitstats->cloak->count > 1 ) + { + newItem(hitstats->cloak->type, hitstats->cloak->status, hitstats->cloak->beatitude, hitstats->cloak->count - 1, hitstats->cloak->appearance, hitstats->cloak->identified, &hitstats->inventory); + } } } else if ( reflection == 2 ) { - if ( hitstats->amulet->count > 1 ) + if ( hitstats->amulet ) { - newItem(hitstats->amulet->type, hitstats->amulet->status, hitstats->amulet->beatitude, hitstats->amulet->count - 1, hitstats->amulet->appearance, hitstats->amulet->identified, &hitstats->inventory); + if ( hitstats->amulet->count > 1 ) + { + newItem(hitstats->amulet->type, hitstats->amulet->status, hitstats->amulet->beatitude, hitstats->amulet->count - 1, hitstats->amulet->appearance, hitstats->amulet->identified, &hitstats->inventory); + } } } else if ( reflection == -1 ) { - if ( hitstats->shield->count > 1 ) + if ( hitstats->shield ) { - newItem(hitstats->shield->type, hitstats->shield->status, hitstats->shield->beatitude, hitstats->shield->count - 1, hitstats->shield->appearance, hitstats->shield->identified, &hitstats->inventory); + if ( hitstats->shield->count > 1 ) + { + newItem(hitstats->shield->type, hitstats->shield->status, hitstats->shield->beatitude, hitstats->shield->count - 1, hitstats->shield->appearance, hitstats->shield->identified, &hitstats->inventory); + } } } } if ( reflection == 1 ) { - hitstats->cloak->count = 1; - hitstats->cloak->status = static_cast(std::max(static_cast(BROKEN), hitstats->cloak->status - 1)); - if ( hitstats->cloak->status != BROKEN ) - { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(380)); - } - else + if ( hitstats->cloak ) { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(381)); - playSoundEntity(hit.entity, 76, 64); + hitstats->cloak->count = 1; + hitstats->cloak->status = static_cast(std::max(static_cast(BROKEN), hitstats->cloak->status - 1)); + if ( hitstats->cloak->status != BROKEN ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(380)); + } + else + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(381)); + playSoundEntity(hit.entity, 76, 64); + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, hitstats->cloak->type, 1); + } + } } } else if ( reflection == 2 ) { - hitstats->amulet->count = 1; - hitstats->amulet->status = static_cast(std::max(static_cast(BROKEN), hitstats->amulet->status - 1)); - if ( hitstats->amulet->status != BROKEN ) - { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(382)); - } - else + if ( hitstats->amulet ) { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(383)); - playSoundEntity(hit.entity, 76, 64); + hitstats->amulet->count = 1; + hitstats->amulet->status = static_cast(std::max(static_cast(BROKEN), hitstats->amulet->status - 1)); + if ( hitstats->amulet->status != BROKEN ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(382)); + } + else + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(383)); + playSoundEntity(hit.entity, 76, 64); + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, hitstats->amulet->type, 1); + } + } } } else if ( reflection == -1 ) { - hitstats->shield->count = 1; - hitstats->shield->status = static_cast(std::max(static_cast(BROKEN), hitstats->shield->status - 1)); - if ( hitstats->shield->status != BROKEN ) - { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(384)); - } - else + if ( hitstats->shield ) { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(385)); - playSoundEntity(hit.entity, 76, 64); + hitstats->shield->count = 1; + hitstats->shield->status = static_cast(std::max(static_cast(BROKEN), hitstats->shield->status - 1)); + if ( hitstats->shield->status != BROKEN ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(384)); + } + else + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(385)); + playSoundEntity(hit.entity, 76, 64); + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, hitstats->shield->type, 1); + } + } } } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) diff --git a/src/menu.cpp b/src/menu.cpp index c9d4c276e..d7b527112 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8672,6 +8672,10 @@ void doNewGame(bool makeHighscore) { } Player::Minimap_t::mapDetails.clear(); + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + players[c]->compendiumProgress.itemEvents.clear(); + } // disable cheats noclip = false; @@ -9433,6 +9437,7 @@ void doEndgame(bool saveHighscore) { bool localScores = gameModeManager.allowsHiscores(); bool onlineScores = gameModeManager.allowsGlobalHiscores(); bool allowedSavegames = gameModeManager.allowsSaves(); + bool allowedCompendiumProgression = gameModeManager.getMode() != GameModeManager_t::GAME_MODE_CUSTOM_RUN; if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) { victory = 0; @@ -9480,6 +9485,17 @@ void doEndgame(bool saveHighscore) { conductGameChallenges[CONDUCT_BOOTS_SPEED] = 1; } achievementObserver.updateGlobalStat(STEAM_GSTAT_GAMES_WON); + + if ( allowedCompendiumProgression ) + { + for ( int c = 0; c < MAXPLAYERS; c++ ) + { + if ( !client_disconnected[c] ) + { + Compendium_t::Events_t::onVictoryEvent(c); + } + } + } } } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index af199cc5d..3f356e819 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10638,7 +10638,7 @@ std::map Compendium_t::CompendiumItems_t::contentsMap; void Compendium_t::readItemsFromFile() { - const std::string filename = "data/compendium/items.json"; + const std::string filename = "data/compendium/compendium_items.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -10708,6 +10708,31 @@ void Compendium_t::readItemsFromFile() } if ( w.HasMember("events") ) { + std::vector alwaysTrackedEvents = { + "APPRAISED", + "RUNS_COLLECTED" + }; + + for ( auto& s : alwaysTrackedEvents ) + { + auto find = Compendium_t::Events_t::eventIdLookup.find(s); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); + } + } + } + } + } for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) { std::string eventName = itr->GetString(); @@ -10942,7 +10967,8 @@ std::map Compendium_t: std::map Compendium_t::Events_t::eventIdLookup; std::map> Compendium_t::Events_t::itemEventLookup; std::map> Compendium_t::Events_t::eventItemLookup; -std::map> Compendium_t::Events_t::playerEvents[MAXPLAYERS]; +std::map> Compendium_t::Events_t::playerEvents; +std::map> Compendium_t::Events_t::serverPlayerEvents[MAXPLAYERS]; void Compendium_t::Events_t::readEventsFromFile() { @@ -11008,7 +11034,14 @@ void Compendium_t::Events_t::readEventsFromFile() entry.id = index; if ( itr2->value.HasMember("client") ) { - entry.client = itr2->value["client"].GetBool(); + int tmp = itr2->value["client"].GetInt(); + tmp = std::max(0, tmp); + tmp = std::min((int)Compendium_t::Events_t::CLIENT_UPDATETYPE_MAX - 1, tmp); + entry.clienttype = (Compendium_t::Events_t::ClientUpdateType)tmp; + } + if ( itr2->value.HasMember("once_per_run") ) + { + entry.oncePerRun = itr2->value["once_per_run"].GetBool(); } } } @@ -11048,10 +11081,7 @@ void Compendium_t::Events_t::loadItemsSaveData() return; } - for ( int i = 0; i < MAXPLAYERS; ++i ) - { - playerEvents[i].clear(); - } + playerEvents.clear(); for ( auto itr = d["items"].MemberBegin(); itr != d["items"].MemberEnd(); ++itr ) { auto find = eventIdLookup.find(itr->name.GetString()); @@ -11085,7 +11115,7 @@ void Compendium_t::Events_t::writeItemsSaveData() CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(VERSION)); rapidjson::Value itemsObj(rapidjson::kObjectType); - for ( auto& pair : playerEvents[0] ) + for ( auto& pair : playerEvents ) { const std::string& key = events[pair.first].name; rapidjson::Value namekey(key.c_str(), exportDocument.GetAllocator()); @@ -11115,7 +11145,7 @@ void Compendium_t::Events_t::writeItemsSaveData() return; } -void Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) +bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) { if ( type == SUM ) { @@ -11124,16 +11154,68 @@ void Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) { value = 0x7FFFFFFF; } + return true; } else if ( type == MAX ) { + if ( value == val ) + { + return false; + } value = std::max(val, value); + return true; + } +} + +void Compendium_t::Events_t::onVictoryEvent(const int playernum) +{ + +} + +void Compendium_t::Events_t::onLevelChangeEvent(const int playernum) +{ + if ( intro ) { return; } + if ( !players[playernum]->isLocalPlayer() ) { return; } + + if ( multiplayer == SERVER && playernum == 0 ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + sendClientDataOverNet(i); + } } + + ////std::set slots; + //int numItems = 0; + //for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) + //{ + // Item* item = (Item*)node->element; + // if ( !item ) + // { + // continue; + // } + // if ( itemCategory(item) == SPELL_CAT ) + // { + // continue; + // } + // numItems += item->count; + // //if ( item->x >= 0 && item->x < players[playernum]->inventoryUI.getSizeX() ) + // //{ + // // if ( item->y >= 0 && item->y < players[playernum]->inventoryUI.getSizeY() ) + // // { + // // //Uint32 key = item->x + 10000 * item->y; + // // //slots.insert(key); + // // } + // //} + //} + //size_t maxSlots = players[playernum]->inventoryUI.getSizeX() * players[playernum]->inventoryUI.getSizeY(); + //Compendium_t::Events_t::eventUpdate(playernum, Compendium_t::CPDM_INVENTORY_QTY_MAX, CLOAK_BACKPACK, numItems); } -void Compendium_t::Events_t::eventUpdate(const int playernum, const EventTags tag, const ItemType type, - const Sint32 value, const bool forceValue) +void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, const ItemType type, + const Sint32 value, const bool loadingValue) { + if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } if ( multiplayer == SINGLE && playernum != 0 ) { return; } @@ -11145,21 +11227,31 @@ void Compendium_t::Events_t::eventUpdate(const int playernum, const EventTags ta } auto& def = find->second; - if ( def.client ) + if ( !loadingValue ) { - if ( multiplayer != SINGLE ) + if ( def.clienttype == CLIENT_ONLY ) { - if ( playernum != clientnum ) + if ( multiplayer != SINGLE ) { - return; + if ( playernum != clientnum ) + { + return; + } } } - } - else - { - if ( multiplayer == CLIENT ) + else if ( def.clienttype == SERVER_ONLY ) { - return; + if ( multiplayer == CLIENT ) + { + if ( playernum == 0 ) + { + playernum = clientnum; // when a client receives an update from the server + } + else + { + return; + } + } } } @@ -11169,20 +11261,141 @@ void Compendium_t::Events_t::eventUpdate(const int playernum, const EventTags ta return; } - auto& e = playerEvents[(multiplayer == CLIENT) ? 0 : playernum][tag]; + auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; if ( e.find(type) == e.end() ) { e[type] = EventVal_t(tag); } + + if ( def.oncePerRun && !loadingValue ) + { + auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(type); + if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) + { + // already present, skip adding + return; + } + players[playernum]->compendiumProgress.itemEvents[def.name][type] += value; + } auto& val = e[type]; - if ( forceValue ) + if ( loadingValue ) { - val.value = value; + val.value = value; // reading from savefile } else { - val.applyValue(value); - writeItemsSaveData(); + if ( val.applyValue(value) ) + { + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } + } + } +} + +Uint8 Compendium_t::Events_t::clientSequence = 0; +std::map Compendium_t::Events_t::clientDataStrings[MAXPLAYERS]; +std::map> Compendium_t::Events_t::clientReceiveData; +void Compendium_t::Events_t::sendClientDataOverNet(const int playernum) +{ + if ( multiplayer == SERVER ) { + if ( playernum == 0 || client_disconnected[playernum] ) { + return; + } + + if ( serverPlayerEvents[playernum].empty() ) + { + return; + } + + rapidjson::Document d; + d.SetObject(); + CustomHelpers::addMemberToRoot(d, "seq", rapidjson::Value(clientSequence)); + rapidjson::Value data(rapidjson::kObjectType); + for ( auto& p1 : serverPlayerEvents[playernum] ) + { + std::string key = std::to_string(p1.first); + rapidjson::Value namekey(key.c_str(), d.GetAllocator()); + data.AddMember(namekey, rapidjson::Value(rapidjson::kObjectType), d.GetAllocator()); + auto& obj = data[key.c_str()]; + for ( auto& itemsData : p1.second ) + { + rapidjson::Value itemKey(std::to_string(itemsData.first).c_str(), d.GetAllocator()); + obj.AddMember(itemKey, itemsData.second.value, d.GetAllocator()); + } + } + CustomHelpers::addMemberToRoot(d, "item", data); + + rapidjson::StringBuffer os; + rapidjson::Writer writer(os); + d.Accept(writer); + clientDataStrings[playernum][clientSequence] = os.GetString(); + auto& dataStr = clientDataStrings[playernum][clientSequence]; + + const size_t len = dataStr.size(); + if ( len == 0 ) + { + return; + } + + serverPlayerEvents[playernum].clear(); + + // packet header + memset(net_packet->data, 0, NET_PACKET_SIZE); + memcpy(net_packet->data, "CMPD", 4); + Uint8 sequence = 0; + // encode name + int chunksize = 256; + const int numchunks = 1 + (len / chunksize); + for ( int c = 0; c < len; c += chunksize ) + { + sequence += 1; + + if ( c + chunksize > len ) + { + chunksize = len - c; + } + net_packet->data[4] = clientSequence; + net_packet->data[5] = sequence; // chunk index + net_packet->data[6] = numchunks; // num chunks + + std::string substr = dataStr.substr(c, chunksize); + stringCopy((char*)net_packet->data + 7, substr.c_str(), + 256 + 1, substr.size()); + + net_packet->len = 7 + substr.size(); + + net_packet->address.host = net_clients[playernum - 1].host; + net_packet->address.port = net_clients[playernum - 1].port; + sendPacketSafe(net_sock, -1, net_packet, playernum - 1); + } + + ++clientSequence; + if ( clientSequence >= 255 ) + { + clientSequence = 0; + } + + //else + //{ + // // packet header + // memcpy(net_packet->data, "CSCN", 4); + // net_packet->data[4] = 0; + // net_packet->len = 5; + // for ( int i = 1; i < MAXPLAYERS; i++ ) { + // if ( client_disconnected[i] ) { + // continue; + // } + // if ( playernum != i ) + // { + // continue; + // } + // net_packet->address.host = net_clients[i - 1].host; + // net_packet->address.port = net_clients[i - 1].port; + // sendPacketSafe(net_sock, -1, net_packet, i - 1); + // } + //} } } \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index ad9b8a646..0d9f17780 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3526,39 +3526,50 @@ struct Compendium_t enum EventTags { - CPDM_HOLD_TIME = 0, - CPDM_SCRAPPED, - CPDM_SHIELD_REFLECT, CPDM_BLOCKED_ATTACKS, - CPDM_BLESSED_TOTAL, - CPDM_INSPIRATION_XP, - CPDM_BLINDFOLDED_TIME, - CPDM_MASKED_ROBBERIES, - CPDM_CARRY_WGT_MAX, - CPDM_BROKEN, - CPDM_VICTORIES_WITH, CPDM_BROKEN_BY_BLOCKING, - CPDM_PWR_MAX, - CPDM_ROSES_OFFERED, - CPDM_GOLD_MAX_NULLIFIED, - CPDM_BLESSED_MAX_DOUBLET, - CPDM_PREFERRED_CLASS, + CPDM_BROKEN, CPDM_BLESSED_MAX, + CPDM_ATTACKS, + CPDM_THROWN, + CPDM_SHOTS_FIRED, + CPDM_AMMO_FIRED, + CPDM_CONSUMED, + CPDM_TOWEL_USES, + CPDM_PICKAXE_WALLS_DUG, + CPDM_SINKS_TAPPED, + CPDM_ALEMBIC_BREWED, + CPDM_TINKERKIT_CRAFTS, CPDM_TORCH_WALLS, - CPDM_SHIELD_REFLECT_KILLS, - CPDM_BLESSED_MAX_CLOAK, - CPDM_INVENTORY_SLOTS_AVG, - CPDM_COLLECTED_RUN, - CPDM_TORCH_BURNT_OUT, - CPDM_STEALTH_ATTACKS_WITH, + CPDM_SHIELD_REFLECT, + CPDM_BLESSED_TOTAL, + CPDM_DMG_MAX, + CPDM_TRADING_GOLD_EARNED, + CPDM_FOUNTAINS_TAPPED, + CPDM_ALEMBIC_DECANTED, + CPDM_TINKERKIT_REPAIRS, CPDM_MIRROR_TELEPORTS, CPDM_BLOCKED_HIGHEST_DMG, - CPDM_BOULDER_DMG_BLOCKED, - CPDM_HUNGER_PACIFIED, - CPDM_DISORIENT_EFFECT_APPLIED, + CPDM_TRADING_SOLD, + CPDM_DMG_0, + CPDM_BOTTLE_FROM_BREWING, + CPDM_ALEMBIC_DUPLICATED, + CPDM_TINKERKIT_METAL_SCRAPPED, + CPDM_INSPIRATION_XP, CPDM_CLOAK_BURNED, - CPDM_INVENTORY_QTY_MAX, - CPDM_DEATHS_WEARING, + CPDM_BOTTLES_FROM_CONSUME, + CPDM_ALEMBIC_BROKEN, + CPDM_TINKERKIT_MAGIC_SCRAPPED, + CPDM_TINKERKIT_SALVAGED, + CPDM_APPRAISED, + CPDM_TOWEL_BLEEDING, + CPDM_TOWEL_MESSY, + CPDM_TOWEL_GREASY, + CPDM_RUNS_COLLECTED, + CPDM_ATTACKS_MISSES, + CPDM_THROWN_HITS, + CPDM_SHOTS_HIT, + CPDM_AMMO_HIT, CPDM_EVENT_TAGS_MAX }; @@ -3570,10 +3581,18 @@ struct Compendium_t MAX, AVERAGE_RANGE }; + enum ClientUpdateType + { + SERVER_ONLY, + CLIENT_ONLY, + CLIENT_AND_SERVER, + CLIENT_UPDATETYPE_MAX + }; struct Event_t { Type type = SUM; - bool client = true; + bool oncePerRun = false; + ClientUpdateType clienttype = CLIENT_ONLY; std::string name = ""; int id = CPDM_EVENT_TAGS_MAX; }; @@ -3582,7 +3601,7 @@ struct Compendium_t Type type = SUM; int id = CPDM_EVENT_TAGS_MAX; Sint32 value = 0; - void applyValue(const Sint32 val); + bool applyValue(const Sint32 val); EventVal_t() = default; EventVal_t(EventTags tag) { @@ -3599,8 +3618,15 @@ struct Compendium_t static void readEventsFromFile(); static void writeItemsSaveData(); static void loadItemsSaveData(); - static void eventUpdate(const int playernum, const EventTags tag, const ItemType type, const Sint32 val, const bool forceValue = false); - static std::map> playerEvents[MAXPLAYERS]; + static void eventUpdate(int playernum, const EventTags tag, const ItemType type, const Sint32 val, const bool loadingValue = false); + static std::map> playerEvents; + static std::map> serverPlayerEvents[MAXPLAYERS]; + static void onLevelChangeEvent(const int playernum); + static void onVictoryEvent(const int playernum); + static void sendClientDataOverNet(const int playernum); + static std::map clientDataStrings[MAXPLAYERS]; + static std::map> clientReceiveData; // todo clean up on end + static Uint8 clientSequence; }; }; diff --git a/src/net.cpp b/src/net.cpp index 9c31baa78..83d0e5f75 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2304,6 +2304,8 @@ static void changeLevel() { Player::Minimap_t::mapDetails.push_back(std::make_pair("map_flag_disable_hunger", "")); } + Compendium_t::Events_t::onLevelChangeEvent(clientnum); + if ( gameModeManager.allowsSaves() ) { saveGame(); @@ -3780,6 +3782,10 @@ static std::unordered_map clientPacketHandlers = { { nextnode = node->next; Item* item = (Item*)node->element; + if ( itemCategory(item) == SPELL_CAT ) + { + continue; + } if ( item->type == ARTIFACT_ORB_PURPLE ) { strcpy((char*)net_packet->data, "DIEI"); @@ -4330,42 +4336,52 @@ static std::unordered_map clientPacketHandlers = { if ( stats[clientnum]->helmet ) { stats[clientnum]->helmet->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->helmet->type, 1); } if ( stats[clientnum]->breastplate ) { stats[clientnum]->breastplate->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->breastplate->type, 1); } if ( stats[clientnum]->gloves ) { stats[clientnum]->gloves->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->gloves->type, 1); } if ( stats[clientnum]->shoes ) { stats[clientnum]->shoes->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->shoes->type, 1); } if ( stats[clientnum]->shield ) { stats[clientnum]->shield->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->shield->type, 1); } if ( stats[clientnum]->weapon ) { stats[clientnum]->weapon->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->weapon->type, 1); } if ( stats[clientnum]->cloak ) { stats[clientnum]->cloak->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->cloak->type, 1); } if ( stats[clientnum]->amulet ) { stats[clientnum]->amulet->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->amulet->type, 1); } if ( stats[clientnum]->ring ) { stats[clientnum]->ring->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->ring->type, 1); } if ( stats[clientnum]->mask ) { stats[clientnum]->mask->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->mask->type, 1); } }}, @@ -4378,60 +4394,70 @@ static std::unordered_map clientPacketHandlers = { if ( stats[clientnum]->helmet ) { stats[clientnum]->helmet->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->helmet->type, 1); } break; case 1: if ( stats[clientnum]->breastplate ) { stats[clientnum]->breastplate->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->breastplate->type, 1); } break; case 2: if ( stats[clientnum]->gloves ) { stats[clientnum]->gloves->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->gloves->type, 1); } break; case 3: if ( stats[clientnum]->shoes ) { stats[clientnum]->shoes->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->shoes->type, 1); } break; case 4: if ( stats[clientnum]->shield ) { stats[clientnum]->shield->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->shield->type, 1); } break; case 5: if ( stats[clientnum]->weapon ) { stats[clientnum]->weapon->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->weapon->type, 1); } break; case 6: if ( stats[clientnum]->cloak ) { stats[clientnum]->cloak->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->cloak->type, 1); } break; case 7: if ( stats[clientnum]->amulet ) { stats[clientnum]->amulet->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->amulet->type, 1); } break; case 8: if ( stats[clientnum]->ring ) { stats[clientnum]->ring->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->ring->type, 1); } break; case 9: if ( stats[clientnum]->mask ) { stats[clientnum]->mask->beatitude++; + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_TOTAL, stats[clientnum]->mask->type, 1); } break; default: @@ -5034,6 +5060,67 @@ static std::unordered_map clientPacketHandlers = { { achievementObserver.playerAchievements[player].wearingBountyHat = net_packet->data[5] > 0 ? true : false; } + } }, + + // compendium data update + { 'CMPD', []() { + Uint8 clientSequence = net_packet->data[4]; + int sequence = net_packet->data[5]; + int numchunks = net_packet->data[6]; + if ( numchunks == 0 ) + { + return; + } + char buf[512]; + stringCopy(buf, (char*)(&net_packet->data[7]), sizeof(buf), std::max(0, (int)net_packet->len - 7)); + Compendium_t::Events_t::clientReceiveData[clientSequence][sequence] = buf; + if ( (int)Compendium_t::Events_t::clientReceiveData[clientSequence].size() == numchunks ) + { + std::string str = ""; + for ( int i = 1; i <= numchunks; ++i ) + { + str += Compendium_t::Events_t::clientReceiveData[clientSequence][i]; + } + + rapidjson::Document d; + d.Parse(str.c_str()); + if ( !d.HasParseError() ) + { + if ( d.HasMember("seq") && d.HasMember("item") ) + { + if ( d["seq"].GetInt() == clientSequence ) + { + for ( auto itr = d["item"].MemberBegin(); itr != d["item"].MemberEnd(); ++itr ) + { + int id = std::stoi(itr->name.GetString()); + if ( id >= 0 && id < Compendium_t::EventTags::CPDM_EVENT_TAGS_MAX ) + { + for ( auto itr2 = itr->value.MemberBegin(); itr2 != itr->value.MemberEnd(); ++itr2 ) + { + int itemType = std::stoi(itr2->name.GetString()); + if ( itemType < 0 || itemType >= NUMITEMS ) + { + continue; + } + Sint32 value = itr2->value.GetInt(); + Compendium_t::Events_t::eventUpdate(0, (Compendium_t::EventTags)id, (ItemType)itemType, value); + } + } + } + } + } + } + Compendium_t::Events_t::clientReceiveData.erase(clientSequence); + + // reply got packet + strcpy((char*)net_packet->data, "CMPD"); + net_packet->data[4] = clientnum; + net_packet->data[5] = clientSequence; + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 7; + sendPacketSafe(net_sock, -1, net_packet, 0); + } }} }; @@ -6871,7 +6958,19 @@ static std::unordered_map serverPacketHandlers = { } playSoundEntity(players[player]->entity, 162, 64); } - }}, + } }, + + { 'CMPD', []() { + // client ack received the packet + const int player = std::min(net_packet->data[4], (Uint8)(MAXPLAYERS - 1)); + const Uint8 clientSequence = net_packet->data[5]; + + auto find = Compendium_t::Events_t::clientDataStrings[player].find(clientSequence); + if ( find != Compendium_t::Events_t::clientDataStrings[player].end() ) + { + Compendium_t::Events_t::clientDataStrings[player].erase(clientSequence); + } + }} }; void serverHandlePacket() diff --git a/src/player.cpp b/src/player.cpp index 497e9478e..da83faa17 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3077,7 +3077,8 @@ Player::Player(int in_playernum, bool in_local_host) : signGUI(*this), paperDoll(*this), minimap(*this), - shopGUI(*this) + shopGUI(*this), + compendiumProgress(*this) { local_host = false; playernum = in_playernum; diff --git a/src/player.hpp b/src/player.hpp index 087d7dcfa..c958d4191 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2264,6 +2264,16 @@ class Player static real_t compact2pVerticalBigScale; } minimap; + class CompendiumProgress_t + { + Player& player; + public: + std::map> itemEvents; + CompendiumProgress_t(Player& p) : player(p) + {}; + ~CompendiumProgress_t() {}; + } compendiumProgress; + static void soundMovement(); static void soundActivate(); static void soundCancel(); diff --git a/src/scores.cpp b/src/scores.cpp index 3f6da0833..b056e8432 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -6055,6 +6055,16 @@ int SaveGameInfo::populateFromSession(const int playernum) h2.second.numAccessories = h.second.numAccessories; } + for ( auto& pair : ::players[c]->compendiumProgress.itemEvents ) + { + player.compendium_item_events.push_back(std::make_pair(pair.first, std::vector>())); + auto& vec_entry = player.compendium_item_events.back(); + for ( auto& itemValue : pair.second ) + { + vec_entry.second.push_back(itemValue); + } + } + for ( auto& loot : stats[c]->player_lootbags ) { player.stats.player_lootbags.push_back(std::make_pair(loot.first, @@ -6240,6 +6250,7 @@ int SaveGameInfo::populateFromSession(const int playernum) info->additional_data.push_back(std::make_pair("game_scenario", gameModeManager.currentSession.challengeRun.scenarioStr)); } + if ( info->game_version >= 410 ) { info->computeHash(info->player_num, info->hash); @@ -6893,6 +6904,18 @@ int loadGame(int player, const SaveGameInfo& info) { } } + // compendium progress + { + auto& compendiumProgress = players[statsPlayer]->compendiumProgress; + for ( auto& compendium_item_events : info.players[player].compendium_item_events ) + { + for ( auto& itemValues : compendium_item_events.second ) + { + compendiumProgress.itemEvents[compendium_item_events.first][(ItemType)itemValues.first] = (Sint32)itemValues.second; + } + } + } + Player::Minimap_t::mapDetails = info.map_messages; if ( !info.hiscore_dummy_loading ) diff --git a/src/scores.hpp b/src/scores.hpp index f8e845e37..fbd8b2a0b 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -422,6 +422,7 @@ struct SaveGameInfo { } }; std::vector> shopkeeperHostility; + std::vector>>> compendium_item_events; struct stat_t { struct item_t { @@ -604,6 +605,7 @@ struct SaveGameInfo { fp->property("followers", followers); fp->property("game_statistics", gameStatistics); fp->property("shopkeeper_hostility", shopkeeperHostility); + fp->property("compendium_item_events", compendium_item_events); return true; } diff --git a/src/shops.cpp b/src/shops.cpp index 8f788a2f4..fb336ecf7 100644 --- a/src/shops.cpp +++ b/src/shops.cpp @@ -20,6 +20,7 @@ #include "player.hpp" #include "scores.hpp" #include "prng.hpp" +#include "mod_tools.hpp" list_t* shopInv[MAXPLAYERS] = { nullptr }; Uint32 shopkeeper[MAXPLAYERS] = { 0 }; @@ -605,6 +606,12 @@ bool sellItemToShop(const int player, Item* item) shopChangeGoldEvent(player, item->sellValue(player)); stats[player]->GOLD += item->sellValue(player); + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TRADING_GOLD_EARNED, item->type, item->sellValue(player)); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TRADING_SOLD, item->type, 1); + } + if ( multiplayer != CLIENT ) { Entity* entity = uidToEntity(shopkeeper[player]); From 0a8f4981c4007e3c975aac9a64e0c1df8251f1d9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 8 Jun 2024 02:00:17 +1000 Subject: [PATCH 016/244] * compendium world events, refactor getSpellFromItem --- src/actarrow.cpp | 20 + src/actarrowtrap.cpp | 1 + src/actboulder.cpp | 58 +- src/actchest.cpp | 3 + src/actdoor.cpp | 5 + src/actfountain.cpp | 10 + src/actgeneral.cpp | 1 + src/actheadstone.cpp | 21 +- src/actitem.cpp | 24 + src/actmagictrap.cpp | 10 + src/actplayer.cpp | 52 + src/actsink.cpp | 12 +- src/actspeartrap.cpp | 21 + src/charclass.cpp | 8 +- src/draw.cpp | 5 + src/draw.hpp | 2 +- src/entity.cpp | 68 +- src/game.cpp | 34 +- src/init.cpp | 2 +- src/init_game.cpp | 10 +- src/interface/consolecommand.cpp | 2 + src/interface/drawstatus.cpp | 8 +- src/interface/interface.cpp | 21 +- src/interface/playerinventory.cpp | 58 +- src/item_tool.cpp | 5 + src/item_usage_funcs.cpp | 8 +- src/items.cpp | 4 +- src/magic/actmagic.cpp | 60 ++ src/magic/castSpell.cpp | 20 + src/magic/magic.hpp | 2 +- src/magic/spell.cpp | 35 +- src/mechanisms.cpp | 6 +- src/menu.cpp | 44 +- src/mod_tools.cpp | 1530 +++++++++++++++++++++++++++-- src/mod_tools.hpp | 136 ++- src/monster_mimic.cpp | 9 + src/monster_minotaur.cpp | 8 + src/monster_shadow.cpp | 2 +- src/monster_succubus.cpp | 8 +- src/net.cpp | 38 +- src/opengl.cpp | 57 +- src/player.cpp | 2 +- src/player.hpp | 2 +- src/scores.cpp | 16 +- src/scores.hpp | 2 +- src/shops.cpp | 4 + src/ui/GameUI.cpp | 95 +- src/ui/GameUI.hpp | 2 +- src/ui/MainMenu.cpp | 649 ++++++++++-- 49 files changed, 2915 insertions(+), 285 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index 0953be2da..7a6b966c8 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -649,6 +649,7 @@ void actArrow(Entity* my) /*messagePlayer(0, "My damage: %d, AC: %d, Pierce: %d", my->arrowPower, AC(hitstats), my->arrowArmorPierce); messagePlayer(0, "Resolved to %d damage.", damage);*/ + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); // write obituary if ( parent ) @@ -657,6 +658,25 @@ void actArrow(Entity* my) { hit.entity->setObituary(Language::get(1503)); hitstats->killer = KilledBy::TRAP_ARROW; + + if ( oldHP > hitstats->HP ) + { + if ( hit.entity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(hit.entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "arrow trap", oldHP - hitstats->HP); + if ( hitstats->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(hit.entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "arrow trap", 1); + } + } + else if ( hit.entity->behavior == &actMonster ) + { + if ( auto leader = hit.entity->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdateWorld(hit.entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "arrow trap", 1); + } + } + } } else { diff --git a/src/actarrowtrap.cpp b/src/actarrowtrap.cpp index 950c0989c..1aadae3a7 100644 --- a/src/actarrowtrap.cpp +++ b/src/actarrowtrap.cpp @@ -60,6 +60,7 @@ void actArrowTrap(Entity* my) { ItemType quiver = static_cast(ARROWTRAP_TYPE); int qty = 2 + (5 - ARROWTRAP_FIRED / 2); // 2 to 7 + Compendium_t::Events_t::eventUpdateWorld(ARROWTRAP_DISABLED - 1, Compendium_t::CPDM_ARROWS_PILFERED, "arrow trap", qty); Entity* dropped = dropItemMonster(newItem(quiver, SERVICABLE, 0, qty, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr), my, nullptr, qty); std::vector> freeTiles; int x = my->x / 16; diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 2bd0d4bd2..f991bd265 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -327,24 +327,39 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit } } + Sint32 oldHP = stats->HP; if ( my->sprite == BOULDER_LAVA_SPRITE ) { entity->modHP(-damage); + if ( entity->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "brimstone boulder", oldHP - stats->HP); + } entity->setObituary(Language::get(3898)); stats->killer = KilledBy::BOULDER; } else if ( my->sprite == BOULDER_ARCANE_SPRITE ) { entity->modHP(-damage); + if ( entity->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP); + } entity->setObituary(Language::get(3899)); stats->killer = KilledBy::BOULDER; } else { entity->modHP(-damage); + if ( entity->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "boulder trap", oldHP - stats->HP); + } entity->setObituary(Language::get(1505)); stats->killer = KilledBy::BOULDER; } + + bool lifeSaving = (stats->HP <= 0 && stats->amulet && stats->amulet->type == AMULET_LIFESAVING); if ( entity->behavior == &actPlayer ) { if ( stats->HP <= 0 ) @@ -358,11 +373,19 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit { steamAchievementClient(BOULDER_PLAYERPUSHED, "BARONY_ACH_MOVED_ITSELF"); } + + if ( my->sprite == BOULDER_LAVA_SPRITE ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "brimstone boulder", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "boulder trap", 1); + } achievementObserver.updateGlobalStat(STEAM_GSTAT_BOULDER_DEATHS); } } - bool lifeSaving = (stats->HP <= 0 && stats->amulet && stats->amulet->type == AMULET_LIFESAVING); if ( !lifeSaving ) { if ( stats->HP <= 0 && entity->behavior == &actPlayer @@ -497,10 +520,17 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit } else { - if ( stats->type == GYROBOT ) + if ( Entity* leader = entity->monsterAllyGetPlayerLeader() ) { - Entity* leader = entity->monsterAllyGetPlayerLeader(); - if ( leader ) + if ( my->sprite == BOULDER_LAVA_SPRITE ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "brimstone boulder", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "boulder trap", 1); + } + if ( stats->type == GYROBOT ) { real_t tangent = atan2(leader->y - entity->y, leader->x - entity->x); Entity* ohitentity = hit.entity; @@ -1291,6 +1321,8 @@ void actBoulder(Entity* my) inputs.addRumbleRemotePlayer(i, Inputs::HAPTIC_SFX_BOULDER_ROLL_LOW_VOL, my->getUID()); } } + + Compendium_t::Events_t::eventUpdateWorld(BOULDER_SOUND_ON_PUSH - 1, Compendium_t::CPDM_BOULDERS_PUSHED, "boulder trap", 1); BOULDER_SOUND_ON_PUSH = 0; } } @@ -1995,13 +2027,31 @@ void boulderSokobanOnDestroy(bool pushedOffLedge) } } //messagePlayer(0, "Solved it!"); + Uint32 playerAliveTicks = 0; for ( int c = 0; c < MAXPLAYERS; c++ ) { + if ( players[c] && players[c]->entity ) + { + playerAliveTicks = std::min((Uint32)0x7FFFFFFF, players[c]->entity->ticks); + break; + } + } + for ( int c = 0; c < MAXPLAYERS; c++ ) + { + if ( playerAliveTicks > 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_SOKOBAN_SOLVES, "sokoban", 1); + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_SOKOBAN_FASTEST_SOLVE, "sokoban", playerAliveTicks); + } Uint32 color = makeColorRGB(255, 128, 0); if ( goldCount >= 39 ) { playSoundPlayer(c, 393, 128); messagePlayerColor(c, MESSAGE_HINT, color, Language::get(2969)); + if ( playerAliveTicks > 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_SOKOBAN_PERFECT_SOLVES, "sokoban", 1); + } } else { diff --git a/src/actchest.cpp b/src/actchest.cpp index b51c438b0..b90e2e54f 100644 --- a/src/actchest.cpp +++ b/src/actchest.cpp @@ -817,6 +817,8 @@ void Entity::actChest() messagePlayer(chestclicked, MESSAGE_INTERACTION, Language::get(459)); openedChest[chestclicked] = this; + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_CHESTS_OPENED, "chest", 1); + chestOpener = chestclicked; if ( !players[chestclicked]->isLocalPlayer() && multiplayer == SERVER) { @@ -1638,6 +1640,7 @@ void Entity::chestHandleDamageMagic(int damage, Entity &magicProjectile, Entity { messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2520)); } + Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_CHESTS_DESTROYED, "chest", 1); } else { diff --git a/src/actdoor.cpp b/src/actdoor.cpp index a3f52164a..73a0f551c 100644 --- a/src/actdoor.cpp +++ b/src/actdoor.cpp @@ -20,6 +20,7 @@ #include "interface/interface.hpp" #include "items.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -150,6 +151,7 @@ void actDoor(Entity* my) my->doorStatus = 1 + (playerEntity->x > my->x); playSoundEntity(my, 21, 96); messagePlayer(i, MESSAGE_INTERACTION, Language::get(464)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_OPENED, "door", 1); } else if ( my->doorDir && !my->doorStatus ) { @@ -157,6 +159,7 @@ void actDoor(Entity* my) my->doorStatus = 1 + (playerEntity->y < my->y); playSoundEntity(my, 21, 96); messagePlayer(i, MESSAGE_INTERACTION, Language::get(464)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_OPENED, "door", 1); } else { @@ -164,6 +167,7 @@ void actDoor(Entity* my) my->doorStatus = 0; playSoundEntity(my, 22, 96); messagePlayer(i, MESSAGE_INTERACTION, Language::get(465)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DOOR_CLOSED, "door", 1); } } else @@ -342,6 +346,7 @@ void Entity::doorHandleDamageMagic(int damage, Entity &magicProjectile, Entity * { messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(387)); } + Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_DOOR_BROKEN, "door", 1); } else { diff --git a/src/actfountain.cpp b/src/actfountain.cpp index 28f9558ca..fff195b74 100644 --- a/src/actfountain.cpp +++ b/src/actfountain.cpp @@ -232,11 +232,13 @@ void actFountain(Entity* my) } } } + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_USED, "fountain", 1); switch (my->skill[1]) { case 0: { playSoundEntity(players[i]->entity, 52, 64); + //Spawn succubus. Uint32 color = makeColorRGB(255, 128, 0); Entity* spawnedMonster = nullptr; @@ -285,6 +287,7 @@ void actFountain(Entity* my) { messagePlayerColor(i, MESSAGE_INTERACTION, color, Language::get(469)); } + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_FOOCUBI, "fountain", 1); } } else if ( currentlevel < 10 ) @@ -308,6 +311,7 @@ void actFountain(Entity* my) strcpy(tmpStats->name, "lesser incubus"); } messagePlayerColor(i, MESSAGE_INTERACTION, color, Language::get(2519)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_FOOCUBI, "fountain", 1); } } else @@ -316,6 +320,7 @@ void actFountain(Entity* my) { spawnedMonster->seedEntityRNG(rng.getU32()); messagePlayerColor(i, MESSAGE_INTERACTION, color, Language::get(469)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_FOOCUBI, "fountain", 1); } } } @@ -325,11 +330,13 @@ void actFountain(Entity* my) { spawnedMonster->seedEntityRNG(rng.getU32()); messagePlayerColor(i, MESSAGE_INTERACTION, color, Language::get(2519)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_FOOCUBI, "fountain", 1); } } break; } case 1: + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_DRUNK, "fountain", 1); if ( stats[i] && stats[i]->type != VAMPIRE ) { messagePlayer(i, MESSAGE_INTERACTION, Language::get(470)); @@ -372,6 +379,7 @@ void actFountain(Entity* my) case 2: { //Potion effect. Potion effect is stored in my->skill[3], randomly chosen when the fountain is created. + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_DRUNK, "fountain", 1); messagePlayer(i, MESSAGE_INTERACTION, Language::get(470)); Item* item = newItem(static_cast(POTION_WATER + my->skill[3]), static_cast(4), 0, 1, 0, false, NULL); useItem(item, i, my); @@ -387,6 +395,7 @@ void actFountain(Entity* my) messagePlayerColor(i, MESSAGE_STATUS, textcolor, Language::get(471)); messagePlayerColor(i, MESSAGE_STATUS, textcolor, Language::get(473)); bool stuckOnYouSuccess = false; + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_BLESS_ALL, "fountain", 1); if ( !stats[i] ) { @@ -560,6 +569,7 @@ void actFountain(Entity* my) if ( items.size() ) { messagePlayerColor(i, MESSAGE_STATUS, textcolor, Language::get(2592)); //"The fountain blesses a piece of equipment" + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_FOUNTAIN_BLESS, "fountain", 1); //Randomly choose a piece of equipment. std::pair chosen = items[rng.rand()%items.size()]; if ( chosen.first->beatitude == 0 ) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 266ddf285..24d17196e 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -795,6 +795,7 @@ void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Enti { messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(getColliderLangName())); } + Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); } else { diff --git a/src/actheadstone.cpp b/src/actheadstone.cpp index 74fbaa1b0..49aeb05a3 100644 --- a/src/actheadstone.cpp +++ b/src/actheadstone.cpp @@ -19,6 +19,7 @@ #include "collision.hpp" #include "player.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -100,10 +101,10 @@ void actHeadstone(Entity* my) bool shouldspawn = false; // rightclick message - int i; + int triggeredPlayer = -1; if ( multiplayer != CLIENT ) { - for (i = 0; i < MAXPLAYERS; i++) + for (int i = 0; i < MAXPLAYERS; i++) { if ( selectedEntity[i] == my || client_selected[i] == my ) { @@ -114,6 +115,11 @@ void actHeadstone(Entity* my) Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_GRAVE, Language::get(485 + HEADSTONE_MESSAGE % 17)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GRAVE_EPITAPHS_READ, "gravestone", 1); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GRAVE_EPITAPHS_PERCENT, "gravestone", (1 << HEADSTONE_MESSAGE)); + + triggeredPlayer = i; + if ( HEADSTONE_GHOUL && !HEADSTONE_FIRED ) { shouldspawn = true; @@ -146,6 +152,17 @@ void actHeadstone(Entity* my) { strcpy(tmpStats->name, "enslaved ghoul"); } + if ( triggeredPlayer >= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(triggeredPlayer, Compendium_t::CPDM_GRAVE_GHOULS_ENSLAVED, "gravestone", 1); + } + } + else + { + if ( triggeredPlayer >= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(triggeredPlayer, Compendium_t::CPDM_GRAVE_GHOULS, "gravestone", 1); + } } } } diff --git a/src/actitem.cpp b/src/actitem.cpp index 46d20fc89..890153fb6 100644 --- a/src/actitem.cpp +++ b/src/actitem.cpp @@ -21,6 +21,7 @@ #include "scores.hpp" #include "paths.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -267,6 +268,7 @@ void actItem(Entity* my) { if ( inrange[i] && players[i] && players[i]->ghost.isActive() ) { + Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_GHOST_PUSHES, players[i]->ghost.my, 1); my->vel_x += 1.0 * cos(players[i]->ghost.my->yaw); my->vel_y += 1.0 * sin(players[i]->ghost.my->yaw); my->z = std::max(my->z - 0.1, 0.0); @@ -684,6 +686,28 @@ void actItem(Entity* my) // falling out of the map (or burning in a pit of lava) if ( (my->flags[BURNING] && my->z > 12) || my->z > 128 ) { + if ( multiplayer != CLIENT ) + { + int playerOwner = achievementObserver.checkUidIsFromPlayer(my->itemOriginalOwner); + if ( (my->flags[BURNING] && my->z > 12) ) + { + if ( playerOwner >= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_LAVA_ITEMS_BURNT, "lava", 1); + } + } + else if ( my->z > 128 ) + { + if ( playerOwner >= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_PITS_ITEMS_LOST, "pits", 1); + if ( ITEM_TYPE >= 0 && ITEM_TYPE < NUMITEMS ) + { + Compendium_t::Events_t::eventUpdateWorld(playerOwner, Compendium_t::CPDM_PITS_ITEMS_VALUE_LOST, "pits", items[ITEM_TYPE].value); + } + } + } + } if ( ITEM_TYPE == ARTIFACT_MACE && my->parent != 0 ) { steamAchievementEntity(uidToEntity(my->parent), "BARONY_ACH_STFU"); diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp index af9a23d57..986854e52 100644 --- a/src/actmagictrap.cpp +++ b/src/actmagictrap.cpp @@ -21,6 +21,7 @@ #include "player.hpp" #include "magic/magic.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -365,6 +366,12 @@ void Entity::actTeleportShrine() { playSoundEntity(this, 252, 128); //messagePlayer(i, MESSAGE_INTERACTION, Language::get(4301)); + + if ( auto leader = monsterInteracting->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdateWorld(leader->skill[2], Compendium_t::CPDM_OBELISK_FOLLOWER_USES, "obelisk", 1); + } + Entity* spellTimer = createParticleTimer(this, 200, 625); spellTimer->particleTimerPreDelay = 0; // wait x ticks before animation. spellTimer->particleTimerEndAction = PARTICLE_EFFECT_SHRINE_TELEPORT; // teleport behavior of timer. @@ -432,6 +439,9 @@ void Entity::actTeleportShrine() { playSoundEntity(this, 252, 128); messagePlayer(i, MESSAGE_INTERACTION, Language::get(4301)); + + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_OBELISK_USES, "obelisk", 1); + Entity* spellTimer = createParticleTimer(this, 200, 625); spellTimer->particleTimerPreDelay = 0; // wait x ticks before animation. spellTimer->particleTimerEndAction = PARTICLE_EFFECT_SHRINE_TELEPORT; // teleport behavior of timer. diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 4c0a4d277..2a5447fc0 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -1236,6 +1236,7 @@ Entity* Player::Ghost_t::spawnGhost() } players[player.playernum]->ghost.my = entity; players[player.playernum]->ghost.uid = entity->getUID(); + Compendium_t::Events_t::eventUpdateMonster(player.playernum, Compendium_t::CPDM_GHOST_SPAWNED, entity, 1); return entity; } @@ -6120,6 +6121,15 @@ void actPlayer(Entity* my) { my->z -= 1; // floating insectoidLevitating = (playerRace == INSECTOID && stats[PLAYER_NUM]->EFFECTS[EFF_FLUTTER]); + if ( players[PLAYER_NUM]->isLocalPlayer() ) + { + int x = std::min(std::max(1, my->x / 16), map.width - 2); + int y = std::min(std::max(1, my->y / 16), map.height - 2); + if ( !map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_PITS_LEVITATED, "pits", 1); + } + } } if ( !levitating && prevlevitating ) @@ -6220,6 +6230,10 @@ void actPlayer(Entity* my) { my->setObituary(Language::get(3010)); // fell to their death. stats[PLAYER_NUM]->killer = KilledBy::BOTTOMLESS_PIT; + if ( multiplayer != CLIENT && stats[PLAYER_NUM]->HP > 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_PITS_DEATHS, "pits", 1); + } stats[PLAYER_NUM]->HP = 0; // kill me instantly if (stats[PLAYER_NUM]->type == AUTOMATON) { @@ -6231,6 +6245,10 @@ void actPlayer(Entity* my) { my->setObituary(Language::get(3010)); // fell to their death. stats[PLAYER_NUM]->killer = KilledBy::BOTTOMLESS_PIT; + if ( multiplayer != CLIENT && stats[PLAYER_NUM]->HP > 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_PITS_DEATHS, "pits", 1); + } stats[PLAYER_NUM]->HP = 0; // kill me instantly if (stats[PLAYER_NUM]->type == AUTOMATON) { @@ -6319,6 +6337,19 @@ void actPlayer(Entity* my) playSound(136, 128); } } + + if ( players[PLAYER_NUM]->isLocalPlayer() ) + { + if ( swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] ) + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_SWIM_TIME, "murky water", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_SWIM_TIME, "lava", 1); + } + } + if ( multiplayer != CLIENT ) { // Check if the Player is in Water or Lava @@ -6329,6 +6360,8 @@ void actPlayer(Entity* my) my->flags[BURNING] = false; messagePlayer(PLAYER_NUM, MESSAGE_STATUS, Language::get(574)); // "The water extinguishes the flames!" serverUpdateEntityFlag(my, BURNING); + + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_SWIM_BURN_CURED, "murky water", 1); } if ( stats[PLAYER_NUM]->EFFECTS[EFF_POLYMORPH] ) { @@ -6385,6 +6418,7 @@ void actPlayer(Entity* my) { int damage = (2 + local_rng.rand() % 2); damage = std::max(damage, stats[PLAYER_NUM]->MAXHP / 20); + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_LAVA_DAMAGE, "lava", damage); my->modHP(-damage); if ( stats[PLAYER_NUM]->type == AUTOMATON ) { @@ -7804,6 +7838,24 @@ void actPlayer(Entity* my) deleteMultiplayerSaveGames(); //Will only delete save games if was last player alive. } + if ( players[PLAYER_NUM]->isLocalPlayer() || multiplayer == SERVER ) + { + bool swimming = players[PLAYER_NUM]->movement.isPlayerSwimming(); + if ( swimming ) + { + int x = std::min(std::max(0, floor(my->x / 16)), map.width - 1); + int y = std::min(std::max(0, floor(my->y / 16)), map.height - 1); + if ( swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] ) + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_SWIM_KILLED_WHILE, "murky water", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(PLAYER_NUM, Compendium_t::CPDM_SWIM_KILLED_WHILE, "lava", 1); + } + } + } + assailant[PLAYER_NUM] = false; assailantTimer[PLAYER_NUM] = 0; diff --git a/src/actsink.cpp b/src/actsink.cpp index 2fbe0d4b6..2938d0618 100644 --- a/src/actsink.cpp +++ b/src/actsink.cpp @@ -21,6 +21,7 @@ #include "player.hpp" #include "magic/magic.hpp" #include "prng.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -131,7 +132,7 @@ void actSink(Entity* my) { //playSoundEntity(players[i]->entity, 52, 64); messagePlayer(i, MESSAGE_INTERACTION, Language::get(581)); - + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_USED, "sink", 1); //Randomly choose a ring. //88-99 are rings. //So 12 rings total. @@ -168,13 +169,14 @@ void actSink(Entity* my) itemPickup(i, item); messagePlayer(i, MESSAGE_INTERACTION | MESSAGE_INVENTORY, Language::get(504), item->description()); free(item); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_RINGS, "sink", 1); } break; } case 1: { //playSoundEntity(players[i]->entity, 52, 64); - + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_USED, "sink", 1); // spawn slime Entity* monster = summonMonster(SLIME, my->x, my->y); if ( monster ) @@ -184,6 +186,7 @@ void actSink(Entity* my) messagePlayerColor(i, MESSAGE_HINT, color, Language::get(582)); Stat* monsterStats = monster->getStats(); monsterStats->LVL = 4; + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_SLIMES, "sink", 1); } break; } @@ -193,6 +196,7 @@ void actSink(Entity* my) { break; } + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_USED, "sink", 1); if ( stats[i]->type == AUTOMATON ) { Uint32 color = makeColorRGB(255, 128, 0); @@ -212,10 +216,12 @@ void actSink(Entity* my) serverUpdateHunger(i); } players[i]->entity->modHP(1); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_HEALTH_RESTORED, "sink", 1); } else { players[i]->entity->modHP(-2); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_HEALTH_RESTORED, "sink", -2); playSoundEntity(players[i]->entity, 28, 64); playSoundEntity(players[i]->entity, 249, 128); players[i]->entity->setObituary(Language::get(1533)); @@ -247,6 +253,7 @@ void actSink(Entity* my) { break; } + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_USED, "sink", 1); if ( stats[i]->type == AUTOMATON ) { Uint32 color = makeColorRGB(255, 128, 0); @@ -268,6 +275,7 @@ void actSink(Entity* my) players[i]->entity->modHP(-2); playSoundEntity(players[i]->entity, 249, 128); } + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_HEALTH_RESTORED, "sink", -2); playSoundEntity(players[i]->entity, 28, 64); players[i]->entity->setObituary(Language::get(1533)); stats[i]->killer = KilledBy::SINK; diff --git a/src/actspeartrap.cpp b/src/actspeartrap.cpp index d680a5600..82f1b6782 100644 --- a/src/actspeartrap.cpp +++ b/src/actspeartrap.cpp @@ -168,8 +168,29 @@ void actSpearTrap(Entity* my) { playSoundEntity(entity, 28, 64); spawnGib(entity); + + Sint32 oldHP = stats->HP; entity->modHP(-damage); + if ( oldHP > stats->HP ) + { + if ( entity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "spike trap", oldHP - stats->HP); + if ( stats->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "spike trap", 1); + } + } + else if ( entity->behavior == &actMonster ) + { + if ( auto leader = entity->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, "spike trap", 1); + } + } + } + if ( stats->HP <= 0 ) { if ( stats->type == AUTOMATON && entity->behavior == &actPlayer ) diff --git a/src/charclass.cpp b/src/charclass.cpp index abc6d9c10..7cc27c626 100644 --- a/src/charclass.cpp +++ b/src/charclass.cpp @@ -2947,7 +2947,7 @@ void initClass(const int player) if ( item->type == SPELL_ITEM ) { bool skipSpellRearrange = false; - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( spell && client_classes[player] == CLASS_SHAMAN ) { // don't add shapeshift spells to hotbar. @@ -3059,7 +3059,7 @@ void initShapeshiftHotbar(int player) Item* item = static_cast(node->element); if ( item && item->type == SPELL_ITEM ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, true); if ( spell ) { if ( newSpell && newSpell == spell ) @@ -3252,7 +3252,7 @@ void deinitShapeshiftHotbar(int player) { if ( item->type == SPELL_ITEM && item->appearance >= 1000 ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, true); if ( spell && client_classes[player] == CLASS_SHAMAN ) { // move shapeshift spells out of inventory. @@ -3304,7 +3304,7 @@ bool playerUnlockedShamanSpell(const int player, Item* const item) return false; } - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); int levelRequirement = 0; if ( spell && client_classes[player] == CLASS_SHAMAN ) { diff --git a/src/draw.cpp b/src/draw.cpp index 82244fc33..bcc3e6891 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2125,9 +2125,14 @@ void drawEntities3D(view_t* camera, int mode) } else { if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) { +#ifndef EDITOR static ConsoleVariable cvar_dither_invisibility("/dither_invisibility", 5); dither.value = decrease ? std::max(0, dither.value - 2) : std::min(*cvar_dither_invisibility, dither.value + 1); +#else + dither.value = decrease ? std::max(0, dither.value - 2) : + dither.value + 1; +#endif } else { diff --git a/src/draw.hpp b/src/draw.hpp index 8b562854f..5ac06e591 100644 --- a/src/draw.hpp +++ b/src/draw.hpp @@ -427,7 +427,7 @@ void glDrawSprite(view_t* camera, Entity* entity, int mode); void glDrawWorldUISprite(view_t* camera, Entity* entity, int mode); void glDrawWorldDialogueSprite(view_t* camera, void* worldDialogue, int mode); void glDrawEnemyBarSprite(view_t* camera, int mode, int playerViewport, void* enemyHPBarDetails); -void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode); +void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode, bool useTextAsImgPath = false, bool rotate = false); void glDrawWorld(view_t* camera, int mode); void glEndCamera(view_t* camera, bool useHDR); unsigned int GO_GetPixelU32(int x, int y, view_t& camera); diff --git a/src/entity.cpp b/src/entity.cpp index d79936a5a..d3e04c25d 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -470,7 +470,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli { flags[c] = false; } - if ( entlist == map.entities ) + if ( entlist != nullptr && entlist == map.entities ) { if ( multiplayer != CLIENT || loading ) { @@ -6907,6 +6907,11 @@ void Entity::attack(int pose, int charge, Entity* target) break; } + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_MAGICSTAFF_CASTS, myStats->weapon->type, 1); + } + // magicstaffs deplete themselves for each use bool degradeWeapon = true; if ( myStats->type == SHADOW || myStats->type == LICH_FIRE || myStats->type == LICH_ICE ) @@ -6947,6 +6952,10 @@ void Entity::attack(int pose, int charge, Entity* target) { steamAchievementClient(player, "BARONY_ACH_ONE_MANS_TRASH"); } + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, myStats->weapon->type, 1); + } messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(660)); if ( player >= 0 && players[player]->isLocalPlayer() && client_classes[player] == CLASS_MESMER ) { @@ -6958,7 +6967,7 @@ void Entity::attack(int pose, int charge, Entity* target) Item* item = (Item*)spellnode->element; if ( item && itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( spell && spell->ID == SPELL_CHARM_MONSTER ) { foundCharmSpell = true; @@ -8025,10 +8034,12 @@ void Entity::attack(int pose, int charge, Entity* target) { hit.entity->skill[6] = (y < hit.entity->y); } + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_DOOR_BROKEN, "door", 1); } else if ( hit.entity->behavior == &::actChest ) { messagePlayer(player, MESSAGE_COMBAT, Language::get(671)); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_CHESTS_DESTROYED, "chest", 1); } else if ( mimic ) { @@ -8038,6 +8049,7 @@ void Entity::attack(int pose, int charge, Entity* target) { messagePlayer(player, MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()), Language::get(hit.entity->getColliderLangName())); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); } else if ( hit.entity->behavior == &::actFurniture ) { @@ -11913,6 +11925,11 @@ void Entity::awardXP(Entity* src, bool share, bool root) return; } + if ( src->behavior == &actPlayer && behavior == &actMonster && root ) + { + Compendium_t::Events_t::eventUpdateMonster(src->skill[2], Compendium_t::CPDM_KILLED_BY, this, 1); + } + if ( src->behavior == &actMonster && (src->monsterAllySummonRank != 0 || src->monsterIsTinkeringCreation()) ) @@ -12347,6 +12364,29 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( root ) { + if ( multiplayer == SINGLE ) + { + if ( splitscreen ) + { + Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } + else + { + Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_SOLO, src, 1); + } + } + else + { + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } + } + } + for ( int i = 0; i < MAXPLAYERS; ++i ) { if ( stats[i]->helmet && stats[i]->helmet->type == HAT_BOUNTYHUNTER ) @@ -12429,6 +12469,29 @@ void Entity::awardXP(Entity* src, bool share, bool root) } } killIncrementEvent = true; + + if ( multiplayer == SINGLE ) + { + if ( splitscreen ) + { + Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } + else + { + Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_SOLO, src, 1); + } + } + else + { + Compendium_t::Events_t::eventUpdateMonster(leader->skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } + } + } } } @@ -20844,6 +20907,7 @@ void Entity::handleKnockbackDamage(Stat& myStats, Entity* knockedInto) if ( whoKnockedMe && whoKnockedMe->behavior == &actPlayer ) { steamStatisticUpdateClient(whoKnockedMe->skill[2], STEAM_STAT_TAKE_THIS_OUTSIDE, STEAM_STAT_INT, 1); + Compendium_t::Events_t::eventUpdateWorld(whoKnockedMe->skill[2], Compendium_t::CPDM_DOOR_BROKEN, "door", 1); } knockedInto->doorHealth = 0; // smash doors instantly playSoundEntity(knockedInto, 28, 64); diff --git a/src/game.cpp b/src/game.cpp index e74dd29eb..17721d067 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1987,6 +1987,10 @@ void gameLogic(void) } } + int prevcurrentlevel = currentlevel; + bool prevsecretfloor = secretlevel; + std::string prevmapname = map.name; + bool loadingTheSameFloorAsCurrent = false; if ( skipLevelsOnLoad > 0 ) { @@ -2486,7 +2490,7 @@ void gameLogic(void) for ( c = 0; c < MAXPLAYERS; c++ ) { - Compendium_t::Events_t::onLevelChangeEvent(c); + Compendium_t::Events_t::onLevelChangeEvent(c, prevcurrentlevel, prevsecretfloor, prevmapname); } // save at end of level change @@ -2494,6 +2498,7 @@ void gameLogic(void) { saveGame(); } + Compendium_t::Events_t::writeItemsSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif @@ -2748,7 +2753,17 @@ void gameLogic(void) { if ( item->identified ) { - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + if ( item->type == SPELL_ITEM ) + { + if ( auto spell = getSpellFromItem(player, item, true) ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1, false, spell->ID); + } + } + else + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + } } } @@ -3394,7 +3409,17 @@ void gameLogic(void) { if ( item->identified ) { - Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + if ( item->type == SPELL_ITEM ) + { + if ( auto spell = getSpellFromItem(clientnum, item, true) ) + { + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1, false, spell->ID); + } + } + else + { + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + } } } @@ -7065,6 +7090,8 @@ int main(int argc, char** argv) ImGui_t::render(); #endif #ifndef NINTENDO + Compendium_t::updateTooltip(); + // draw mouse // only draw 1 cursor in the main menu if ( inputs.getVirtualMouse(inputs.getPlayerIDAllowedKeyboard())->draw_cursor ) @@ -7318,6 +7345,7 @@ int main(int argc, char** argv) framesProcResult = doFrames(); DebugStats.t6Messages = std::chrono::high_resolution_clock::now(); ingameHud(); + Compendium_t::updateTooltip(); static ConsoleVariable showConsumeMouseInputs("/debug_consume_mouse", false); if (!framesProcResult.usable) { diff --git a/src/init.cpp b/src/init.cpp index 6f0815d49..5045338ee 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -747,7 +747,7 @@ int initApp(char const * const title, int fullscreen) #endif updateLoadingScreen(80); - + loading_done = true; return 0; }); diff --git a/src/init_game.cpp b/src/init_game.cpp index 26945e7c0..6bde2e97c 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -91,12 +91,16 @@ void initGameDatafiles(bool moddedReload) { EquipmentModelOffsets.readFromFile(monstertypename[c], c); } + setupSpells(); CompendiumEntries.readMonstersFromFile(); - CompendiumEntries.readWorldFromFile(); - CompendiumEntries.readCodexFromFile(); Compendium_t::Events_t::readEventsFromFile(); + CompendiumEntries.readCodexFromFile(); + CompendiumEntries.readWorldFromFile(); CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); + Compendium_t::Events_t::readEventsTranslations(); Compendium_t::Events_t::loadItemsSaveData(); + CompendiumEntries.readMonsterLimbsFromFile(); } void initGameDatafilesAsync(bool moddedReload) @@ -182,7 +186,6 @@ int initGame() // load item types initGameDatafiles(false); - setupSpells(); std::atomic_bool loading_done {false}; auto loading_task = std::async(std::launch::async, [&loading_done](){ @@ -484,6 +487,7 @@ void deinitGame() } UIToastNotificationManager.term(true); + Compendium_t::Events_t::writeItemsSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index b67cf761c..13f172f99 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4877,10 +4877,12 @@ namespace ConsoleCommands { static ConsoleCommand ccmd_reloadcompendiumitems("/reloadcompendiumitems", "reloads compendium entries", []CCMD{ CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); }); static ConsoleCommand ccmd_reloadcompendiumevents("/reloadcompendiumevents", "reloads compendium entries", []CCMD{ Compendium_t::Events_t::readEventsFromFile(); + Compendium_t::Events_t::readEventsTranslations(); }); static ConsoleCommand ccmd_mapdebugfixedmonsters("/mapdebugfixedmonsters", "prints fixed monster spawns", []CCMD{ diff --git a/src/interface/drawstatus.cpp b/src/interface/drawstatus.cpp index cafad7001..62878ff1a 100644 --- a/src/interface/drawstatus.cpp +++ b/src/interface/drawstatus.cpp @@ -1186,7 +1186,7 @@ void drawStatus(int player) } else { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( players[player]->magic.selectedSpell() == spell && (players[player]->magic.selected_spell_last_appearance == item->appearance || players[player]->magic.selected_spell_last_appearance == -1 ) ) { @@ -1329,7 +1329,7 @@ void drawStatus(int player) if ( itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( drawHotBarTooltipOnCycle ) { //drawSpellTooltip(player, spell, item, &src); @@ -1856,7 +1856,7 @@ void drawStatus(int player) if ( pressed != Player::Hotbar_t::GROUP_NONE && players[player]->hotbar.faceMenuQuickCastEnabled && item && itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( spell && players[player]->magic.selectedSpell() == spell ) { players[player]->hotbar.faceMenuQuickCast = true; @@ -3089,7 +3089,7 @@ void drawStatusNew(const int player) // quickcasting spells if (item && itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( spell && players[player]->magic.selectedSpell() == spell ) { players[player]->hotbar.faceMenuQuickCast = true; diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index 167993c68..e0cc3d9b2 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -6191,6 +6191,7 @@ void GenericGUIMenu::repairItem(Item* item) { if ( itemCategory(item) == MAGICSTAFF ) { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_MAGICSTAFF_RECHARGED, item->type, 1); if ( item->status == BROKEN ) { if ( itemEffectItemBeatitude > 0 ) @@ -6454,7 +6455,7 @@ void GenericGUIMenu::openGUI(int type, Item* effectItem, int effectBeatitude, in { continue; } - spell_t* spell = getSpellFromItem(gui_player, item); + spell_t* spell = getSpellFromItem(gui_player, item, false); if ( spell && spell->ID == usingSpellID ) { effectItem = item; @@ -10434,6 +10435,7 @@ int GenericGUIMenu::scribingToolDegradeOnUse(Item* itemUsedWith) if ( durability - usageCost < 0 ) { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_FEATHER_CHARGE_USED, ENCHANTED_FEATHER, durability); toDegrade->status = BROKEN; toDegrade->appearance = 0; } @@ -10441,6 +10443,7 @@ int GenericGUIMenu::scribingToolDegradeOnUse(Item* itemUsedWith) { scribingLastUsageDisplayTimer = 200; scribingLastUsageAmount = usageCost; + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_FEATHER_CHARGE_USED, ENCHANTED_FEATHER, scribingLastUsageAmount); toDegrade->appearance -= usageCost; if ( toDegrade->appearance % ENCHANTED_FEATHER_MAX_DURABILITY == 0 ) { @@ -10668,6 +10671,7 @@ bool GenericGUIMenu::scribingWriteItem(Item* item) { steamStatisticUpdate(STEAM_STAT_ROLL_THE_BONES, STEAM_STAT_INT, 1); } + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_FEATHER_ENSCRIBED, ENCHANTED_FEATHER, 1); for ( int i = 0; i < NUMLABELS; ++i ) { if ( label == scroll_label[i] ) @@ -10735,6 +10739,9 @@ bool GenericGUIMenu::scribingWriteItem(Item* item) } } } + + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_FEATHER_SPELLBOOKS, ENCHANTED_FEATHER, 1); + int repairedStatus = std::min(static_cast(item->status + 1), EXCELLENT); bool isEquipped = itemIsEquipped(item, gui_player); item->status = static_cast(repairedStatus); @@ -21121,7 +21128,7 @@ void GenericGUIMenu::ItemEffectGUI_t::updateItemEffectMenu() if ( item && item->type == SPELL_ITEM ) { isSpell = true; - spell = getSpellFromItem(parentGUI.getPlayer(), item); + spell = getSpellFromItem(parentGUI.getPlayer(), item, false); if ( spell ) { spellID = spell->ID; @@ -21238,7 +21245,7 @@ void GenericGUIMenu::ItemEffectGUI_t::updateItemEffectMenu() { if ( parentGUI.itemEffectScrollItem && parentGUI.itemEffectScrollItem->type == SPELL_ITEM ) { - if ( spell_t* spell = getSpellFromItem(parentGUI.gui_player, parentGUI.itemEffectScrollItem) ) + if ( spell_t* spell = getSpellFromItem(parentGUI.gui_player, parentGUI.itemEffectScrollItem, false) ) { if ( node_t* spellImageNode = ItemTooltips.getSpellNodeFromSpellID(spell->ID) ) { @@ -24657,6 +24664,10 @@ bool CalloutRadialMenu::createParticleCallout(Entity* entity, CalloutRadialMenu: if ( players[getPlayer()]->ghost.isActive() ) { players[getPlayer()]->ghost.createBounceAnimate(); + if ( players[getPlayer()]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdateMonster(getPlayer(), Compendium_t::CPDM_GHOST_PINGS, players[getPlayer()]->ghost.my, 1); + } } return callout.doMessage; @@ -24775,6 +24786,10 @@ bool CalloutRadialMenu::createParticleCallout(real_t x, real_t y, real_t z, Uint if ( players[getPlayer()]->ghost.isActive() ) { players[getPlayer()]->ghost.createBounceAnimate(); + if ( players[getPlayer()]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdateMonster(getPlayer(), Compendium_t::CPDM_GHOST_PINGS, players[getPlayer()]->ghost.my, 1); + } } return callout.doMessage; diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 85523d1e4..9461a7a12 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -1952,7 +1952,7 @@ std::string getItemSpritePath(const int player, Item& item) { if ( item.type == SPELL_ITEM ) { - return ItemTooltips.getSpellIconPath(player, item); + return ItemTooltips.getSpellIconPath(player, item, -1); } else { @@ -4332,7 +4332,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int if ( item->type == SPELL_ITEM ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( !spell ) { snprintf(buf, sizeof(buf), "%s", "Unknown Spell"); @@ -4548,7 +4548,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int std::string minWidthKey = "default"; std::string maxWidthKey = "default"; std::string headerMaxWidthKey = "default"; - if ( spell_t* spell = getSpellFromItem(player, item) ) + if ( spell_t* spell = getSpellFromItem(player, item, false) ) { if ( itemTooltip.minWidths.find(ItemTooltips.spellItems[spell->ID].internalName) != itemTooltip.minWidths.end() ) { @@ -4740,7 +4740,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { imgSpellIcon->disabled = false; imgSpellIconBg->disabled = false; - imgSpellIcon->path = ItemTooltips.getSpellIconPath(player, *item); + imgSpellIcon->path = ItemTooltips.getSpellIconPath(player, *item, -1); static ConsoleVariable cvar_spelltooltipIconX("/spell_tooltip_icon_x", 0); static ConsoleVariable cvar_spelltooltipIconY("/spell_tooltip_icon_y", -4); @@ -4786,6 +4786,12 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int txtThirdValuePositive->setPaddingPerLine(*cvar_item_tooltip_attr_padding); txtThirdValueNegative->setPaddingPerLine(*cvar_item_tooltip_attr_padding); } + + bool compendiumTooltip = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + } if ( itemTooltip.icons.size() > 0 ) { @@ -4802,7 +4808,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { continue; } - icon.iconPath = ItemTooltips.getSpellIconPath(player, *item); + icon.iconPath = ItemTooltips.getSpellIconPath(player, *item, -1); } } else if ( itemCategory(item) == SCROLL ) @@ -4838,7 +4844,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( icon.conditionalAttribute == "TINKERBOT_MAGICATK" ) { - icon.iconPath = ItemTooltips.getSpellIconPath(player, *item); + icon.iconPath = ItemTooltips.getSpellIconPath(player, *item, -1); } } else if ( item->type == SPELL_ITEM ) @@ -4849,7 +4855,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( icon.conditionalAttribute.find("spell_") != std::string::npos ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( spell && ItemTooltips.spellItems[spell->ID].internalName == icon.conditionalAttribute ) { // current spell uses this attribute @@ -4881,6 +4887,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( icon.conditionalAttribute == "SPELLBOOK_UNLEARNED" ) { + if ( compendiumTooltip ) { continue; } if ( isGoblin || playerLearnedSpellbook(player, item) || (spell && skillLVL >= spell->difficulty) ) { continue; @@ -4889,6 +4896,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( icon.conditionalAttribute == "SPELLBOOK_UNLEARNABLE" ) { if ( !isGoblin ) { continue; } + if ( compendiumTooltip ) { continue; } if ( playerLearnedSpellbook(player, item) ) { continue; @@ -4897,6 +4905,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( icon.conditionalAttribute == "SPELLBOOK_LEARNABLE" ) { if ( isGoblin ) { continue; } + if ( compendiumTooltip ) { continue; } if ( playerLearnedSpellbook(player, item) || (spell && skillLVL < spell->difficulty) ) { continue; @@ -4907,19 +4916,20 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { if ( icon.conditionalAttribute == "SPELLBOOK_SPELLINFO_LEARNED" ) { - if ( !playerLearnedSpellbook(player, item) ) + if ( !compendiumTooltip && !playerLearnedSpellbook(player, item) ) { continue; } } else if ( icon.conditionalAttribute == "SPELLBOOK_SPELLINFO_UNLEARNED" ) { + if ( compendiumTooltip ) { continue; } if ( playerLearnedSpellbook(player, item) ) { continue; } } - icon.iconPath = ItemTooltips.getSpellIconPath(player, *item); + icon.iconPath = ItemTooltips.getSpellIconPath(player, *item, -1); } else if ( !items[item->type].hasAttribute(icon.conditionalAttribute) ) { @@ -4952,7 +4962,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int imgPrimaryIcon->path = icon.iconPath; std::string iconText = icon.text; - ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute); + ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute, parentFrame); if ( tooltipType.find("tooltip_spell_") != std::string::npos && iconText == "" ) { @@ -4986,7 +4996,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int imgSecondaryIcon->path = icon.iconPath; std::string iconText = icon.text; - ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute); + ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute, parentFrame); std::string bracketText = ""; ItemTooltips.stripOutHighlightBracketText(iconText, bracketText); @@ -5013,7 +5023,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int imgThirdIcon->path = icon.iconPath; std::string iconText = icon.text; - ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute); + ItemTooltips.formatItemIcon(player, tooltipType, *item, iconText, index, icon.conditionalAttribute, parentFrame); std::string bracketText = ""; ItemTooltips.stripOutHighlightBracketText(iconText, bracketText); @@ -5069,7 +5079,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } frameDesc->setDisabled(true); - + std::string detailsTextString = ""; if ( !doShortTooltip && itemTooltip.detailsText.size() > 0 ) { @@ -5322,6 +5332,10 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( tag.compare("spellbook_cast_success") == 0 || tag.compare("spellbook_extramana_chance") == 0 ) { + if ( compendiumTooltip ) + { + continue; + } bool newbie = isSpellcasterBeginnerFromSpellbook(player, players[player]->entity, stats[player], getSpellFromID(getSpellIDFromSpellbook(item->type)), item); if ( !newbie ) @@ -5333,6 +5347,10 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int || tag.compare("spellbook_magic_current") == 0 || tag.compare("spellbook_unlearned_blank_space") == 0 ) { + if ( compendiumTooltip ) + { + continue; + } if ( playerLearnedSpellbook(player, item) ) { continue; @@ -5341,7 +5359,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( tag.compare("spell_damage_bonus") == 0 ) { if ( !ItemTooltips.bIsSpellDamageOrHealingType(spell) ) @@ -5352,6 +5370,10 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( tag.compare("spell_cast_success") == 0 || tag.compare("spell_extramana_chance") == 0 ) { + if ( compendiumTooltip ) + { + continue; + } bool newbie = isSpellcasterBeginner(player, players[player]->entity); if ( !newbie ) { @@ -5360,6 +5382,10 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( tag.compare("spell_newbie_newline") == 0 ) { + if ( compendiumTooltip ) + { + continue; + } bool newbie = isSpellcasterBeginner(player, players[player]->entity); if ( !newbie ) { @@ -5372,7 +5398,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( tag.find("attribute_spell_") != std::string::npos ) { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, false); if ( !spell ) { continue; } std::string subs = tag.substr(10, std::string::npos); if ( !spell || ItemTooltips.spellItems[spell->ID].internalName != subs ) @@ -11298,7 +11324,7 @@ bool playerLearnedSpellbook(int player, Item* current_item) // special shaman racial spells, don't count this as being learnt continue; } - spell_t *spell = getSpellFromItem(player, item); //Do not free or delete this. + spell_t *spell = getSpellFromItem(player, item, false); //Do not free or delete this. if ( !spell ) { continue; diff --git a/src/item_tool.cpp b/src/item_tool.cpp index 88048d4c2..d5b2f65c9 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -21,6 +21,7 @@ #include "scores.hpp" #include "shops.hpp" #include "prng.hpp" +#include "mod_tools.hpp" void Item::applySkeletonKey(int player, Entity& entity) { @@ -31,6 +32,7 @@ void Item::applySkeletonKey(int player, Entity& entity) { messagePlayer(player, MESSAGE_INTERACTION, Language::get(1097)); entity.unlockChest(); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); } else { @@ -52,6 +54,7 @@ void Item::applySkeletonKey(int player, Entity& entity) { messagePlayer(player, MESSAGE_INTERACTION, Language::get(1099)); entity.doorLocked = 0; + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); } } else @@ -255,6 +258,7 @@ void Item::applyLockpick(int player, Entity& entity) } } entity.unlockChest(); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); } else { @@ -348,6 +352,7 @@ void Item::applyLockpick(int player, Entity& entity) //Unlock door. playSoundEntity(&entity, 91, 64); messagePlayer(player, MESSAGE_INTERACTION, Language::get(1099)); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); entity.doorLocked = 0; if ( !entity.doorPreventLockpickExploit ) { diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 9fff05343..c36da28c0 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -2342,8 +2342,13 @@ Entity* item_PotionPolymorph(Item*& item, Entity* entity, Entity* usedBy) void onScrollUseAppraisalIncrease(Item* item, int player) { if ( !item ) { return; } - if ( !item->identified && players[player] && players[player]->isLocalPlayer() ) + if ( item->identified && players[player] && players[player]->isLocalPlayer() ) { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CONSUMED, item->type, 1); + } + else if ( !item->identified && players[player] && players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CONSUMED_UNIDENTIFIED, item->type, 1); if ( stats[player]->getProficiency(PRO_APPRAISAL) < SKILL_LEVEL_BASIC ) { if ( stats[player] && players[player]->entity ) @@ -5323,6 +5328,7 @@ void item_Spellbook(Item*& item, int player) if ( learned ) { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_LEARNT, item->type, 1); if ( item->type >= SPELLBOOK_RAT_FORM && item->type <= SPELLBOOK_IMP_FORM ) { ItemType originalSpellbook = item->type; diff --git a/src/items.cpp b/src/items.cpp index 5d3fa4321..35dd8d341 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1006,7 +1006,7 @@ SDL_Surface* itemSprite(Item* const item) spell_t* spell = nullptr; for ( int i = 0; i < MAXPLAYERS; ++i ) { - spell = getSpellFromItem(i, item); + spell = getSpellFromItem(i, item, false); if ( spell ) { break; @@ -2708,7 +2708,7 @@ void useItem(Item* item, const int player, Entity* usedBy, bool unequipForDroppi break; case SPELL_ITEM: { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, true); if (spell) { equipSpell(spell, player, item); diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 506876a41..852fe4e00 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -584,6 +584,41 @@ void spawnBloodVialOnMonsterDeath(Entity* entity, Stat* hitstats, Entity* killer static ConsoleVariable cvar_magic_fx_use_vismap("/magic_fx_use_vismap", true); +void magicTrapOnHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 oldHP, int spellID) +{ + if ( !parent || !hitentity || !hitstats ) { return; } + if ( spellID == SPELL_NONE ) { return; } + if ( parent->behavior == &actMagicTrap || parent->behavior == &actMagicTrapCeiling ) + { + const char* category = parent->behavior == &actMagicTrap ? "magic trap" : "ceiling trap"; + if ( oldHP == 0 ) + { + if ( hitentity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(hitentity->skill[2], Compendium_t::CPDM_TRAP_MAGIC_STATUSED, category, 1); + } + } + else if ( oldHP > hitstats->HP ) + { + if ( hitentity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(hitentity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, category, oldHP - hitstats->HP); + if ( hitstats->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(hitentity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, category, 1); + } + } + else if ( hitentity->behavior == &actMonster ) + { + if ( auto leader = hitentity->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdateWorld(hitentity->monsterAllyIndex, Compendium_t::CPDM_TRAP_FOLLOWERS_KILLED, category, 1); + } + } + } + } +} + void actMagicMissile(Entity* my) //TODO: Verify this function. { if (!my || !my->children.first || !my->children.first->element) @@ -1416,7 +1451,9 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; damage *= damageMultiplier; damage /= (1 + (int)resistance); + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { Entity* gib = spawnGib(hit.entity); @@ -1591,7 +1628,9 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage *= damageMultiplier; damage /= (1 + (int)resistance); + Sint32 oldHP; hit.entity->modHP(-damage); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { Entity* gib = spawnGib(hit.entity); @@ -1876,6 +1915,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage *= fireMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); //for (i = 0; i < damage; i += 2) { //Spawn a gib for every two points of damage. Entity* gib = spawnGib(hit.entity); serverSpawnGibForClient(gib); @@ -2125,6 +2165,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( duration > 0 && hit.entity->setEffect(EFF_CONFUSED, true, duration, false) ) { + magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); playSoundEntity(hit.entity, 174, 64); if ( hit.entity->behavior == &actMonster ) { @@ -2246,6 +2287,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. hit.entity->modHP(-damage); Entity* gib = spawnGib(hit.entity); serverSpawnGibForClient(gib); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); } // write the obituary @@ -2323,6 +2365,8 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast(element->base_mana)) * element->overload_multiplier)); hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance); + magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); + // If the Entity hit is a Player, update their status to be Slowed if ( hit.entity->behavior == &actPlayer ) { @@ -2397,6 +2441,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { if ( hit.entity->setEffect(EFF_ASLEEP, true, effectDuration, false) ) { + magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); playSoundEntity(hit.entity, 174, 64); hitstats->OLDHP = hitstats->HP; if ( hit.entity->behavior == &actPlayer ) @@ -2499,6 +2544,8 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage /= (1 + (int)resistance); hit.entity->modHP(-damage); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); + // write the obituary if (parent) { @@ -2875,6 +2922,13 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { // Open the Door playSoundEntity(hit.entity, 91, 64); // "UnlockDoor.ogg" + if ( hit.entity->doorLocked ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); + } + } hit.entity->doorLocked = 0; // Unlocks the Door hit.entity->doorPreventLockpickExploit = 1; @@ -2927,6 +2981,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent->behavior == &actPlayer ) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(403)); // "The spell opens the gate!" + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_GATE_OPENED_SPELL, "portcullis", 1); } } } @@ -2955,6 +3010,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent->behavior == &actPlayer) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(404)); // "The spell unlocks the chest!" + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); } } } @@ -3168,8 +3224,11 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } damage /= (1 + (int)resistance); + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); + // write the obituary if ( parent ) { @@ -6847,6 +6906,7 @@ bool magicDig(Entity* parent, Entity* projectile, int numRocks, int randRocks) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(4337), Language::get(hit.entity->getColliderLangName())); // you destroy the %s! + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); } } diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index d4585a66f..9a89d60a8 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -24,6 +24,7 @@ #include "../ui/MainMenu.hpp" #include "magic.hpp" #include "../prng.hpp" +#include "../mod_tools.hpp" bool spellIsNaturallyLearnedByRaceOrClass(Entity& caster, Stat& stat, int spellID); @@ -550,6 +551,10 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool consumeItem(toBreak, player); } } + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_FAILURES, SPELL_ITEM, 1, false, spell->ID); + } return NULL; } } @@ -864,6 +869,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { if ( caster->behavior == &actDeathGhost ) { + Compendium_t::Events_t::eventUpdateMonster(caster->skill[2], Compendium_t::CPDM_GHOST_TELEPORTS, caster, 1); auto& ghost = players[caster->skill[2]]->ghost; int tx = ghost.spawnX; int ty = ghost.spawnY; @@ -2444,6 +2450,20 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool //Random chance to level up spellcasting skill. if ( !trap ) { + if ( player >= 0 ) + { + if ( usingSpellbook && !using_magicstaff ) + { + if ( stat && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CASTS, stat->shield->type, 1); + } + } + else if ( !usingSpellbook && !using_magicstaff ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_CASTS, SPELL_ITEM, 1, false, spell->ID); + } + } if ( using_magicstaff ) { if ( stat ) diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index 7170a3a1a..c32b2c846 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -615,7 +615,7 @@ void spellcastingAnimationManager_completeSpell(spellcasting_animation_manager_t class Item; -spell_t* getSpellFromItem(const int player, Item* item); +spell_t* getSpellFromItem(const int player, Item* item, bool usePlayerInventory); int getSpellIDFromSpellbook(int spellbookType); int canUseShapeshiftSpellInCurrentForm(const int player, Item& item); diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index 2c268d401..d53eb463d 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -1303,7 +1303,7 @@ void spell_changeHealth(Entity* entity, int amount, bool overdrewFromHP) } } -spell_t* getSpellFromItem(const int player, Item* item) +spell_t* getSpellFromItem(const int player, Item* item, bool usePlayerInventory) { spell_t* spell = nullptr; node_t* node = nullptr; @@ -1311,23 +1311,40 @@ spell_t* getSpellFromItem(const int player, Item* item) { return nullptr; } - for ( node = players[player]->magic.spellList.first; node; node = node->next ) + + Uint32 appearance = item->appearance; + if ( item->type == SPELL_ITEM && item->appearance >= 1000 ) + { + appearance -= 1000; // hack for normally uncontrollable spells. + } + if ( usePlayerInventory ) { - if ( node->element ) + if ( player < 0 || player >= MAXPLAYERS ) { - spell = (spell_t*) node->element; - Uint32 appearance = item->appearance; - if ( item->type == SPELL_ITEM && item->appearance >= 1000 ) + return nullptr; + } + for ( node = players[player]->magic.spellList.first; node; node = node->next ) + { + if ( node->element ) { - appearance -= 1000; // hack for normally uncontrollable spells. + spell = (spell_t*) node->element; + if ( spell->ID == appearance ) + { + return spell; //Found the spell. + } } + } + } + else + { + for ( auto spell : allGameSpells ) + { if ( spell->ID == appearance ) { return spell; //Found the spell. } } } - return nullptr; } @@ -1345,7 +1362,7 @@ int canUseShapeshiftSpellInCurrentForm(const int player, Item& item) { return -1; } - spell_t* spell = getSpellFromItem(player, &item); + spell_t* spell = getSpellFromItem(player, &item, false); if ( !spell ) { return -1; diff --git a/src/mechanisms.cpp b/src/mechanisms.cpp index 3eed189f8..7e196b3d2 100644 --- a/src/mechanisms.cpp +++ b/src/mechanisms.cpp @@ -17,6 +17,7 @@ #include "net.hpp" #include "player.hpp" #include "scores.hpp" +#include "mod_tools.hpp" //Circuits do not overlap. They connect to all their neighbors, allowing for circuits to interfere with eachother. static ConsoleVariable cvar_wire_debug("/wire_debug", false); @@ -188,8 +189,7 @@ void actSwitch(Entity* my) if ( multiplayer != CLIENT ) { - int i = 0; - for (i = 0; i < MAXPLAYERS; ++i) + for (int i = 0; i < MAXPLAYERS; ++i) { if ( selectedEntity[i] == my || client_selected[i] == my ) { @@ -198,6 +198,7 @@ void actSwitch(Entity* my) messagePlayer(i, MESSAGE_INTERACTION, Language::get(1110)); playSoundEntity(my, 56, 64); my->toggleSwitch(); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_LEVER_PULLED, "lever", 1); } } } @@ -210,6 +211,7 @@ void actSwitch(Entity* my) if ( leader ) { achievementObserver.playerAchievements[monsterInteracting->monsterAllyIndex].checkPathBetweenObjects(leader, my, AchievementObserver::BARONY_ACH_LEVITANT_LACKEY); + Compendium_t::Events_t::eventUpdateWorld(monsterInteracting->monsterAllyIndex, Compendium_t::CPDM_LEVER_FOLLOWER_PULLED, "lever", 1); } } my->toggleSwitch(); diff --git a/src/menu.cpp b/src/menu.cpp index d7b527112..65d1c7368 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8525,6 +8525,8 @@ void doNewGame(bool makeHighscore) { lastEntityUIDs = entity_uids; loading = true; darkmap = false; + std::string prevmapname = map.name; + bool died = players[clientnum]->entity == nullptr; for ( int i = 0; i < MAXPLAYERS; ++i ) { @@ -9354,6 +9356,8 @@ void doNewGame(bool makeHighscore) { } } + const auto wasLoadingSaveGame = loadingsavegame; + if ( loadingsavegame && multiplayer != CLIENT ) { loadingsavegame = 0; @@ -9399,6 +9403,7 @@ void doNewGame(bool makeHighscore) { } } + Compendium_t::Events_t::writeItemsSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif @@ -9407,6 +9412,32 @@ void doNewGame(bool makeHighscore) { pauseGame(1, 0); loading = false; intro = false; + + if ( !wasLoadingSaveGame ) + { + if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) + { + const std::string mapname = map.name; + if ( mapname.find("Tutorial Hub") == std::string::npos + && mapname.find("Tutorial ") != std::string::npos ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_ATTEMPTS, "hall of trials", 1); + } + + // restarting from a trial, this is a failure + if ( died ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + } + } + else + { + if ( currentlevel == 0 && !secretlevel ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER, "minehead", 1); + } + } + } } void doCredits() { @@ -9437,7 +9468,6 @@ void doEndgame(bool saveHighscore) { bool localScores = gameModeManager.allowsHiscores(); bool onlineScores = gameModeManager.allowsGlobalHiscores(); bool allowedSavegames = gameModeManager.allowsSaves(); - bool allowedCompendiumProgression = gameModeManager.getMode() != GameModeManager_t::GAME_MODE_CUSTOM_RUN; if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) { victory = 0; @@ -9486,19 +9516,11 @@ void doEndgame(bool saveHighscore) { } achievementObserver.updateGlobalStat(STEAM_GSTAT_GAMES_WON); - if ( allowedCompendiumProgression ) - { - for ( int c = 0; c < MAXPLAYERS; c++ ) - { - if ( !client_disconnected[c] ) - { - Compendium_t::Events_t::onVictoryEvent(c); - } - } - } } } + Compendium_t::Events_t::onVictoryEvent(clientnum, endTutorial); + // figure out the victory crawl texts... int movieCrawlType = -1; if ( victory ) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 3f356e819..19d213bf1 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -1179,6 +1179,8 @@ void ItemTooltips_t::readItemsFromFile() t.magicstaffId = i; } } + + spellNameStringToSpellID[t.internalName] = t.id; assert(spellItems.find(t.id) == spellItems.end()); // check we haven't got duplicate key spellItems.insert(std::make_pair(t.id, t)); ++spellsRead; @@ -2136,7 +2138,7 @@ std::string ItemTooltips_t::getSpellDescriptionText(const int player, Item& item #ifdef EDITOR return defaultString; #else - spell_t* spell = getSpellFromItem(player, &item); + spell_t* spell = getSpellFromItem(player, &item, false); if ( !spell || spellItems.find(spell->ID) == spellItems.end() ) { return defaultString; @@ -2192,7 +2194,7 @@ std::string ItemTooltips_t::getSpellIconText(const int player, Item& item) } else { - spell = getSpellFromItem(player, &item); + spell = getSpellFromItem(player, &item, false); } if ( !spell || spellItems.find(spell->ID) == spellItems.end() ) { @@ -2279,7 +2281,7 @@ std::string& ItemTooltips_t::getSpellTypeString(const int player, Item& item) #ifdef EDITOR return defaultString; #else - spell_t* spell = getSpellFromItem(player, &item); + spell_t* spell = getSpellFromItem(player, &item, false); if ( !spell ) { return defaultString; @@ -2314,7 +2316,7 @@ std::string ItemTooltips_t::getCostOfSpellString(const int player, Item& item) #ifdef EDITOR return defaultString; #else - spell_t* spell = getSpellFromItem(player, &item); + spell_t* spell = getSpellFromItem(player, &item, false); if ( !spell ) { return defaultString; @@ -2429,7 +2431,7 @@ node_t* ItemTooltips_t::getSpellNodeFromSpellID(int spellID) return spellImageNode; } -std::string ItemTooltips_t::getSpellIconPath(const int player, Item& item) +std::string ItemTooltips_t::getSpellIconPath(const int player, Item& item, int spellID) { #ifdef EDITOR return "items/images/null.png"; @@ -2465,14 +2467,21 @@ std::string ItemTooltips_t::getSpellIconPath(const int player, Item& item) } else if ( item.type == SPELL_ITEM ) { - spell_t* spell = getSpellFromItem(player, &item); - if ( spell ) + if ( spellID > SPELL_NONE ) { - spellImageNode = getSpellNodeFromSpellID(spell->ID); + spellImageNode = getSpellNodeFromSpellID(spellID); } else { - spellImageNode = getSpellNodeFromSpellID(SPELL_NONE); + spell_t* spell = getSpellFromItem(player, &item, false); + if ( spell ) + { + spellImageNode = getSpellNodeFromSpellID(spell->ID); + } + else + { + spellImageNode = getSpellNodeFromSpellID(SPELL_NONE); + } } } if ( spellImageNode ) @@ -2697,7 +2706,7 @@ Sint32 getStatAttributeBonusFromItem(const int player, Item& item, std::string& #endif } -void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, Item& item, std::string& str, int iconIndex, std::string& conditionalAttribute) +void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, Item& item, std::string& str, int iconIndex, std::string& conditionalAttribute, Frame* parentFrame) { #ifndef EDITOR auto itemTooltip = tooltips[tooltipType]; @@ -3664,7 +3673,14 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I { if ( conditionalAttribute == "SCROLL_LABEL" ) { - snprintf(buf, sizeof(buf), str.c_str(), item.getScrollLabel()); + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + snprintf(buf, sizeof(buf), str.c_str(), "???"); // hide labels in compendium + } + else + { + snprintf(buf, sizeof(buf), str.c_str(), item.getScrollLabel()); + } } else { @@ -4466,7 +4482,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { if ( detailTag.compare("spell_damage_bonus") == 0 ) { - spell_t* spell = getSpellFromItem(player, &item); + spell_t* spell = getSpellFromItem(player, &item, false); if ( !spell ) { return; } //int totalDamage = getSpellDamageOrHealAmount(player, spell, nullptr); @@ -7721,7 +7737,7 @@ void ClassHotbarConfig_t::writeToFile(HotbarConfigType fileWriteType, HotbarConf std::string itemstr = itemNameStrings[item->type + 2]; if ( itemstr == "spell_item" ) { - if ( spell_t* spell = getSpellFromItem(clientnum, item) ) + if ( spell_t* spell = getSpellFromItem(clientnum, item, false) ) { itemstr = ItemTooltips.spellItems[spell->ID].internalName; } @@ -7787,7 +7803,7 @@ void ClassHotbarConfig_t::writeToFile(HotbarConfigType fileWriteType, HotbarConf std::string itemstr = itemNameStrings[item->type + 2]; if ( itemstr == "spell_item" ) { - if ( spell_t* spell = getSpellFromItem(clientnum, item) ) + if ( spell_t* spell = getSpellFromItem(clientnum, item, false) ) { itemstr = ItemTooltips.spellItems[spell->ID].internalName; } @@ -8085,7 +8101,7 @@ void ClassHotbarConfig_t::assignHotbarSlots(const int player) { continue; // shaman form spells } - if ( spell_t* spell = getSpellFromItem(player, item) ) + if ( spell_t* spell = getSpellFromItem(player, item, false) ) { itemType = spell->ID + 10000; } @@ -10626,7 +10642,13 @@ void jsonVecToVec(rapidjson::Value& val, std::vector& vec) } } +#ifndef EDITOR Compendium_t CompendiumEntries; +Item Compendium_t::compendiumItem; +Entity Compendium_t::compendiumItemModel(-1, 0, nullptr, nullptr); +bool Compendium_t::tooltipNeedUpdate = false; +SDL_Rect Compendium_t::tooltipPos; + std::vector> Compendium_t::CompendiumMonsters_t::contents; std::map Compendium_t::CompendiumMonsters_t::contentsMap; std::vector> Compendium_t::CompendiumWorld_t::contents; @@ -10635,10 +10657,31 @@ std::vector> Compendium_t::CompendiumCodex_t std::map Compendium_t::CompendiumCodex_t::contentsMap; std::vector> Compendium_t::CompendiumItems_t::contents; std::map Compendium_t::CompendiumItems_t::contentsMap; +std::vector> Compendium_t::CompendiumMagic_t::contents; +std::map Compendium_t::CompendiumMagic_t::contentsMap; + +void Compendium_t::updateTooltip() +{ + bool update = tooltipNeedUpdate; + tooltipNeedUpdate = false; + + if ( MainMenu::main_menu_frame ) + { + auto compendiumFrame = MainMenu::main_menu_frame->findFrame("compendium"); + if ( !compendiumFrame ) { return; } + + players[0]->inventoryUI.updateInventoryItemTooltip(compendiumFrame); + + if ( update ) + { + players[0]->hud.updateFrameTooltip(&compendiumItem, tooltipPos.x, tooltipPos.y, Player::PANEL_JUSTIFY_RIGHT, compendiumFrame); + } + } +} void Compendium_t::readItemsFromFile() { - const std::string filename = "data/compendium/compendium_items.json"; + const std::string filename = "data/compendium/comp_items.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -10675,6 +10718,7 @@ void Compendium_t::readItemsFromFile() CompendiumItems_t::contentsMap.clear(); Compendium_t::Events_t::itemEventLookup.clear(); Compendium_t::Events_t::eventItemLookup.clear(); + Compendium_t::Events_t::itemDisplayedEventsList.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -10755,12 +10799,35 @@ void Compendium_t::readItemsFromFile() } } } + if ( w.HasMember("events_display") ) + { + for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + } + } + } + } + } + } } } -void Compendium_t::readCodexFromFile() +void Compendium_t::readMagicFromFile() { - const std::string filename = "data/compendium/codex.json"; + const std::string filename = "data/compendium/comp_magic.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -10786,40 +10853,259 @@ void Compendium_t::readCodexFromFile() rapidjson::Document d; d.ParseStream(is); - if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("codex") ) + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("items") ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); return; } - codex.clear(); - CompendiumCodex_t::contents.clear(); - CompendiumCodex_t::contentsMap.clear(); + magic.clear(); + CompendiumMagic_t::contents.clear(); + CompendiumMagic_t::contentsMap.clear(); + //Compendium_t::Events_t::itemEventLookup.clear(); + //Compendium_t::Events_t::eventItemLookup.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) { - CompendiumCodex_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumCodex_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + CompendiumMagic_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumMagic_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); } } - auto& entries = d["codex"]; + auto& entries = d["items"]; + std::unordered_set ignoredSpells; + std::unordered_set ignoredSpellbooks; + + for ( auto itr = d["exclude_spells"].Begin(); itr != d["exclude_spells"].End(); ++itr ) + { + ignoredSpells.insert(itr->GetString()); + } + for ( auto itr = d["exclude_spellbooks"].Begin(); itr != d["exclude_spellbooks"].End(); ++itr ) + { + ignoredSpellbooks.insert(itr->GetString()); + } + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) { std::string name = itr->name.GetString(); auto& w = itr->value; - auto& obj = codex[name]; + auto& obj = magic[name]; jsonVecToVec(w["blurb"], obj.blurb); - jsonVecToVec(w["details"], obj.details); - obj.imagePath = w["img"].GetString(); + for ( auto itr = w["items"].Begin(); itr != w["items"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumItems_t::Codex_t::CodexItem_t item; + item.name = itr2->name.GetString(); + item.rotation = 0; + if ( itr2->value.HasMember("rotation") ) + { + item.rotation = itr2->value["rotation"].GetInt(); + } + obj.items_in_category.push_back(item); + } + } + + std::list spellsToSort; + bool spells = name.find("spells") != std::string::npos; + bool spellbooks = name.find("spellbooks") != std::string::npos; + if ( spells || spellbooks ) + { + for ( auto spell : allGameSpells ) + { + if ( spells && ignoredSpells.find(spell->spell_internal_name) != ignoredSpells.end() ) + { + continue; + } + if ( spellbooks ) + { + int book = getSpellbookFromSpellID(spell->ID); + if ( book >= WOODEN_SHIELD && book < NUMITEMS && ::items[book].category == SPELLBOOK ) + { + if ( ignoredSpellbooks.find(itemNameStrings[book + 2]) != ignoredSpellbooks.end() ) + { + continue; + } + } + else + { + continue; + } + } + + if ( name.find("damage") != std::string::npos ) + { + if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + == ItemTooltips.spellItems[spell->ID].spellTags.end() ) + { + continue; + } + } + else if ( name.find("status") != std::string::npos ) + { + if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) + == ItemTooltips.spellItems[spell->ID].spellTags.end() ) + { + continue; + } + } + else if ( name.find("utility") != std::string::npos ) + { + if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + != ItemTooltips.spellItems[spell->ID].spellTags.end() + || ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) + != ItemTooltips.spellItems[spell->ID].spellTags.end() ) + { + continue; + } + } + else + { + continue; + } + spellsToSort.push_back(spell); + } + + if ( spellbooks ) + { + spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { + const int bookLeft = getSpellbookFromSpellID(lhs->ID); + const int bookRight = getSpellbookFromSpellID(rhs->ID); + + const int bookLevelLeft = ::items[bookLeft].level >= 0 ? ::items[bookLeft].level : 10000; // -1 level sorted to the end + const int bookLevelRight = ::items[bookRight].level >= 0 ? ::items[bookRight].level : 10000; + + if ( bookLevelLeft < bookLevelRight ) { return true; } + if ( bookLevelLeft > bookLevelRight ) { return false; } + if ( rhs->difficulty > lhs->difficulty ) { return true; } + if ( rhs->difficulty < lhs->difficulty ) { return false; } + + return rhs->ID < lhs->ID; + }); + } + else + { + spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { + if ( rhs->difficulty > lhs->difficulty ) { return true; } + if ( rhs->difficulty < lhs->difficulty ) { return false; } + + return rhs->ID < lhs->ID; + }); + } + + for ( auto spell : spellsToSort ) + { + CompendiumItems_t::Codex_t::CodexItem_t item; + item.rotation = 0; + if ( spellbooks ) + { + int book = getSpellbookFromSpellID(spell->ID); + item.name = itemNameStrings[book + 2]; + item.rotation = 180; + } + else + { + item.name = spell->spell_internal_name; + } + item.spellID = spell->ID; + obj.items_in_category.push_back(item); + } + } + + if ( w.HasMember("events") ) + { + std::vector alwaysTrackedEvents = { + "APPRAISED", + "RUNS_COLLECTED" + }; + + for ( auto& s : alwaysTrackedEvents ) + { + auto find = Compendium_t::Events_t::eventIdLookup.find(s); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType == SPELL_ITEM ) + { + Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert(Compendium_t::Events_t::kEventSpellOffset + item.spellID); + } + else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); + } + } + } + } + } + for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType == SPELL_ITEM ) + { + Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert(Compendium_t::Events_t::kEventSpellOffset + item.spellID); + } + else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); + } + } + } + } + } + } + if ( w.HasMember("events_display") ) + { + for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + for ( auto& item : obj.items_in_category ) + { + const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType == SPELL_ITEM ) + { + Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventSpellOffset + item.spellID].push_back((Compendium_t::EventTags)find2->second.id); + } + else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + } + } + } + } + } + } } } -void Compendium_t::readWorldFromFile() +void Compendium_t::readCodexFromFile() { - const std::string filename = "data/compendium/world.json"; + const std::string filename = "data/compendium/codex.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -10845,30 +11131,30 @@ void Compendium_t::readWorldFromFile() rapidjson::Document d; d.ParseStream(is); - if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("world") ) + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("codex") ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); return; } - worldObjects.clear(); - CompendiumWorld_t::contents.clear(); - CompendiumWorld_t::contentsMap.clear(); + codex.clear(); + CompendiumCodex_t::contents.clear(); + CompendiumCodex_t::contentsMap.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) { - CompendiumWorld_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumWorld_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + CompendiumCodex_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumCodex_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); } } - auto& entries = d["world"]; + auto& entries = d["codex"]; for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) { std::string name = itr->name.GetString(); auto& w = itr->value; - auto& obj = worldObjects[name]; + auto& obj = codex[name]; jsonVecToVec(w["blurb"], obj.blurb); jsonVecToVec(w["details"], obj.details); @@ -10876,9 +11162,9 @@ void Compendium_t::readWorldFromFile() } } -void Compendium_t::readMonstersFromFile() +void Compendium_t::readWorldFromFile() { - const std::string filename = "data/compendium/monsters.json"; + const std::string filename = "data/compendium/world.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -10904,42 +11190,182 @@ void Compendium_t::readMonstersFromFile() rapidjson::Document d; d.ParseStream(is); - if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("monsters") ) + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("world") ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); return; } - monsters.clear(); - CompendiumMonsters_t::contents.clear(); - CompendiumMonsters_t::contentsMap.clear(); + worldObjects.clear(); + CompendiumWorld_t::contents.clear(); + CompendiumWorld_t::contentsMap.clear(); + Compendium_t::Events_t::eventWorldIDLookup.clear(); + Compendium_t::Events_t::eventWorldLookup.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) { - CompendiumMonsters_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumMonsters_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + CompendiumWorld_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumWorld_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); } } - auto& entries = d["monsters"]; + auto& entries = d["world"]; for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) { std::string name = itr->name.GetString(); - int monsterType = NOTHING; - for ( int i = 0; i < NUMMONSTERS; ++i ) - { - if ( name == monstertypename[i] ) - { - monsterType = i; - break; - } - } + auto& w = itr->value; + auto& obj = worldObjects[name]; - if ( monsterType == NOTHING ) { continue; } + obj.id = w["event_lookup"].GetInt(); + jsonVecToVec(w["blurb"], obj.blurb); + jsonVecToVec(w["details"], obj.details); + obj.imagePath = w["img"].GetString(); - auto& m = itr->value; - auto& monster = monsters[name]; + Compendium_t::Events_t::eventWorldIDLookup[name] = obj.id; + if ( w.HasMember("events") ) + { + for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + Compendium_t::Events_t::eventWorldLookup[(Compendium_t::EventTags)find2->second.id].insert(name); + } + } + } + } + /*if ( w.HasMember("events_display") ) + { + for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + } + } + } + }*/ + } +} + +std::map Compendium_t::Events_t::monsterUniqueIDLookup; +std::map> Compendium_t::Events_t::eventMonsterLookup; + +void Compendium_t::readMonstersFromFile() +{ + const std::string filename = "data/compendium/monsters.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("monsters") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + monsters.clear(); + CompendiumMonsters_t::contents.clear(); + CompendiumMonsters_t::contentsMap.clear(); + Compendium_t::Events_t::monsterUniqueIDLookup.clear(); + Compendium_t::Events_t::eventMonsterLookup.clear(); + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + CompendiumMonsters_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); + CompendiumMonsters_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); + } + } + + if ( d.HasMember("unique_tags") ) + { + for ( auto itr = d["unique_tags"].MemberBegin(); itr != d["unique_tags"].MemberEnd(); ++itr ) + { + Compendium_t::Events_t::monsterUniqueIDLookup[itr->name.GetString()] = itr->value.GetInt(); + } + } + + for ( int i = 0; i < NUMMONSTERS; ++i ) + { + int type = i + Compendium_t::Events_t::kEventMonsterOffset; + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); + } + for ( auto pair : Compendium_t::Events_t::monsterUniqueIDLookup ) + { + int type = pair.second + Compendium_t::Events_t::kEventMonsterOffset; + if ( pair.first == "ghost" ) + { + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GHOST_SPAWNED].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GHOST_TELEPORTS].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GHOST_PINGS].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GHOST_PUSHES].insert(type); + } + else + { + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); + } + } + + auto& entries = d["monsters"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + if ( itr->value.HasMember("type") ) + { + name = itr->value["type"].GetString(); + } + int monsterType = NOTHING; + for ( int i = 0; i < NUMMONSTERS; ++i ) + { + if ( name == monstertypename[i] ) + { + monsterType = i; + break; + } + } + + if ( monsterType == NOTHING ) { continue; } + + auto& m = itr->value; + auto& monster = monsters[itr->name.GetString()]; monster.monsterType = monsterType; jsonVecToVec(m["blurb"], monster.blurb); auto& stats = m["stats"]; @@ -10960,15 +11386,84 @@ void Compendium_t::readMonstersFromFile() } } jsonVecToVec(m["inventory"], monster.inventory); + if ( m.HasMember("models") ) + { + jsonVecToVec(m["models"], monster.models); + } } } +Uint32 Compendium_t::lastTickUpdate = 0; std::map Compendium_t::Events_t::events; std::map Compendium_t::Events_t::eventIdLookup; -std::map> Compendium_t::Events_t::itemEventLookup; -std::map> Compendium_t::Events_t::eventItemLookup; -std::map> Compendium_t::Events_t::playerEvents; -std::map> Compendium_t::Events_t::serverPlayerEvents[MAXPLAYERS]; +std::map> Compendium_t::Events_t::itemEventLookup; +std::map> Compendium_t::Events_t::eventItemLookup; +std::map> Compendium_t::Events_t::eventWorldLookup; +std::map Compendium_t::Events_t::eventWorldIDLookup; +std::map> Compendium_t::Events_t::itemDisplayedEventsList; +std::map> Compendium_t::Events_t::playerEvents; +std::map> Compendium_t::Events_t::serverPlayerEvents[MAXPLAYERS]; +std::map> Compendium_t::Events_t::eventLangEntries; + +void Compendium_t::Events_t::readEventsTranslations() +{ + const std::string filename = "data/compendium/events_text.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("tags") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + eventLangEntries.clear(); + for ( auto itr = d["tags"].Begin(); itr != d["tags"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + auto find = eventIdLookup.find(itr2->name.GetString()); + if ( find != eventIdLookup.end()) + { + EventTags tag = eventIdLookup[find->first]; + auto& entry = eventLangEntries[tag]; + if ( itr2->value.HasMember("default") ) + { + entry["default"] = itr2->value["default"].GetString(); + } + if ( itr2->value.HasMember("overrides") ) + { + for ( auto itr3 = itr2->value["overrides"].MemberBegin(); itr3 != itr2->value["overrides"].MemberEnd(); ++itr3 ) + { + entry[itr3->name.GetString()] = itr3->value.GetString(); + } + } + } + } + } +} void Compendium_t::Events_t::readEventsFromFile() { @@ -11030,6 +11525,14 @@ void Compendium_t::Events_t::readEventsFromFile() { entry.type = MAX; } + else if ( type == "bit" ) + { + entry.type = BITFIELD; + } + else if ( type == "min" ) + { + entry.type = MIN; + } } entry.id = index; if ( itr2->value.HasMember("client") ) @@ -11067,7 +11570,8 @@ void Compendium_t::Events_t::loadItemsSaveData() return; } - char buf[65536]; + const int bufSize = 360000; + char buf[bufSize]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11093,12 +11597,73 @@ void Compendium_t::Events_t::loadItemsSaveData() for ( auto itr2 = itr->value.MemberBegin(); itr2 != itr->value.MemberEnd(); ++itr2 ) { int itemType = std::stoi(itr2->name.GetString()); - if ( itemType < 0 || itemType >= NUMITEMS ) + Sint32 value = itr2->value.GetInt(); + if ( itemType >= kEventMonsterOffset && itemType < kEventMonsterOffset + 1000 ) { + eventUpdateMonster(0, id, nullptr, value, true, itemType); continue; } - Sint32 value = itr2->value.GetInt(); - eventUpdate(0, id, (ItemType)itemType, value, true); + if ( itemType >= kEventWorldOffset && itemType < kEventWorldOffset + 1000 ) + { + eventUpdateWorld(0, id, nullptr, value, true, itemType - kEventWorldOffset); + continue; + } + if ( itemType < 0 || (itemType >= NUMITEMS && itemType < kEventSpellOffset) ) + { + continue; + } + if ( itemType >= kEventSpellOffset ) + { + eventUpdate(0, id, SPELL_ITEM, value, true, itemType - kEventSpellOffset); + } + else + { + eventUpdate(0, id, (ItemType)itemType, value, true); + } + } + } +} + +static ConsoleVariable cvar_compendiumClientSave("/compendium_client_save", false); +static ConsoleCommand ccmd_compendium_dummy_data( + "/compendium_dummy_data", "Create test compendium data", + [](int argc, const char** argv) { + if ( argc < 2 ) + { + return; + } + int playernum = atoi(argv[1]); + Compendium_t::Events_t::createDummyClientData(playernum); + }); +void Compendium_t::Events_t::createDummyClientData(const int playernum) +{ + if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } + for ( int i = 0; i < NUMITEMS; ++i ) + { + for ( auto& tag : itemEventLookup[i] ) + { + eventUpdate(playernum, tag, (ItemType)i, 1); + } + } + for ( int i = 0; i < NUM_SPELLS; ++i ) + { + for ( auto& tag : itemEventLookup[i + kEventSpellOffset] ) + { + eventUpdate(playernum, tag, SPELL_ITEM, 1, false, i); + } + } + for ( auto& pair : eventMonsterLookup ) + { + for ( auto monster : pair.second ) + { + eventUpdateMonster(playernum, pair.first, nullptr, 1, false, monster); + } + } + for ( auto& pair : eventWorldLookup ) + { + for ( auto world : pair.second ) + { + eventUpdateWorld(playernum, pair.first, world.c_str(), 1); } } } @@ -11106,7 +11671,14 @@ void Compendium_t::Events_t::loadItemsSaveData() void Compendium_t::Events_t::writeItemsSaveData() { char path[PATH_MAX] = ""; - completePath(path, "savegames/compendium_items.json", outputdir); + if ( *cvar_compendiumClientSave && multiplayer == CLIENT ) + { + completePath(path, "savegames/compendium_items_mp.json", outputdir); + } + else + { + completePath(path, "savegames/compendium_items.json", outputdir); + } rapidjson::Document exportDocument; exportDocument.SetObject(); @@ -11165,34 +11737,341 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) value = std::max(val, value); return true; } + else if ( type == MIN ) + { + if ( value == val ) + { + return false; + } + if ( value == 0 ) + { + value = val; + return true; + } + value = std::min(val, value); + return true; + } + else if ( type == BITFIELD ) + { + value |= val; + return true; + } } -void Compendium_t::Events_t::onVictoryEvent(const int playernum) +void onCompendiumLevelExit(const int playernum, const char* level, const bool enteringLvl) { - + if ( enteringLvl ) + { + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_ENTERED, level, 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_EXITED, level, 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_GOLD, level, stats[playernum]->GOLD); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_LVL, level, stats[playernum]->LVL); + } } -void Compendium_t::Events_t::onLevelChangeEvent(const int playernum) +void Compendium_t::Events_t::onVictoryEvent(const int playernum, const bool tutorialend) { - if ( intro ) { return; } - if ( !players[playernum]->isLocalPlayer() ) { return; } - - if ( multiplayer == SERVER && playernum == 0 ) + if ( players[playernum]->isLocalPlayer() ) { - for ( int i = 1; i < MAXPLAYERS; ++i ) + if ( tutorialend ) { - sendClientDataOverNet(i); + if ( stats[playernum]->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + } + } + else + { + if ( victory ) + { + if ( currentlevel == 35 ) + { + onCompendiumLevelExit(playernum, "citadel sanctum", false); + } + else if ( currentlevel == 24 ) + { + onCompendiumLevelExit(playernum, "hell", false); + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "hell", 1); + } + else if ( currentlevel == 20 ) + { + onCompendiumLevelExit(playernum, "herx lair", false); + } + } } } +} - ////std::set slots; - //int numItems = 0; - //for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) - //{ - // Item* item = (Item*)node->element; - // if ( !item ) - // { - // continue; +void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname) +{ + if ( intro ) { return; } + if ( playernum < 0 || playernum >= MAXPLAYERS ) + { + return; + } + + if ( players[playernum]->isLocalPlayer() ) + { + if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL + || gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL_INIT ) + { + const std::string mapname = map.name; + if ( mapname.find("Tutorial Hub") == std::string::npos + && mapname.find("Tutorial ") != std::string::npos ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_ATTEMPTS, "hall of trials", 1); + } + if ( prevmapname.find("Tutorial Hub") == std::string::npos + && prevmapname.find("Tutorial ") != std::string::npos ) + { + if ( mapname.find("Tutorial Hub") != std::string::npos ) + { + // returning to hub from a trial, success + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_PASSED, "hall of trials", 1); + } + } + } + else + { + if ( !secretlevel ) + { + if ( currentlevel == 5 || currentlevel == 10 || currentlevel == 15 + || currentlevel == 30 ) + { + onCompendiumLevelExit(playernum, "transition floor", true); + } + else if ( currentlevel >= 1 && currentlevel <= 4 ) + { + onCompendiumLevelExit(playernum, "mines", true); + } + else if ( currentlevel >= 6 && currentlevel <= 9 ) + { + onCompendiumLevelExit(playernum, "swamps", true); + } + else if ( currentlevel >= 11 && currentlevel <= 14 ) + { + onCompendiumLevelExit(playernum, "labyrinth", true); + } + else if ( currentlevel >= 16 && currentlevel <= 19 ) + { + onCompendiumLevelExit(playernum, "ruins", true); + } + else if ( currentlevel == 20 ) + { + onCompendiumLevelExit(playernum, "herx lair", true); + } + else if ( currentlevel >= 21 && currentlevel <= 24 ) + { + onCompendiumLevelExit(playernum, "hell", true); + } + else if ( currentlevel == 25 ) + { + onCompendiumLevelExit(playernum, "hamlet", true); + } + else if ( currentlevel >= 26 && currentlevel <= 29 ) + { + onCompendiumLevelExit(playernum, "crystal caves", true); + } + else if ( currentlevel >= 31 && currentlevel <= 34 ) + { + onCompendiumLevelExit(playernum, "arcane citadel", true); + } + else if ( currentlevel == 35 ) + { + onCompendiumLevelExit(playernum, "citadel sanctum", true); + } + } + else + { + if ( currentlevel == 3 ) + { + onCompendiumLevelExit(playernum, "gnomish mines", true); + } + else if ( currentlevel == 4 ) + { + onCompendiumLevelExit(playernum, "minetown", true); + } + else if ( currentlevel == 8 ) + { + onCompendiumLevelExit(playernum, "temple", true); + } + else if ( currentlevel == 9 ) + { + onCompendiumLevelExit(playernum, "haunted castle", true); + } + else if ( currentlevel == 12 ) + { + onCompendiumLevelExit(playernum, "sokoban", true); + } + else if ( currentlevel == 14 ) + { + onCompendiumLevelExit(playernum, "minotaur maze", true); + } + else if ( currentlevel == 17 ) + { + onCompendiumLevelExit(playernum, "mystic library", true); + } + else if ( currentlevel == 19 || currentlevel == 20 ) + { + onCompendiumLevelExit(playernum, "underworld", true); + } + else if ( currentlevel == 6 || currentlevel == 7 ) + { + onCompendiumLevelExit(playernum, "underworld", true); + } + else if ( currentlevel == 29 ) + { + onCompendiumLevelExit(playernum, "cockatrice lair", true); + } + else if ( currentlevel == 34 ) + { + onCompendiumLevelExit(playernum, "brams castle", true); + } + } + + if ( !prevsecretfloor ) + { + if ( prevlevel == 5 || prevlevel == 10 || prevlevel == 15 + || prevlevel == 30 ) + { + onCompendiumLevelExit(playernum, "transition floor", false); + } + else if ( prevlevel >= 1 && prevlevel <= 4 ) + { + onCompendiumLevelExit(playernum, "mines", false); + if ( prevlevel == 4 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "mines", 1); + } + } + else if ( prevlevel >= 6 && prevlevel <= 9 ) + { + onCompendiumLevelExit(playernum, "swamps", false); + if ( prevlevel == 9 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "swamps", 1); + } + } + else if ( prevlevel >= 11 && prevlevel <= 14 ) + { + onCompendiumLevelExit(playernum, "labyrinth", false); + if ( prevlevel == 14 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "labyrinth", 1); + } + } + else if ( prevlevel >= 16 && prevlevel <= 19 ) + { + onCompendiumLevelExit(playernum, "ruins", false); + if ( prevlevel == 19 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "ruins", 1); + } + } + else if ( prevlevel == 20 ) + { + onCompendiumLevelExit(playernum, "herx lair", false); + } + else if ( prevlevel >= 21 && prevlevel <= 24 ) + { + onCompendiumLevelExit(playernum, "hell", false); + if ( prevlevel == 24 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "hell", 1); + } + } + else if ( prevlevel == 25 ) + { + onCompendiumLevelExit(playernum, "hamlet", false); + } + else if ( prevlevel >= 26 && prevlevel <= 29 ) + { + onCompendiumLevelExit(playernum, "crystal caves", false); + if ( prevlevel == 29 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "crystal caves", 1); + } + } + else if ( prevlevel >= 31 && prevlevel <= 34 ) + { + onCompendiumLevelExit(playernum, "arcane citadel", false); + if ( prevlevel == 34 ) + { + eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "arcane citadel", 1); + } + } + } + else + { + if ( prevlevel == 3 ) + { + onCompendiumLevelExit(playernum, "gnomish mines", false); + } + else if ( prevlevel == 4 ) + { + onCompendiumLevelExit(playernum, "minetown", false); + } + else if ( prevlevel == 8 ) + { + onCompendiumLevelExit(playernum, "temple", false); + } + else if ( prevlevel == 9 ) + { + onCompendiumLevelExit(playernum, "haunted castle", false); + } + else if ( prevlevel == 12 ) + { + onCompendiumLevelExit(playernum, "sokoban", false); + } + else if ( prevlevel == 14 ) + { + onCompendiumLevelExit(playernum, "minotaur maze", false); + } + else if ( prevlevel == 17 ) + { + onCompendiumLevelExit(playernum, "mystic library", false); + } + else if ( prevlevel == 19 || prevlevel == 20 ) + { + onCompendiumLevelExit(playernum, "underworld", false); + } + else if ( prevlevel == 6 || prevlevel == 7 ) + { + onCompendiumLevelExit(playernum, "underworld", false); + } + else if ( prevlevel == 29 ) + { + onCompendiumLevelExit(playernum, "cockatrice lair", false); + } + else if ( prevlevel == 34 ) + { + onCompendiumLevelExit(playernum, "brams castle", false); + } + } + } + } + + if ( !players[playernum]->isLocalPlayer() ) { return; } + + if ( multiplayer == SERVER && playernum == 0 ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + sendClientDataOverNet(i); + } + } + + ////std::set slots; + //int numItems = 0; + //for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) + //{ + // Item* item = (Item*)node->element; + // if ( !item ) + // { + // continue; // } // if ( itemCategory(item) == SPELL_CAT ) // { @@ -11212,9 +12091,22 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum) //Compendium_t::Events_t::eventUpdate(playernum, Compendium_t::CPDM_INVENTORY_QTY_MAX, CLOAK_BACKPACK, numItems); } +bool allowedCompendiumProgress() +{ + if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_CUSTOM_RUN && gameModeManager.currentSession.challengeRun.isActive() + && gameModeManager.currentSession.challengeRun.lid.find("challenge") != std::string::npos ) + { + return false; // challenge event run + } + return true; +} + +static ConsoleVariable cvar_compendiumDebugSave("/compendium_debug_save", false); + void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, const ItemType type, - const Sint32 value, const bool loadingValue) + const Sint32 value, const bool loadingValue, const int spellID) { + if ( !allowedCompendiumProgress() ) { return; } if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } @@ -11253,32 +12145,48 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } } } + else if ( def.clienttype == CLIENT_AND_SERVER ) + { + if ( multiplayer == CLIENT ) + { + if ( playernum == 0 ) + { + playernum = clientnum; // when a client receives an update from the server + } + } + } + } + + int itemType = type; + if ( type == SPELL_ITEM && spellID >= 0 ) + { + itemType = kEventSpellOffset + spellID; } - auto find2 = eventItemLookup[tag].find(type); + auto find2 = eventItemLookup[tag].find(itemType); if ( find2 == eventItemLookup[tag].end() ) { return; } auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; - if ( e.find(type) == e.end() ) + if ( e.find(itemType) == e.end() ) { - e[type] = EventVal_t(tag); + e[itemType] = EventVal_t(tag); } if ( def.oncePerRun && !loadingValue ) { - auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(type); + auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(itemType); if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) { // already present, skip adding return; } - players[playernum]->compendiumProgress.itemEvents[def.name][type] += value; + players[playernum]->compendiumProgress.itemEvents[def.name][itemType] += value; } - auto& val = e[type]; + auto& val = e[itemType]; if ( loadingValue ) { val.value = value; // reading from savefile @@ -11287,9 +12195,278 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con { if ( val.applyValue(value) ) { - if ( playernum == clientnum ) + if ( *cvar_compendiumDebugSave ) { - writeItemsSaveData(); + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } + } + } + } +} + +void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, + const Sint32 value, const bool loadingValue, const int entryID) +{ + if ( !allowedCompendiumProgress() ) { return; } + if ( intro && !loadingValue ) { return; } + if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } + + if ( multiplayer == SINGLE && playernum != 0 ) { return; } + + auto find = events.find(tag); + if ( find == events.end() ) + { + return; + } + auto& def = find->second; + + if ( !loadingValue ) + { + if ( def.clienttype == CLIENT_ONLY ) + { + if ( multiplayer != SINGLE ) + { + if ( playernum != clientnum ) + { + return; + } + } + } + else if ( def.clienttype == SERVER_ONLY ) + { + if ( multiplayer == CLIENT ) + { + if ( playernum == 0 ) + { + playernum = clientnum; // when a client receives an update from the server + } + else + { + return; + } + } + } + } + + int monsterType = -1; + std::string monsterStrLookup = ""; + if ( entryID >= 0 ) + { + monsterType = entryID; + } + else if ( entity && entity->behavior == &actDeathGhost ) + { + if ( monsterUniqueIDLookup.find("ghost") != monsterUniqueIDLookup.end() ) + { + monsterType = monsterUniqueIDLookup["ghost"]; + } + } + else if ( entity && entity->behavior == &actMonster ) + { + if ( auto stats = entity->getStats() ) + { + if ( stats->type == SHOPKEEPER && stats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 ) + { + monsterStrLookup = "mysterious shop"; + } + else + { + monsterStrLookup = stats->getAttribute("special_npc"); + } + + if ( monsterStrLookup != "" && monsterUniqueIDLookup.find(monsterStrLookup) != monsterUniqueIDLookup.end() ) + { + monsterType = monsterUniqueIDLookup[monsterStrLookup]; + } + else if ( monsterStrLookup == "" ) + { + monsterType = stats->type; + } + } + } + + if ( monsterType == -1 ) + { + return; + } + + if ( monsterType < kEventMonsterOffset ) + { + monsterType += kEventMonsterOffset; // convert to offset + } + + auto find2 = eventMonsterLookup[tag].find(monsterType); + if ( find2 == eventMonsterLookup[tag].end() ) + { + return; + } + + auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; + if ( e.find(monsterType) == e.end() ) + { + e[monsterType] = EventVal_t(tag); + } + + if ( def.oncePerRun && !loadingValue ) + { + auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(monsterType); + if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) + { + // already present, skip adding + return; + } + players[playernum]->compendiumProgress.itemEvents[def.name][monsterType] += value; + } + + auto& val = e[monsterType]; + if ( loadingValue ) + { + val.value = value; // reading from savefile + } + else + { + if ( val.applyValue(value) ) + { + if ( *cvar_compendiumDebugSave ) + { + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } + } + } + } +} + +void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag, const char* category, const Sint32 value, + const bool loadingValue, const int entryID) +{ + if ( !allowedCompendiumProgress() ) { return; } + if ( intro && !loadingValue ) { return; } + if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } + + if ( multiplayer == SINGLE && playernum != 0 ) { return; } + if ( multiplayer == SERVER && client_disconnected[playernum] ) + { + return; + } + + auto find = events.find(tag); + if ( find == events.end() ) + { + return; + } + auto& def = find->second; + + if ( !loadingValue ) + { + if ( def.clienttype == CLIENT_ONLY ) + { + if ( multiplayer != SINGLE ) + { + if ( playernum != clientnum ) + { + return; + } + } + } + else if ( def.clienttype == SERVER_ONLY ) + { + if ( multiplayer == CLIENT ) + { + if ( playernum == 0 ) + { + playernum = clientnum; // when a client receives an update from the server + } + else + { + return; + } + } + } + } + + int worldID = -1; + if ( entryID >= 0 ) + { + worldID = entryID; + bool foundCategory = false; + for ( auto cat : eventWorldLookup[tag] ) + { + auto find = eventWorldIDLookup.find(cat); + if ( find != eventWorldIDLookup.end() ) + { + if ( eventWorldIDLookup[cat] == worldID ) + { + foundCategory = true; + break; + } + } + } + if ( !foundCategory ) + { + return; + } + } + else + { + auto find2 = eventWorldLookup[tag].find(category); + if ( find2 == eventWorldLookup[tag].end() ) + { + return; + } + auto find = eventWorldIDLookup.find(category); + if ( find != eventWorldIDLookup.end() ) + { + worldID = find->second; + } + } + + if ( worldID == -1 ) + { + return; + } + + if ( worldID < kEventWorldOffset ) + { + worldID += kEventWorldOffset; // convert to offset + } + + + auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; + if ( e.find(worldID) == e.end() ) + { + e[worldID] = EventVal_t(tag); + } + + if ( def.oncePerRun && !loadingValue ) + { + auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(worldID); + if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) + { + // already present, skip adding + return; + } + players[playernum]->compendiumProgress.itemEvents[def.name][worldID] += value; + } + + auto& val = e[worldID]; + if ( loadingValue ) + { + val.value = value; // reading from savefile + } + else + { + if ( val.applyValue(value) ) + { + if ( *cvar_compendiumDebugSave ) + { + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } } } } @@ -11398,4 +12575,167 @@ void Compendium_t::Events_t::sendClientDataOverNet(const int playernum) // } //} } -} \ No newline at end of file +} + +void Compendium_t::readMonsterLimbsFromFile() +{ + std::string fullpath = "data/compendium/monster_models/"; + for ( auto f : directoryContents(fullpath.c_str(), false, true) ) + { + std::string inputPath = "/data/compendium/monster_models/" + f; + std::string path = PHYSFS_getRealDir(inputPath.c_str()) ? PHYSFS_getRealDir(inputPath.c_str()) : ""; + if ( path != "" ) + { + inputPath = path + inputPath; + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.HasMember("version") || !d.HasMember("limbs") || !d.HasMember("statue_id") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + int version = d["version"].GetInt(); + Uint32 statueId = d["statue_id"].GetUint(); + + std::string filename = f.substr(0, f.find(".json")); + auto& allLimbs = compendiumMonsterLimbs[filename]; + allLimbs.clear(); + + int index = 0; + for ( auto itr = d["limbs"].Begin(); itr != d["limbs"].End(); ++itr ) + { + /*if ( d.HasMember("height_offset") ) + { + statue.heightOffset = d["height_offset"].GetDouble(); + }*/ + + allLimbs.push_back(Entity(-1, 0, nullptr, nullptr)); + auto& limb = allLimbs[allLimbs.size() - 1]; + if ( index > 0 ) + { + limb.x = allLimbs[0].x - (*itr)["x"].GetDouble(); + limb.y = allLimbs[0].y - (*itr)["y"].GetDouble(); + } + else + { + limb.x = (*itr)["x"].GetDouble(); + limb.y = (*itr)["y"].GetDouble(); + } + limb.z = (*itr)["z"].GetDouble(); + limb.focalx = (*itr)["focalx"].GetDouble(); + limb.focaly = (*itr)["focaly"].GetDouble(); + limb.focalz = (*itr)["focalz"].GetDouble(); + limb.pitch = (*itr)["pitch"].GetDouble(); + limb.roll = (*itr)["roll"].GetDouble(); + limb.yaw = (*itr)["yaw"].GetDouble(); + limb.sprite = (*itr)["sprite"].GetInt(); + + ++index; + } + + printlog("[JSON]: Successfully read json file %s", inputPath.c_str()); + } + } +} + +void Compendium_t::exportCurrentMonster(Entity* monster) +{ + if ( !monster ) + { + return; + } + + int filenum = 0; + std::string monsterName = monster->getStats() ? monstertypename[monster->getStats()->type] : "nothing"; + std::string testPath = "/data/compendium/monster_models/" + monsterName + std::to_string(filenum) + ".json"; + while ( PHYSFS_getRealDir(testPath.c_str()) != nullptr && filenum < 1000 ) + { + ++filenum; + testPath = "/data/compendium/monster_models/" + monsterName + std::to_string(filenum) + ".json"; + } + + std::string exportFileName = monsterName + std::to_string(filenum) + ".json"; + + rapidjson::Document exportDocument; + exportDocument.SetObject(); + CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(1)); + CustomHelpers::addMemberToRoot(exportDocument, "statue_id", rapidjson::Value(local_rng.rand())); + CustomHelpers::addMemberToRoot(exportDocument, "height_offset", rapidjson::Value(0)); + rapidjson::Value limbsObject(rapidjson::kObjectType); + + rapidjson::Value limbsArray(rapidjson::kArrayType); + + std::vector allLimbs; + allLimbs.push_back(monster); + + for ( auto& bodypart : monster->bodyparts ) + { + allLimbs.push_back(bodypart); + } + + int index = 0; + for ( auto& limb : allLimbs ) + { + if ( limb->flags[INVISIBLE] ) + { + continue; + } + rapidjson::Value limbsObj(rapidjson::kObjectType); + + if ( index != 0 ) + { + limbsObj.AddMember("x", rapidjson::Value(monster->x - limb->x), exportDocument.GetAllocator()); + limbsObj.AddMember("y", rapidjson::Value(monster->y - limb->y), exportDocument.GetAllocator()); + limbsObj.AddMember("z", rapidjson::Value(limb->z), exportDocument.GetAllocator()); + } + else + { + limbsObj.AddMember("x", rapidjson::Value(0), exportDocument.GetAllocator()); + limbsObj.AddMember("y", rapidjson::Value(0), exportDocument.GetAllocator()); + limbsObj.AddMember("z", rapidjson::Value(limb->z), exportDocument.GetAllocator()); + } + limbsObj.AddMember("pitch", rapidjson::Value(limb->pitch), exportDocument.GetAllocator()); + limbsObj.AddMember("roll", rapidjson::Value(limb->roll), exportDocument.GetAllocator()); + limbsObj.AddMember("yaw", rapidjson::Value(limb->yaw), exportDocument.GetAllocator()); + limbsObj.AddMember("focalx", rapidjson::Value(limb->focalx), exportDocument.GetAllocator()); + limbsObj.AddMember("focaly", rapidjson::Value(limb->focaly), exportDocument.GetAllocator()); + limbsObj.AddMember("focalz", rapidjson::Value(limb->focalz), exportDocument.GetAllocator()); + limbsObj.AddMember("sprite", rapidjson::Value(limb->sprite), exportDocument.GetAllocator()); + limbsArray.PushBack(limbsObj, exportDocument.GetAllocator()); + + ++index; + } + + CustomHelpers::addMemberToRoot(exportDocument, "limbs", limbsArray); + + std::string outputPath = PHYSFS_getRealDir("/data/compendium/monster_models"); + outputPath.append(PHYSFS_getDirSeparator()); + std::string fileName = "data/compendium/monster_models/" + exportFileName; + outputPath.append(fileName.c_str()); + + File* fp = FileIO::open(outputPath.c_str(), "wb"); + if ( !fp ) + { + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + exportDocument.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + + return; +} +#endif \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 0d9f17780..87b5b346c 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2830,6 +2830,7 @@ class ItemTooltips_t std::map bookNameLocalizations; std::map spellNameLocalizations; std::map itemNameStringToItemID; + std::map spellNameStringToSpellID; std::string defaultString = ""; char buf[2048]; bool autoReload = false; @@ -2848,7 +2849,7 @@ class ItemTooltips_t std::string& getIconLabel(Item& item); std::string getSpellIconText(const int player, Item& item); std::string getSpellDescriptionText(const int player, Item& item); - std::string getSpellIconPath(const int player, Item& item); + std::string getSpellIconPath(const int player, Item& item, int spellID); std::string getCostOfSpellString(const int player, Item& item); std::string& getSpellTypeString(const int player, Item& item); node_t* getSpellNodeFromSpellID(int spellID); @@ -2857,7 +2858,7 @@ class ItemTooltips_t bool bIsSpellDamageOrHealingType(spell_t* spell); bool bSpellHasBasicHitMessage(const int spellID); - void formatItemIcon(const int player, std::string tooltipType, Item& item, std::string& str, int iconIndex, std::string& conditionalAttribute); + void formatItemIcon(const int player, std::string tooltipType, Item& item, std::string& str, int iconIndex, std::string& conditionalAttribute, Frame* parentFrame = nullptr); void formatItemDescription(const int player, std::string tooltipType, Item& item, std::string& str); void formatItemDetails(const int player, std::string tooltipType, Item& item, std::string& str, std::string detailTag); void stripOutPositiveNegativeItemDetails(std::string& str, std::string& positiveValues, std::string& negativeValues); @@ -3467,12 +3468,16 @@ struct Compendium_t std::vector abilities; std::vector inventory; std::string imagePath = ""; + std::vector models; }; static std::vector> contents; static std::map contentsMap; }; std::map monsters; void readMonstersFromFile(); + void exportCurrentMonster(Entity* monster); + void readMonsterLimbsFromFile(); + std::map> compendiumMonsterLimbs; struct CompendiumWorld_t { @@ -3482,6 +3487,7 @@ struct Compendium_t std::string imagePath = ""; std::vector blurb; std::vector details; + int id = -1; }; static std::vector> contents; static std::map contentsMap; @@ -3512,6 +3518,8 @@ struct Compendium_t { std::string name = ""; int rotation = 0; + int spellID = -1; + int effectID = -1; }; int modelIndex = -1; std::string imagePath = ""; @@ -3524,6 +3532,20 @@ struct Compendium_t std::map items; void readItemsFromFile(); + struct CompendiumMagic_t + { + static std::vector> contents; + static std::map contentsMap; + }; + std::map magic; + void readMagicFromFile(); + static Item compendiumItem; + static bool tooltipNeedUpdate; + static void updateTooltip(); + static SDL_Rect tooltipPos; + static Entity compendiumItemModel; + static Uint32 lastTickUpdate; + enum EventTags { CPDM_BLOCKED_ATTACKS, @@ -3570,6 +3592,85 @@ struct Compendium_t CPDM_THROWN_HITS, CPDM_SHOTS_HIT, CPDM_AMMO_HIT, + CPDM_MAGICSTAFF_RECHARGED, + CPDM_MAGICSTAFF_CASTS, + CPDM_FEATHER_ENSCRIBED, + CPDM_FEATHER_CHARGE_USED, + CPDM_FEATHER_SPELLBOOKS, + CPDM_CONSUMED_UNIDENTIFIED, + CPDM_SPELL_CASTS, + CPDM_SPELL_FAILURES, + CPDM_SPELLBOOK_CASTS, + CPDM_SPELLBOOK_LEARNT, + CPDM_KILLED_SOLO, + CPDM_KILLED_PARTY, + CPDM_KILLED_BY, + CPDM_GHOST_SPAWNED, + CPDM_GHOST_TELEPORTS, + CPDM_GHOST_PINGS, + CPDM_GHOST_PUSHES, + CPDM_MINEHEAD_ENTER, + CPDM_MINEHEAD_RETURN, + CPDM_GATE_OPENED_SPELL, + CPDM_GATE_MINOTAUR, + CPDM_LEVER_PULLED, + CPDM_LEVER_FOLLOWER_PULLED, + CPDM_DOOR_BROKEN, + CPDM_DOOR_OPENED, + CPDM_DOOR_UNLOCKED, + CPDM_LEVELS_ENTERED, + CPDM_LEVELS_EXITED, + CPDM_LEVELS_MAX_LVL, + CPDM_LEVELS_MAX_GOLD, + CPDM_SINKS_USED, + CPDM_SINKS_RINGS, + CPDM_SINKS_SLIMES, + CPDM_FOUNTAIN_FOOCUBI, + CPDM_FOUNTAIN_DRUNK, + CPDM_FOUNTAIN_BLESS, + CPDM_FOUNTAIN_BLESS_ALL, + CPDM_CHESTS_OPENED, + CPDM_CHESTS_MIMICS_AWAKENED, + CPDM_CHESTS_UNLOCKED, + CPDM_CHESTS_DESTROYED, + CPDM_BARRIER_DESTROYED, + CPDM_GRAVE_GHOULS, + CPDM_GRAVE_EPITAPHS_READ, + CPDM_GRAVE_EPITAPHS_PERCENT, + CPDM_GRAVE_GHOULS_ENSLAVED, + CPDM_SHOP_BOUGHT, + CPDM_SHOP_SOLD, + CPDM_SHOP_GOLD_EARNED, + CPDM_TRAP_KILLED_BY, + CPDM_TRAP_DAMAGE, + CPDM_TRAP_FOLLOWERS_KILLED, + CPDM_BOULDERS_PUSHED, + CPDM_ARROWS_PILFERED, + CPDM_SWIM_TIME, + CPDM_SWIM_KILLED_WHILE, + CPDM_SWIM_BURN_CURED, + CPDM_LAVA_DAMAGE, + CPDM_LAVA_ITEMS_BURNT, + CPDM_SOKOBAN_SOLVES, + CPDM_SOKOBAN_FASTEST_SOLVE, + CPDM_TRAP_MAGIC_STATUSED, + CPDM_OBELISK_USES, + CPDM_OBELISK_FOLLOWER_USES, + CPDM_TRIALS_ATTEMPTS, + CPDM_TRIALS_PASSED, + CPDM_TRIALS_DEATHS, + CPDM_LEVELS_BIOME_CLEAR, + CPDM_DOOR_CLOSED, + CPDM_SINKS_HEALTH_RESTORED, + CPDM_FOUNTAIN_USED, + CPDM_CHESTS_MIMICS_AWAKENED1ST, + CPDM_SHOP_SPENT, + CPDM_SOKOBAN_PERFECT_SOLVES, + CPDM_PITS_ITEMS_LOST, + CPDM_PITS_LEVITATED, + CPDM_PITS_DEATHS, + CPDM_PITS_ITEMS_VALUE_LOST, + CPDM_KILLED_MULTIPLAYER, CPDM_EVENT_TAGS_MAX }; @@ -3579,7 +3680,9 @@ struct Compendium_t { SUM, MAX, - AVERAGE_RANGE + AVERAGE_RANGE, + BITFIELD, + MIN }; enum ClientUpdateType { @@ -3613,20 +3716,33 @@ struct Compendium_t }; static std::map events; static std::map eventIdLookup; - static std::map> itemEventLookup; - static std::map> eventItemLookup; + static std::map> itemEventLookup; + static std::map monsterUniqueIDLookup; + static std::map> itemDisplayedEventsList; + static std::map> eventItemLookup; + static std::map> eventMonsterLookup; + static std::map> eventWorldLookup; + static std::map eventWorldIDLookup; + static std::map> eventLangEntries; static void readEventsFromFile(); static void writeItemsSaveData(); static void loadItemsSaveData(); - static void eventUpdate(int playernum, const EventTags tag, const ItemType type, const Sint32 val, const bool loadingValue = false); - static std::map> playerEvents; - static std::map> serverPlayerEvents[MAXPLAYERS]; - static void onLevelChangeEvent(const int playernum); - static void onVictoryEvent(const int playernum); + static void readEventsTranslations(); + static void createDummyClientData(const int playernum); + static void eventUpdate(int playernum, const EventTags tag, const ItemType type, const Sint32 value, const bool loadingValue = false, const int spellID = -1); + static void eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, const Sint32 value, const bool loadingValue = false, const int entryID = -1); + static void eventUpdateWorld(int playernum, const EventTags tag, const char* category, const Sint32 value, const bool loadingValue = false, const int entryID = -1); + static std::map> playerEvents; + static std::map> serverPlayerEvents[MAXPLAYERS]; + static void onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname); + static void onVictoryEvent(const int playernum, const bool tutorialend); static void sendClientDataOverNet(const int playernum); static std::map clientDataStrings[MAXPLAYERS]; static std::map> clientReceiveData; // todo clean up on end static Uint8 clientSequence; + static const int kEventSpellOffset = 10000; + static const int kEventMonsterOffset = 1000; + static const int kEventWorldOffset = 2000; }; }; diff --git a/src/monster_mimic.cpp b/src/monster_mimic.cpp index 4e5e976bc..37b913ed9 100644 --- a/src/monster_mimic.cpp +++ b/src/monster_mimic.cpp @@ -23,6 +23,7 @@ #include "magic/magic.hpp" #include "interface/interface.hpp" #include "prng.hpp" +#include "mod_tools.hpp" void initMimic(Entity* my, Stat* myStats) { @@ -913,6 +914,14 @@ bool Entity::disturbMimic(Entity* touched, bool takenDamage, bool doMessage) setEffect(EFF_STUNNED, true, 20, false); } monsterHitTime = HITRATE / 2; + if ( touched && touched->behavior == &actPlayer ) + { + if ( monsterSpecialState == MIMIC_INERT ) + { + Compendium_t::Events_t::eventUpdateWorld(touched->skill[2], Compendium_t::CPDM_CHESTS_MIMICS_AWAKENED1ST, "chest", 1); + } + Compendium_t::Events_t::eventUpdateWorld(touched->skill[2], Compendium_t::CPDM_CHESTS_MIMICS_AWAKENED, "chest", 1); + } } monsterSpecialState = MIMIC_ACTIVE; diff --git a/src/monster_minotaur.cpp b/src/monster_minotaur.cpp index e8edc3b17..066c08d30 100644 --- a/src/monster_minotaur.cpp +++ b/src/monster_minotaur.cpp @@ -22,6 +22,7 @@ #include "player.hpp" #include "colors.hpp" #include "prng.hpp" +#include "mod_tools.hpp" void initMinotaur(Entity* my, Stat* myStats) { @@ -1014,6 +1015,13 @@ void actMinotaurCeilingBuster(Entity* my) { playSoundEntity(entity, 76, 64); list_RemoveNode(entity->mynode); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GATE_MINOTAUR, "portcullis", 1); + } + } } } else if ( entity->behavior == &actStalagCeiling || diff --git a/src/monster_shadow.cpp b/src/monster_shadow.cpp index e6f056923..48910d89b 100644 --- a/src/monster_shadow.cpp +++ b/src/monster_shadow.cpp @@ -1549,7 +1549,7 @@ void Entity::shadowSpecialAbility(bool initialMimic) continue; } - spell_t *spell = getSpellFromItem(target->skill[2], item); //Do not free or delete this. + spell_t *spell = getSpellFromItem(target->skill[2], item, false); //Do not free or delete this. if ( !spell ) { continue; diff --git a/src/monster_succubus.cpp b/src/monster_succubus.cpp index fbb39ba91..86eaf4fb2 100644 --- a/src/monster_succubus.cpp +++ b/src/monster_succubus.cpp @@ -61,7 +61,13 @@ void initSuccubus(Entity* my, Stat* myStats) rng.rand() % 50 == 0 && !my->flags[USERFLAG2] && !myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS]; - if ( (boss || (*cvar_summonBosses && conductGameChallenges[CONDUCT_CHEATS_ENABLED])) && myStats->leader_uid == 0 ) + + if ( !strcmp(myStats->name, "Marishka") || !strcmp(myStats->name, "Aleera") + || !strcmp(myStats->name, "Verona") ) + { + myStats->setAttribute("special_npc", "bram succubi"); + } + else if ( (boss || (*cvar_summonBosses && conductGameChallenges[CONDUCT_CHEATS_ENABLED])) && myStats->leader_uid == 0 ) { myStats->setAttribute("special_npc", "lilith"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); diff --git a/src/net.cpp b/src/net.cpp index 83d0e5f75..0ce3c47d2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2087,6 +2087,10 @@ static void changeLevel() { closeChestClientside(clientnum); } + int prevcurrentlevel = currentlevel; + int prevsecretfloor = secretlevel; + std::string prevmapname = map.name; + // unlock some steam achievements if ( !secretlevel ) { @@ -2304,12 +2308,14 @@ static void changeLevel() { Player::Minimap_t::mapDetails.push_back(std::make_pair("map_flag_disable_hunger", "")); } - Compendium_t::Events_t::onLevelChangeEvent(clientnum); + Compendium_t::Events_t::onLevelChangeEvent(clientnum, prevcurrentlevel, prevsecretfloor, prevmapname); if ( gameModeManager.allowsSaves() ) { saveGame(); } + + Compendium_t::Events_t::writeItemsSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif @@ -3214,7 +3220,7 @@ static std::unordered_map clientPacketHandlers = { Item* item = (Item*)spellnode->element; if ( item && itemCategory(item) == SPELL_CAT ) { - spell_t* spell = getSpellFromItem(clientnum, item); + spell_t* spell = getSpellFromItem(clientnum, item, false); if ( spell && spell->ID == SPELL_CHARM_MONSTER ) { foundCharmSpell = true; @@ -5098,12 +5104,31 @@ static std::unordered_map clientPacketHandlers = { for ( auto itr2 = itr->value.MemberBegin(); itr2 != itr->value.MemberEnd(); ++itr2 ) { int itemType = std::stoi(itr2->name.GetString()); - if ( itemType < 0 || itemType >= NUMITEMS ) + Sint32 value = itr2->value.GetInt(); + if ( itemType >= Compendium_t::Events_t::kEventMonsterOffset && itemType < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) { + Compendium_t::Events_t::eventUpdateMonster(0, (Compendium_t::EventTags)id, nullptr, value, false, itemType); continue; } - Sint32 value = itr2->value.GetInt(); - Compendium_t::Events_t::eventUpdate(0, (Compendium_t::EventTags)id, (ItemType)itemType, value); + if ( itemType >= Compendium_t::Events_t::kEventWorldOffset && itemType < Compendium_t::Events_t::kEventWorldOffset + 1000 ) + { + Compendium_t::Events_t::eventUpdateWorld(0, (Compendium_t::EventTags)id, nullptr, value, false, + itemType - Compendium_t::Events_t::kEventWorldOffset); + continue; + } + if ( itemType < 0 || (itemType >= NUMITEMS && itemType < Compendium_t::Events_t::kEventSpellOffset) ) + { + continue; + } + if ( itemType >= Compendium_t::Events_t::kEventSpellOffset ) + { + Compendium_t::Events_t::eventUpdate(0, (Compendium_t::EventTags)id, SPELL_ITEM, value, false, + itemType - Compendium_t::Events_t::kEventSpellOffset); + } + else + { + Compendium_t::Events_t::eventUpdate(0, (Compendium_t::EventTags)id, (ItemType)itemType, value); + } } } } @@ -5112,6 +5137,8 @@ static std::unordered_map clientPacketHandlers = { } Compendium_t::Events_t::clientReceiveData.erase(clientSequence); + Compendium_t::Events_t::writeItemsSaveData(); + // reply got packet strcpy((char*)net_packet->data, "CMPD"); net_packet->data[4] = clientnum; @@ -5678,6 +5705,7 @@ static std::unordered_map serverPacketHandlers = { entity->sizex = 2; entity->sizey = 2; entity->flags[UPDATENEEDED] = true; + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_GHOST_SPAWNED, entity, 1); }}, // tried to update diff --git a/src/opengl.cpp b/src/opengl.cpp index ba8a51e4b..f1fac24f8 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -22,6 +22,7 @@ #include "player.hpp" #include "ui/MainMenu.hpp" #include "init.hpp" +#include "ui/Image.hpp" static real_t getLightAtModifier = 1.0; static real_t getLightAtAdder = 0.0; @@ -1804,7 +1805,7 @@ void glDrawSprite(view_t* camera, Entity* entity, int mode) static ConsoleVariable cvar_dmgSpriteDepthRange("/dmg_sprite_depth_range", 0.49); #endif // !EDITOR -void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode) +void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode, bool useTextAsImgPath, bool rotate) { if (!camera || !entity || text.empty()) { return; @@ -1830,15 +1831,35 @@ void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int else if (entity->behavior == &actSpriteNametag) { color = entity->skill[1]; } - auto rendered_text = Text::get( - text.c_str(), "fonts/pixel_maz.ttf#32#2", - color, makeColor(0, 0, 0, 255)); - auto textureId = rendered_text->getTexID(); - // bind texture - GL_CHECK_ERR(glBindTexture(GL_TEXTURE_2D, textureId)); - const GLfloat w = static_cast(rendered_text->getWidth()); - const GLfloat h = static_cast(rendered_text->getHeight()); + GLfloat w = 0.0; + GLfloat h = 0.0; + if ( useTextAsImgPath ) + { + if ( text.find('*') == std::string::npos ) + { + text.insert(text.begin(), '*'); + } + if ( auto imgGet = Image::get(text.c_str()) ) + { + // bind texture + GL_CHECK_ERR(glBindTexture(GL_TEXTURE_2D, imgGet->getTexID())); + w = imgGet->getWidth(); + h = imgGet->getHeight(); + } + } + else + { + auto rendered_text = Text::get( + text.c_str(), "fonts/pixel_maz.ttf#32#2", + color, makeColor(0, 0, 0, 255)); + auto textureId = rendered_text->getTexID(); + + // bind texture + GL_CHECK_ERR(glBindTexture(GL_TEXTURE_2D, textureId)); + w = static_cast(rendered_text->getWidth()); + h = static_cast(rendered_text->getHeight()); + } if (mode == REALCOLORS) { GL_CHECK_ERR(glEnable(GL_BLEND)); } @@ -1878,10 +1899,24 @@ void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int (void)rotate_mat(&m, &t, rotz, &i.z); t = m; // pitch (void)rotate_mat(&m, &t, rotx, &i.x); t = m; // roll } + v = vec4(entity->x * 2.f, -entity->z * 2.f - 1, entity->y * 2.f, 0.f); (void)translate_mat(&m, &t, &v); t = m; - (void)rotate_mat(&m, &t, entity->flags[OVERDRAW] ? -90.f : - -90.f - camera->ang * (180.f / PI), &i.y); t = m; + if ( rotate ) + { + float rotx, roty, rotz; + rotx = entity->roll * 180.0 / PI; // roll + roty = 360.0 - entity->yaw * 180.0 / PI; // yaw + rotz = 360.0 - entity->pitch * 180.0 / PI; // pitch + (void)rotate_mat(&m, &t, roty, &i.y); t = m; // yaw + (void)rotate_mat(&m, &t, rotz, &i.z); t = m; // pitch + (void)rotate_mat(&m, &t, rotx, &i.x); t = m; // roll + } + else + { + (void)rotate_mat(&m, &t, entity->flags[OVERDRAW] ? -90.f : + -90.f - camera->ang * (180.f / PI), &i.y); t = m; + } v = vec4(entity->focalx * 2.f, -entity->focalz * 2.f, entity->focaly * 2.f, 0.f); (void)translate_mat(&m, &t, &v); t = m; v = vec4(entity->scalex * w, entity->scaley * h, entity->scalez, 0.f); diff --git a/src/player.cpp b/src/player.cpp index da83faa17..4bf3f0ab7 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -5377,7 +5377,7 @@ void Player::Magic_t::setQuickCastSpellFromInventory(Item* item) { return; } - quick_cast_spell = getSpellFromItem(player.playernum, item); + quick_cast_spell = getSpellFromItem(player.playernum, item, true); } const bool Player::bUseCompactGUIWidth() const diff --git a/src/player.hpp b/src/player.hpp index c958d4191..48aacc4a4 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2268,7 +2268,7 @@ class Player { Player& player; public: - std::map> itemEvents; + std::map> itemEvents; CompendiumProgress_t(Player& p) : player(p) {}; ~CompendiumProgress_t() {}; diff --git a/src/scores.cpp b/src/scores.cpp index b056e8432..0dc5fc48d 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -6057,11 +6057,12 @@ int SaveGameInfo::populateFromSession(const int playernum) for ( auto& pair : ::players[c]->compendiumProgress.itemEvents ) { - player.compendium_item_events.push_back(std::make_pair(pair.first, std::vector>())); + player.compendium_item_events.push_back(std::make_pair(pair.first, std::vector())); auto& vec_entry = player.compendium_item_events.back(); for ( auto& itemValue : pair.second ) { - vec_entry.second.push_back(itemValue); + vec_entry.second.push_back(itemValue.first); + vec_entry.second.push_back(itemValue.second); } } @@ -6909,9 +6910,16 @@ int loadGame(int player, const SaveGameInfo& info) { auto& compendiumProgress = players[statsPlayer]->compendiumProgress; for ( auto& compendium_item_events : info.players[player].compendium_item_events ) { - for ( auto& itemValues : compendium_item_events.second ) + for ( auto itr = compendium_item_events.second.begin(); itr != compendium_item_events.second.end(); ) { - compendiumProgress.itemEvents[compendium_item_events.first][(ItemType)itemValues.first] = (Sint32)itemValues.second; + int first = *itr; + ++itr; + if ( itr != compendium_item_events.second.end() ) + { + Sint32 second = *itr; + compendiumProgress.itemEvents[compendium_item_events.first][first] = second; + } + ++itr; } } } diff --git a/src/scores.hpp b/src/scores.hpp index fbd8b2a0b..b995871c5 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -422,7 +422,7 @@ struct SaveGameInfo { } }; std::vector> shopkeeperHostility; - std::vector>>> compendium_item_events; + std::vector>> compendium_item_events; struct stat_t { struct item_t { diff --git a/src/shops.cpp b/src/shops.cpp index fb336ecf7..9f2e3056e 100644 --- a/src/shops.cpp +++ b/src/shops.cpp @@ -229,6 +229,8 @@ bool buyItemFromShop(const int player, Item* item, bool& bOutConsumedEntireStack shopChangeGoldEvent(player, -item->buyValue(player)); stats[player]->GOLD -= item->buyValue(player); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_BOUGHT, "shop", 1); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_SPENT, "shop", item->buyValue(player)); if ( stats[player]->playerRace > 0 && players[player] && players[player]->entity->effectPolymorph > NUMMONSTERS ) { @@ -610,6 +612,8 @@ bool sellItemToShop(const int player, Item* item) { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TRADING_GOLD_EARNED, item->type, item->sellValue(player)); Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TRADING_SOLD, item->type, 1); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_SOLD, "shop", 1); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_GOLD_EARNED, "shop", item->sellValue(player)); } if ( multiplayer != CLIENT ) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index fa5a057a0..16aa2193b 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -20668,7 +20668,7 @@ void updateSlotFrameFromItem(Frame* slotFrame, void* itemPtr, bool forceUnusable } else { - spell_t* spell = getSpellFromItem(player, item); + spell_t* spell = getSpellFromItem(player, item, true); if ( players[player]->magic.selectedSpell() == spell && (players[player]->magic.selected_spell_last_appearance == item->appearance || players[player]->magic.selected_spell_last_appearance == -1) ) { @@ -21551,20 +21551,50 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) } } + if ( !item->flags[SPRITE] ) + { + idleAngRotationDir = 0; + item->scalex = 1.0; + item->scaley = 1.0; + item->scalez = 1.0; + } + else + { + item->scalex = 0.25; + item->scaley = 0.25; + item->scalez = 0.25; + } + if ( idleAngRotationDir == 0 ) { - idleAngRotation += 0.005; - /*if ( idleAngRotation >= PI / 8 ) + if ( item->flags[SPRITE] ) { - idleAngRotationDir = 1; - }*/ + idleAngRotation += 0.0015; + if ( idleAngRotation >= 0.6 * PI ) + { + idleAngRotation = 0.6 * PI; + idleAngRotationDir = 1; + } + } + else + { + idleAngRotation += 0.005; + } } else { - idleAngRotation -= 0.01; - if ( idleAngRotation <= -PI / 8 ) + if ( item->flags[SPRITE] ) { - idleAngRotationDir = 0; + idleAngRotation -= 0.0015; + if ( idleAngRotation <= 0.4 * PI ) + { + idleAngRotation = 0.4 * PI; + idleAngRotationDir = 0; + } + } + else + { + idleAngRotation -= 0.0015; } } @@ -21592,7 +21622,15 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) if ( !dark ) { item->flags[BRIGHT] = true; } if ( !item->flags[INVISIBLE] ) { - glDrawVoxel(&view, item, REALCOLORS); + if ( item->flags[SPRITE] && item->skill[10] == SPELL_ITEM ) + { + glDrawSpriteFromImage(&view, item, ItemTooltips.getSpellIconPath(clientnum, Compendium_t::compendiumItem, item->skill[14]), + REALCOLORS, true, true); + } + else + { + glDrawVoxel(&view, item, REALCOLORS); + } } item->flags[BRIGHT] = b; @@ -21605,10 +21643,8 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) ::fov = ofov; } -void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark) +void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark) { - if ( !monster ) { return; } - static int fov = 50; static real_t ang = 0.0; static real_t vang = 0.0; @@ -21652,6 +21688,18 @@ void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool da auto ofov = ::fov; ::fov = fov; + std::vector* limbsArray = nullptr; + if ( CompendiumEntries.compendiumMonsterLimbs.find(modelsPath) != CompendiumEntries.compendiumMonsterLimbs.end() ) + { + limbsArray = &CompendiumEntries.compendiumMonsterLimbs[modelsPath]; + monster = &(limbsArray->at(0)); + } + + if ( !monster ) + { + return; + } + view.x = monster->x / 16.0 + ((.92 + zoom) * cos(offsetyaw + ang + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0))); view.y = monster->y / 16.0 + ((.92 + zoom) * sin(offsetyaw @@ -21678,6 +21726,27 @@ void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool da monster->flags[BRIGHT] = b; int c = 0; + if ( limbsArray ) + { + for ( auto itr = limbsArray->begin(); itr != limbsArray->end(); ++itr ) + { + if ( c == 0 ) + { + c++; + continue; + } + Entity* entity = &(*itr); + if ( !entity->flags[INVISIBLE] ) + { + bool b = entity->flags[BRIGHT]; + if ( !dark ) { entity->flags[BRIGHT] = true; } + glDrawVoxel(&view, entity, REALCOLORS); + entity->flags[BRIGHT] = b; + } + c++; + } + } + else { for ( node_t* node = monster->children.first; node != nullptr; node = node->next ) { @@ -27728,7 +27797,7 @@ void Player::HUD_t::updateXPBar() { xpProgressEndCap->path = playerXPCapPaths[xpPathNum][4]; } - else if ( xpProgressEndCap->path == playerXPCapPaths[player.playernum][4] ) + else if ( xpProgressEndCap->path == playerXPCapPaths[xpPathNum][4] ) { xpProgressEndCap->path = playerXPCapPaths[xpPathNum][0]; } diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index c33fdc3c9..5acec9082 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -24,7 +24,7 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spe void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); -void drawMonsterPreview(Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark = false); +void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark = false); void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index c8bdafb51..c40cf4cff 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32543,6 +32543,8 @@ namespace MainMenu { #endif static Entity* compendiumMonster = nullptr; + static std::pair compendiumMonsterCurrent = { "", ""}; // contents name, then model filename + static std::vector compendiumMonsterLimbs; static Entity* createCompendiumMonster(Monster creature, real_t x, real_t y) { if ( compendiumMonster ) @@ -32687,14 +32689,33 @@ namespace MainMenu { } }; static CompendiumTooltipFrames_t compendiumItemTooltip; + static std::string compendium_contents_current = ""; + static std::vector compendiumCategories = { + "monsters", + "items", + "magic", + "world", + "codex" + }; + static std::string compendium_current = "monsters"; static void refreshCompendiumEntryItemsBlurb(std::string name, Frame* parent) { - if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + if ( compendium_current == "items" ) { - return; + if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + { + return; + } + } + else + { + if ( CompendiumEntries.magic.find(name) == CompendiumEntries.magic.end() ) + { + return; + } } - auto& entry = CompendiumEntries.items[name]; + auto& entry = compendium_current == "items" ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; if ( Frame* page_left = parent->findFrame("page_left") ) { @@ -32711,14 +32732,95 @@ namespace MainMenu { } } - static Entity compendiumItemModel(-1, 0, nullptr, nullptr); - static Item compendiumItem; - static std::string compendium_contents_current = ""; + static void populateRecordsSectionItems(Frame* page_right, int itemType) + { + if ( !page_right ) { return; } + + if ( itemType < 0 || (itemType >= NUMITEMS && itemType < Compendium_t::Events_t::kEventSpellOffset) ) + { + return; + } + + if ( auto page_right_overlay = page_right->findFrame("page_right_overlay") ) + { + page_right_overlay->setDisabled(false); + auto rec1 = page_right_overlay->findField("record 1 txt"); + rec1->setDisabled(true); + auto rec1Val = page_right_overlay->findField("record 1 val"); + rec1Val->setDisabled(true); + auto rec2 = page_right_overlay->findField("record 2 txt"); + rec2->setDisabled(true); + auto rec2Val = page_right_overlay->findField("record 2 val"); + rec2Val->setDisabled(true); + auto rec3 = page_right_overlay->findField("record 3 txt"); + rec3->setDisabled(true); + auto rec3Val = page_right_overlay->findField("record 3 val"); + rec3Val->setDisabled(true); + auto rec4 = page_right_overlay->findField("record 4 txt"); + rec4->setDisabled(true); + auto rec4Val = page_right_overlay->findField("record 4 val"); + rec4Val->setDisabled(true); + + auto find = Compendium_t::Events_t::itemDisplayedEventsList.find(itemType); + if ( find != Compendium_t::Events_t::itemDisplayedEventsList.end() ) + { + int index = -1; + for ( auto tag : find->second ) + { + ++index; + Field* txt = nullptr; + Field* val = nullptr; + switch ( index ) + { + case 0: + txt = rec1; + val = rec1Val; + break; + case 1: + txt = rec2; + val = rec2Val; + break; + case 2: + txt = rec3; + val = rec3Val; + break; + case 3: + txt = rec4; + val = rec4Val; + break; + default: + break; + } + + if ( txt ) + { + txt->setDisabled(false); + txt->setText(Compendium_t::Events_t::eventLangEntries[tag]["default"].c_str()); + } + if ( val ) + { + val->setDisabled(false); + auto find = Compendium_t::Events_t::playerEvents[tag].find(itemType); + if ( find != Compendium_t::Events_t::playerEvents[tag].end() ) + { + val->setText(std::to_string(find->second.value).c_str()); + } + else + { + val->setText("-"); + } + } + } + } + } + } + static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner) { - compendiumItemModel.sprite = -1; - compendiumItemModel.focalz = 0.5; - compendiumItemModel.roll = 0.0; + Compendium_t::compendiumItemModel.sprite = -1; + Compendium_t::compendiumItemModel.focalz = 0.5; + Compendium_t::compendiumItemModel.roll = 0.0; + Compendium_t::compendiumItemModel.flags[SPRITE] = false; for ( auto f : page_right_inner.getFrames() ) { if ( auto txt = f->findField("item name") ) @@ -32726,7 +32828,13 @@ namespace MainMenu { if ( f == &toSelect ) { txt->setColor(compendiumContentsSelectedColor); - const int itemType = ItemTooltips.itemNameStringToItemID[toSelect.getName()]; + auto find = ItemTooltips.itemNameStringToItemID.find(toSelect.getName()); + int itemType = find != ItemTooltips.itemNameStringToItemID.end() ? find->second : -1; + if ( strstr(toSelect.getName(), "spell_") ) + { + itemType = SPELL_ITEM; + Compendium_t::compendiumItemModel.flags[SPRITE] = true; + } if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { int appearance = 0; @@ -32734,15 +32842,31 @@ namespace MainMenu { { appearance = (reinterpret_cast(toSelect.getUserData()) & 0x7F); } - for ( auto& i : CompendiumEntries.items[Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current]].items_in_category ) + + auto& contents = compendium_current == "items" ? CompendiumEntries.items[Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current]].items_in_category + : CompendiumEntries.magic[Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current]].items_in_category; + int itemLookupIndex = itemType; + for ( auto& i : contents ) { if ( i.name == toSelect.getName() ) { - compendiumItemModel.roll = (i.rotation * PI / 180.0); + Compendium_t::compendiumItemModel.roll = (i.rotation * PI / 180.0); + if ( itemType == SPELL_ITEM ) + { + if ( i.spellID >= 0 ) + { + itemLookupIndex = Compendium_t::Events_t::kEventSpellOffset + i.spellID; + } + } + break; } } - compendiumItemModel.sprite = items[itemType].index + appearance; - compendiumItemModel.skill[10] = itemType; + Compendium_t::compendiumItemModel.sprite = items[itemType].index + appearance; + Compendium_t::compendiumItemModel.skill[10] = itemType; + Compendium_t::compendiumItemModel.skill[14] = appearance; + + // find records for this item + populateRecordsSectionItems(page_right_inner.getParent(), itemLookupIndex); } } else @@ -32753,13 +32877,28 @@ namespace MainMenu { } } + static const int compendiumPageRightInnerY = 24; + static const int compendiumPageRightInnerHeight = 412 - 114 + 22; + static const int compendiumPageRightInnerHeightExpanded = 412; static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) { - if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + if ( compendium_current == "items" ) { - return; + if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + { + return; + } } - auto& entry = CompendiumEntries.items[name]; + else + { + if ( CompendiumEntries.magic.find(name) == CompendiumEntries.magic.end() ) + { + return; + } + } + auto& entry = (compendium_current == "items") ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; + + if ( Frame* page_right = parent->findFrame("page_right") ) { if ( page_right = page_right->findFrame("page_right_inner") ) @@ -32770,9 +32909,9 @@ namespace MainMenu { } const int entrySize = 64; - compendiumItem.status = EXCELLENT; - compendiumItem.count = 1; - compendiumItem.identified = true; + Compendium_t::compendiumItem.status = EXCELLENT; + Compendium_t::compendiumItem.count = 1; + Compendium_t::compendiumItem.identified = true; if ( !compendiumItemTooltip.tooltipContainerFrame ) { @@ -32787,7 +32926,7 @@ namespace MainMenu { } compendiumItemTooltip.clear(); - page_right->setTickCallback([](Widget& widget) { + /*page_right->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); if ( auto parent = frame->getParent() ) { @@ -32796,7 +32935,7 @@ namespace MainMenu { players[0]->inventoryUI.updateInventoryItemTooltip(parent); } } - }); + });*/ for ( int i = 0; i < entry.items_in_category.size(); ++i ) { @@ -32804,7 +32943,7 @@ namespace MainMenu { auto entry = page_right->addFrame(data.name.c_str()); entry->setHollow(true); entry->setClickable(false); - entry->setSize(SDL_Rect{ 8, 8 + i * entrySize, page_right->getSize().w - 32, entrySize }); + entry->setSize(SDL_Rect{ 8, 0 + i * entrySize, page_right->getSize().w - 32, entrySize }); /*Button* btn = entry->addButton("item btn"); btn->setSize(entry->getSize()); btn->setText("click");*/ @@ -32839,9 +32978,8 @@ namespace MainMenu { { if ( parent = parent->getParent() ) { - SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( frame->capturesMouseInRealtimeCoords() ) + if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) { if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) { @@ -32871,16 +33009,21 @@ namespace MainMenu { if ( hovered ) { //frame->select(); - - const int itemType = ItemTooltips.itemNameStringToItemID[widget.getName()]; + const int itemType = strstr(widget.getName(), "spell_") ? SPELL_ITEM + : ItemTooltips.itemNameStringToItemID[widget.getName()]; if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - compendiumItem.type = (ItemType)itemType; + Compendium_t::compendiumItem.type = (ItemType)itemType; if ( frame->getUserData() ) { - compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); + Compendium_t::compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); } - players[0]->hud.updateFrameTooltip(&compendiumItem, absolutePos.x - 16, absolutePos.y, Player::PANEL_JUSTIFY_RIGHT, parent); + + Compendium_t::tooltipPos.x = absolutePos.x - 16; + Compendium_t::tooltipPos.y = absolutePos.y; + Compendium_t::tooltipNeedUpdate = true; + + page_right_inner->setAllowScrollBinds(false); } } } @@ -32889,7 +33032,9 @@ namespace MainMenu { } } }); - const int id = ItemTooltips.itemNameStringToItemID[data.name]; + + const int id = strstr(data.name.c_str(), "spell_") ? SPELL_ITEM + : ItemTooltips.itemNameStringToItemID[data.name]; auto itemBg = entry->addImage(SDL_Rect{ 8, 8, 48, 48 }, 0xFFFFFFFF, "*images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_02.png", "item bg"); @@ -32898,19 +33043,66 @@ namespace MainMenu { auto itemName = entry->addField("item name", 128); itemName->setFont(smallfont_outline); - itemName->setSize(SDL_Rect{ itemBg->pos.x + itemBg->pos.w + 4, 8, entry->getSize().w - 8, 24 }); - std::string name = items[id].getIdentifiedName(); - camelCaseString(name); - itemName->setText(name.c_str()); + itemName->setSize(SDL_Rect{ itemBg->pos.x + itemBg->pos.w + 4, 8, entry->getSize().w - 8, 48 }); + if ( id == SPELL_ITEM ) + { + if ( auto spell = getSpellFromID(data.spellID) ) + { + std::string name = spell->getSpellName(); + camelCaseString(name); + if ( auto s = getSpellFromID(data.spellID) ) + { + name += '\n'; + char buf[64]; + snprintf(buf, sizeof(buf), Language::get(6172), + s->difficulty, ItemTooltips.getProficiencyLevelName(spell->difficulty).c_str()); + name += buf; + } + itemName->setText(name.c_str()); + for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line + { + itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + } + } + } + else + { + std::string name = items[id].getIdentifiedName(); + camelCaseString(name); + name += '\n'; + char buf[64]; + if ( items[id].level >= 0 ) + { + snprintf(buf, sizeof(buf), "%s %d", Language::get(6173), items[id].level); + } + else + { + snprintf(buf, sizeof(buf), "%s ???", Language::get(6173)); + } + name += buf; + itemName->setText(name.c_str()); + for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line + { + itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + } + } - compendiumItem.type = (ItemType)id; - compendiumItem.appearance = local_rng.rand() % items[id].variations; + Compendium_t::compendiumItem.type = (ItemType)id; + Compendium_t::compendiumItem.appearance = local_rng.rand() % items[id].variations; if ( id == TOOL_PLAYER_LOOT_BAG ) { - compendiumItem.appearance = local_rng.rand() % 4; + Compendium_t::compendiumItem.appearance = local_rng.rand() % 4; + } + if ( id == SPELL_ITEM ) + { + itemImg->path = ItemTooltips.getSpellIconPath(clientnum, Compendium_t::compendiumItem, data.spellID); + Compendium_t::compendiumItem.appearance = data.spellID; + } + else + { + itemImg->path = getItemSpritePath(0, Compendium_t::compendiumItem); } - itemImg->path = getItemSpritePath(0, compendiumItem); - Uint8 userData = std::min((Uint8)127, (Uint8)(0x7F & compendiumItem.appearance)); + Uint8 userData = std::min((Uint8)127, (Uint8)(0x7F & Compendium_t::compendiumItem.appearance)); if ( i == 0 ) { userData |= 1 << 7; @@ -32924,7 +33116,7 @@ namespace MainMenu { } SDL_Rect actualPos = page_right->getActualSize(); - actualPos.h = std::max(412, entry->getSize().y + entry->getSize().h); + actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); page_right->setActualSize(actualPos); } //page_right->setSelection(0); @@ -32981,9 +33173,11 @@ namespace MainMenu { return; } auto& entry = CompendiumEntries.monsters[name]; - if ( compendiumMonster ) + compendiumMonsterCurrent.first = name; + compendiumMonsterCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; + if ( true /*compendiumMonster */ ) { - if ( auto myStats = compendiumMonster->getStats() ) + if ( true /*auto myStats = compendiumMonster->getStats()*/ ) { if ( Frame* page_left = parent->findFrame("page_left") ) { @@ -33110,7 +33304,7 @@ namespace MainMenu { char buf[32] = ""; if ( false ) { - real_t val = (100.0 * Entity::getDamageTableMultiplier(nullptr, *myStats, (DamageTableType)pair.second)); + real_t val = (100.0 * damagetables[entry.monsterType][(DamageTableType)pair.second]); if ( val > 100.01 ) { field->setColor(hudColors.characterSheetGreen); @@ -33131,7 +33325,7 @@ namespace MainMenu { } else { - real_t val = (100.0 * Entity::getDamageTableMultiplier(nullptr, *myStats, (DamageTableType)pair.second)); + real_t val = (100.0 * damagetables[entry.monsterType][(DamageTableType)pair.second]); if ( val > 100.01 ) { field->setColor(hudColors.characterSheetGreen); @@ -33256,7 +33450,7 @@ namespace MainMenu { inv->setSize(invPos); SDL_Rect actualPos = page_right->getActualSize(); - actualPos.h = std::max(412, invPos.y + invPos.h); + actualPos.h = std::max(compendiumPageRightInnerHeight, invPos.y + invPos.h); page_right->setActualSize(actualPos); } } @@ -33268,15 +33462,6 @@ namespace MainMenu { } } - static std::vector compendiumCategories = { - "monsters", - "items", - "magic", - "world", - "codex" - }; - static std::string compendium_current = "monsters"; - static auto contents_activate_fn = [](Frame::entry_t& entry) { assert(main_menu_frame); @@ -33301,12 +33486,17 @@ namespace MainMenu { slider->setValue(0); slider->getCallback()(*slider); } + if ( Frame* page_right_overlay = page_right->findFrame("page_right_overlay") ) + { + page_right_overlay->setDisabled(true); + } } auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents : nullptr))); + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents : nullptr)))); std::string content = ""; if ( entries ) @@ -33334,6 +33524,11 @@ namespace MainMenu { refreshCompendiumEntryItemsBlurb(content, compendiumFrame); refreshCompendiumEntryItemsList(content, compendiumFrame); } + else if ( compendium_current == "magic" ) + { + refreshCompendiumEntryItemsBlurb(content, compendiumFrame); + refreshCompendiumEntryItemsList(content, compendiumFrame); + } else if ( compendium_current == "world" ) { refreshCompendiumEntryWorld(content, compendiumFrame); @@ -33346,8 +33541,8 @@ namespace MainMenu { { myStats->setAttribute("monster_portrait", "true"); } - refreshCompendiumEntryMonster(content, compendiumFrame); } + refreshCompendiumEntryMonster(content, compendiumFrame); } }; @@ -33358,7 +33553,8 @@ namespace MainMenu { auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents : nullptr))); + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents : nullptr)))); auto toRemove = contents->getEntries(); for ( auto r : toRemove ) @@ -33396,10 +33592,101 @@ namespace MainMenu { if ( !page_right_inner ) { page_right_inner = page_right->addFrame("page_right_inner"); - page_right_inner->setSize(SDL_Rect{ 10, 28, 362, 412 }); - page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, 412 }); + page_right_inner->setSize(SDL_Rect{ 10, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); + page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); + page_right_inner->setTickCallback([](Widget& widget) { + static_cast(&widget)->setAllowScrollBinds(true); + }); } + Frame* page_right_overlay = page_right->findFrame("page_right_overlay"); + if ( !page_right_overlay ) + { + if ( page_right_overlay = page_right->addFrame("page_right_overlay") ) + { + page_right_overlay->setSize(SDL_Rect{ 6, page_right->getSize().h - 114 - 14, page_right->getSize().w - 16, 120 }); + page_right_overlay->addImage(SDL_Rect{ 0, 0, page_right_overlay->getSize().w, page_right_overlay->getSize().h }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Records_Frame_00.png", "page right overlay img"); + + auto heading = page_right_overlay->addField("records txt", 32); + heading->setFont(menu_option_font); + heading->setText("RECORDS"); + heading->setHJustify(Field::justify_t::LEFT); + heading->setVJustify(Field::justify_t::TOP); + heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); + heading->setColor(makeColor(198, 190, 179, 255)); + + const int recordSpacing = 20; + const int recordTextOffsetW = 36; + auto record1 = page_right_overlay->addField("record 1 txt", 128); + record1->setFont(smallfont_outline); + record1->setText(""); + record1->setHJustify(Field::justify_t::LEFT); + record1->setVJustify(Field::justify_t::TOP); + record1->setSize(SDL_Rect{ 18, 24 + 11, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); + record1->setColor(makeColor(135, 94, 45, 255)); + + auto record1val = page_right_overlay->addField("record 1 val", 64); + record1val->setFont(smallfont_outline); + record1val->setText(""); + record1val->setHJustify(Field::justify_t::RIGHT); + record1val->setVJustify(Field::justify_t::TOP); + record1val->setSize(record1->getSize()); + record1val->setColor(makeColor(159, 145, 127, 255)); + + auto record2 = page_right_overlay->addField("record 2 txt", 128); + record2->setFont(smallfont_outline); + record2->setText(""); + record2->setHJustify(Field::justify_t::LEFT); + record2->setVJustify(Field::justify_t::TOP); + record2->setSize(SDL_Rect{ 18, record1->getSize().y + recordSpacing, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); + record2->setColor(makeColor(135, 94, 45, 255)); + + auto record2val = page_right_overlay->addField("record 2 val", 64); + record2val->setFont(smallfont_outline); + record2val->setText(""); + record2val->setHJustify(Field::justify_t::RIGHT); + record2val->setVJustify(Field::justify_t::TOP); + record2val->setSize(record2->getSize()); + record2val->setColor(makeColor(159, 145, 127, 255)); + + auto record3 = page_right_overlay->addField("record 3 txt", 128); + record3->setFont(smallfont_outline); + record3->setText(""); + record3->setHJustify(Field::justify_t::LEFT); + record3->setVJustify(Field::justify_t::TOP); + record3->setSize(SDL_Rect{ 18, record2->getSize().y + recordSpacing, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); + record3->setColor(makeColor(135, 94, 45, 255)); + + auto record3val = page_right_overlay->addField("record 3 val", 64); + record3val->setFont(smallfont_outline); + record3val->setText(""); + record3val->setHJustify(Field::justify_t::RIGHT); + record3val->setVJustify(Field::justify_t::TOP); + record3val->setSize(record3->getSize()); + record3val->setColor(makeColor(159, 145, 127, 255)); + + auto record4 = page_right_overlay->addField("record 4 txt", 128); + record4->setFont(smallfont_outline); + record4->setText(""); + record4->setHJustify(Field::justify_t::LEFT); + record4->setVJustify(Field::justify_t::TOP); + record4->setSize(SDL_Rect{ 18, record3->getSize().y + recordSpacing, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); + record4->setColor(makeColor(135, 94, 45, 255)); + + auto record4val = page_right_overlay->addField("record 4 val", 64); + record4val->setFont(smallfont_outline); + record4val->setText(""); + record4val->setHJustify(Field::justify_t::RIGHT); + record4val->setVJustify(Field::justify_t::TOP); + record4val->setSize(record4->getSize()); + record4val->setColor(makeColor(159, 145, 127, 255)); + } + } + + page_right_overlay->setDisabled(true); + int padx = 10; int pady = 0; @@ -33750,6 +34037,11 @@ namespace MainMenu { } } + static ConsoleCommand ccmd_compendiumExportMonster("/compendium_export_monster", "exports current monster in view", + [](int argc, const char** argv) { + CompendiumEntries.exportCurrentMonster(compendiumMonster); + }); + static void openCompendium() { auto dimmer = main_menu_frame->addFrame("dimmer"); dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); @@ -33776,10 +34068,51 @@ namespace MainMenu { ); { + static auto constexpr tabTextColorActive = makeColorRGB(220, 178, 113); + static auto constexpr tabTextColorInactive = makeColorRGB(192, 192, 192); + Button* tab = window->addButton(compendiumCategories[0].c_str()); tab->setSize(SDL_Rect{ background->pos.x + 86, background->pos.y + background->pos.h - 30, 96, 66 }); - tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Denizens_00.png"); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensInactiveHi_00.png"); + } + }); + + const int tab_title_y = 4; + Field* tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6174)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "monsters"; @@ -33797,10 +34130,48 @@ namespace MainMenu { } }); + tab = window->addButton(compendiumCategories[1].c_str()); tab->setSize(SDL_Rect{ background->pos.x + 190, background->pos.y + background->pos.h - 30, 88, 76 }); tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Items_00.png"); tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsInactiveHi_00.png"); + } + }); + + tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6175)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "items"; if ( auto frame = static_cast(button.getParent()) ) @@ -33821,14 +34192,100 @@ namespace MainMenu { tab->setSize(SDL_Rect{ background->pos.x + 286, background->pos.y + background->pos.h - 30, 96, 64 }); tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Magic_00.png"); tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicInactiveHi_00.png"); + } + }); + + tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6176)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "magic"; + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + compendiumPopulatePageRight(page_right); + } + compendiumPopulateContents(frame); + } }); tab = window->addButton(compendiumCategories[3].c_str()); tab->setSize(SDL_Rect{ background->pos.x + 580, background->pos.y + background->pos.h - 30, 78, 80 }); tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_World_00.png"); tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldInactiveHi_00.png"); + } + }); + + tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6177)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "world"; if ( auto frame = static_cast(button.getParent()) ) @@ -33849,6 +34306,43 @@ namespace MainMenu { tab->setSize(SDL_Rect{ background->pos.x + 672, background->pos.y + background->pos.h - 30, 84, 80 }); tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Codex_00.png"); tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexInactiveHi_00.png"); + } + }); + + tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6178)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "codex"; if ( auto frame = static_cast(button.getParent()) ) @@ -33945,11 +34439,15 @@ namespace MainMenu { model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { if ( compendium_current == "monsters" ) { - drawMonsterPreview(compendiumMonster, pos, 0.0); + drawMonsterPreview(compendiumMonsterCurrent.first, compendiumMonsterCurrent.second, compendiumMonster, pos, 0.0); } else if ( compendium_current == "items" ) { - drawItemPreview(&compendiumItemModel, pos, 0.0); + drawItemPreview(&Compendium_t::compendiumItemModel, pos, 0.0); + } + else if ( compendium_current == "magic" ) + { + drawItemPreview(&Compendium_t::compendiumItemModel, pos, 0.0); } }); model_viewer->setTickCallback([](Widget& widget) { @@ -33957,6 +34455,7 @@ namespace MainMenu { { compendiumMonster->attack(compendiumMonster->getAttackPose(), 0, nullptr); }*/ + if ( ticks % TICKS_PER_SECOND/2 == 0 ) { if ( compendium_current == "monsters" ) @@ -33977,8 +34476,15 @@ namespace MainMenu { else if ( compendium_current == "items" ) { CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); } + else if ( compendium_current == "magic" ) + { + CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); + refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } } /*if ( keystatus[SDLK_g] ) { @@ -34036,8 +34542,11 @@ namespace MainMenu { "*images/ui/Main Menus/AdventureArchives/C_Details_Frame_00.png", "page right img"); auto page_right_inner = page_right->addFrame("page_right_inner"); - page_right_inner->setSize(SDL_Rect{ 10, 28, 362, 412 }); - page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, 412 }); + page_right_inner->setSize(SDL_Rect{ 10, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); + page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); + page_right_inner->setTickCallback([](Widget& widget) { + static_cast(&widget)->setAllowScrollBinds(true); + }); auto page_right_title = window->addField("page_right_title", 128); page_right_title->setFont("fonts/kongtext.ttf#16#0"); @@ -34049,8 +34558,8 @@ namespace MainMenu { auto right_slider_bg = page_right->addImage(SDL_Rect{ page_right_inner->getSize().x + page_right_inner->getSize().w - 18, - page_right_inner->getSize().y + 16, - 16, 412 }, + page_right_inner->getSize().y + 20, + 16, compendiumPageRightInnerHeight }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Details_ScrollBar_01.png", "right_slider_bg"); @@ -34058,7 +34567,7 @@ namespace MainMenu { auto right_slider = page_right->addSlider("right_slider"); right_slider->setRailSize(SDL_Rect{ page_right_inner->getSize().x + page_right_inner->getSize().w - 20, - page_right_inner->getSize().y + 16 + 4, 20, 404 }); + page_right_inner->getSize().y + 20 + 4, 20, compendiumPageRightInnerHeight - 8 }); //right_slider->setRailImage("*images/ui/Main Menus/AdventureArchives/C_Details_ScrollBar_00.png"); right_slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 }); right_slider->setBorder(16); @@ -34093,6 +34602,7 @@ namespace MainMenu { } }); right_slider->setTickCallback([](Widget& widget) { + auto slider = static_cast(&widget); if ( auto frame = static_cast(widget.getParent()) ) { if ( frame = frame->findFrame("page_right_inner") ) @@ -34102,13 +34612,12 @@ namespace MainMenu { widget.setInvisible(false); const int diff = frame->getActualSize().h - frame->getSize().h; - auto slider = static_cast(&widget); slider->setValue(100.0 * frame->getActualSize().y / diff); } else { widget.setInvisible(true); - auto slider = static_cast(&widget); + slider->setValue(0); if ( widget.isSelected() ) { From bad113a3b0dc99d78a8b8756c82ebf7614561328 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 8 Jun 2024 02:00:44 +1000 Subject: [PATCH 017/244] * editor can take xres/yres as arguments because scaling is broken --- src/editor.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/editor.cpp b/src/editor.cpp index a339a2761..9d8ce75b4 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1228,6 +1228,22 @@ void processCommandLine(int argc, char** argv) strncpy(datadir, argv[c] + 9, datadirsz); datadir[datadirsz] = '\0'; } + else if ( !strncmp(argv[c], "-xres=", 6) ) + { + char buf[32]; + size_t len = std::min(sizeof(buf), strlen(argv[c] + 6)); + strncpy(buf, argv[c] + 6, len); + buf[len] = '\0'; + xres = atoi(buf); + } + else if ( !strncmp(argv[c], "-yres=", 6) ) + { + char buf[32]; + size_t len = std::min(sizeof(buf), strlen(argv[c] + 6)); + strncpy(buf, argv[c] + 6, len); + buf[len] = '\0'; + yres = atoi(buf); + } } } } From 49200d2b852b3aac5133d020063e170927fcff3c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 8 Jun 2024 02:03:38 +1000 Subject: [PATCH 018/244] * more lang --- lang/en.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index df2cc6f2e..171867351 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6397,5 +6397,12 @@ to participate.# 6171 Unlock the Achievement: "%s" to play this class combination.# +6172 Magic Required: %d (%s)# +6173 Dungeon Floor:# +6174 Denizens# +6175 Items# +6176 Magic# +6177 World# +6178 Codex# 6200 END# From 69d94b9a4a7cbea6e40fc65da29599100a3b4995 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 8 Jun 2024 02:16:34 +1000 Subject: [PATCH 019/244] * misc lang update --- lang/en.txt | 120 ++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/lang/en.txt b/lang/en.txt index 171867351..d5d805006 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -320,9 +320,9 @@ create an aura of strife around a person.# 217 Lamp oil? Rope? Bombs? Dost -thou want it? It is thine my +thou want it? It is thine, my friend, so long as thou -havest enough rubies!# +hast enough rubies!# 218 They say some items are enchanted with special @@ -349,7 +349,7 @@ off over time.# 224 Beware of picture books. I knew a person who touched -one and, suddenly, they were +one, and suddenly, they were all alone on a tiny island.# 225 Thy skills shall improve as @@ -721,7 +721,7 @@ thou makest use of them.# 486 "Here lies my wife Here let her lie! - Now she's at rest and so am I."# + Now she's at rest and so am I"# 487 "Here lies the body of Blake Hit the gas instead of the brake"# @@ -1179,7 +1179,7 @@ Thank you!# # mail 776 This is a letter: - "This is Moe. I like you Maggie... + "This is Moe. I like you, Maggie... so much that... I want to... eat you... for dinner."# @@ -1317,7 +1317,7 @@ Thank you!# 867 Your food pouch feels lighter!# 868 A map coalesces in your mind!# 869 Huh? What? Where am I?# -870 Your feel a tingling sensation, but it passes.# +870 You feel a tingling sensation, but it passes.# 871 Your %s vibrates.# 872 Your %s looks better!# 873 Your skin itches for a moment.# @@ -1455,7 +1455,7 @@ Thank you!# 982 a broken %+2d # 983 a decrepit %+2d # 984 a worn %+2d # -985 a servicable %+2d # +985 a serviceable %+2d # 986 an excellent %+2d # 987 a destroyed %+2d # 988 a cracked %+2d # @@ -1481,7 +1481,7 @@ Thank you!# 1008 %d broken %+2d # 1009 %d decrepit %+2d # 1010 %d worn %+2d # -1011 %d servicable %+2d # +1011 %d serviceable %+2d # 1012 %d excellent %+2d # 1013 %d destroyed %+2d # 1014 %d cracked %+2d # @@ -1507,7 +1507,7 @@ Thank you!# 1034 a broken # 1035 a decrepit # 1036 a worn # -1037 a servicable # +1037 a serviceable # 1038 an excellent # 1039 a destroyed # 1040 a cracked # @@ -1533,7 +1533,7 @@ Thank you!# 1060 %d broken # 1061 %d decrepit # 1062 %d worn # -1063 %d servicable # +1063 %d serviceable # 1064 %d excellent # 1065 %d destroyed # 1066 %d cracked # @@ -1596,7 +1596,7 @@ Thank you!# 1110 You pull the lever.# 1111 Server flags updated.# 1112 Run, mortal! - The Baphomet dies, but his minions linger. + Baphomet dies, but his minions linger. We impart our power upon you!# 1113 BEWARE! You've unleashed the Minotaur!# @@ -1645,7 +1645,7 @@ Press 'Okay' to return to the main menu.# Progress has been saved at the start of the current level.# 1132 Requesting lobby list...# -1133 You have died. Gameover. +1133 You have died. Game over. # 1134 Your equipment has been identified. @@ -1691,7 +1691,7 @@ Rats make a good food source early on.# Do not be discouraged. Learn from your mistakes and you will eventually succeed.# 1150 -Hold the left-mouse button to perform a +Hold the left mouse button to perform a critical strike.# 1151 Raise your shield for a large defensive boost.# @@ -1957,7 +1957,7 @@ Compatible save file not found.# 1521 gets mugged by a greedy gnome.# 1522 gets quartered by a demon.# 1523 tries vainly to make love to the succubus.# -1524 gets on the Baron's bad-side.# +1524 gets on the Baron's bad side.# 1525 is unceremoniously pulverized by the minotaur.# 1526 is damned to hell. Literally.# 1527 attempts a robbery and fails.# @@ -2784,7 +2784,7 @@ but it retracts beyond your might!# #New effects (contd.) 2451 You are bleeding heavily!# 2452 The %s is bleeding heavily!# -2453 %s's is bleeding heavily!# +2453 %s is bleeding heavily!# 2454 Your wounds rupture further open!# 2455 The %s's wounds rupture further open!# 2456 %s's wounds rupture further open!# @@ -2995,7 +2995,7 @@ last of your magic is drained!# 2631 "Mmm. No."# 2632 "The Kobolds guaranteed us better results in the Caves."# -2633 "Don't worry, brother. There's still a ways to go, they may yet deliver."# +2633 "Don't worry, Brother. There's still a ways to go, they may yet deliver."# 2634 "Brother, I admit, I wish they'd hurry."# 2635 "It would make things simpler, wouldn't it?"# @@ -3011,7 +3011,7 @@ last of your magic is drained!# 2653 "Let them come."# 2640 "How much longer must I wait around?"# -2641 "Go about your business, brother. There isn't much to prepare."# +2641 "Go about your business, Brother. There isn't much to prepare."# 2642 "You were right to exclude the rest of the guild from our plans here, Brother."# 2643 "Small minds always have small ambitions."# @@ -3063,7 +3063,7 @@ last of your magic is drained!# 2731 %s It's a secret to everybody.# 2732 %s - This used to be the foyer to the Baron's mansion.# + This used to be the foyer of the Baron's mansion.# 2733 %s We're remodeling the interior for our next ruler.# 2734 %s @@ -3077,9 +3077,9 @@ last of your magic is drained!# 2741 The %s I tried to get into the Crystal Caves, but a barrier stopped me.# 2742 The %s - Perhaps the Magician's Guild erected that barrier.# + Perhaps the Magicians’ Guild erected that barrier.# 2743 The %s - I'm from a distant city, and a member of the Hunter's Guild.# + I'm from a distant city and a member of the Hunters' Guild.# 2744 The %s Hamlet's Hunters' Guild isn't in this district.# 2745 The %s @@ -3103,17 +3103,17 @@ last of your magic is drained!# 2760 type:seq# 2761 The %s - The Magician's Guild must be up to something.# + The Magicians' Guild must be up to something.# 2762 The %s Guilds always have too many secrets.# 2763 The %s - I heard that Merlin and the Magician's Guild had a falling out.# + I heard that Merlin and the Magicians' Guild had a falling out.# 2764 The %s I bet Herx is in tight with Archmagisters Orpheus and Erudyce.# 2765 The %s Don't get me started on the Hunters' Guild.# 2766 The %s - I heard the Masons Guild works for the Church.# + I heard the Masons' Guild works for the Church.# 2767 The %s The Church has secrets, but they also help the poor.# 2768 The %s @@ -3127,7 +3127,7 @@ last of your magic is drained!# 2772 The %s The magicians inside can explain everything.# 2773 The %s - Me? No I'm just a doorman.# + Me? No, I'm just a doorman.# 2774 The %s Many come and go, %s. No membership required.# 2775 The %s @@ -3151,7 +3151,7 @@ last of your magic is drained!# 2784 The %s I think Herx expected favors from the Archmagisters.# 2785 The %s - Eventually Herx gave up on getting the help of the Guild.# + Eventually, Herx gave up on getting the help of the Guild.# 2786 The %s Things turned out bad, but they could've been worse.# 2787 The %s @@ -3165,7 +3165,7 @@ last of your magic is drained!# 2793 The %s I heard rumors about a new letter from Merlin, but I never saw it.# 2794 The %s - You know, Merlin was never an official member of the Magicians Guild.# + You know, Merlin was never an official member of the Magicians’ Guild.# 2795 The %s Some people just need their independence. I respect that.# 2796 The %s @@ -3223,7 +3223,7 @@ last of your magic is drained!# 2832 %s I wanted to meet you before you left town.# 2833 %s - Me? I am one of the esteemed members of the Merchant's guild.# + Me? I am one of the esteemed members of the Merchants' Guild.# 2834 %s I'm only interested in the rarest of artifacts in the land. @@ -3240,7 +3240,7 @@ last of your magic is drained!# I know you have too much of a heart to do the same yourself.# 2838 %s So it is up to you. - Leave them with me and we'll say no + Leave them with me, and we'll say no more about our little encounter.# 2839 %s ...# @@ -3410,11 +3410,11 @@ last of your magic is drained!# 3068 DEPRECATED# # Follower responses. -3069 I'll stand ground here, %s.# +3069 I'll stand my ground here, %s.# 3070 I'll sit tight.# 3071 My %s cursed!# 3072 This would suit you well, %s!# -3073 I'll try find something better.# +3073 I'll try to find something better.# 3074 I have no use for this.# 3075 That hit the spot!# 3076 Oof. I'm really full now.# @@ -3423,7 +3423,7 @@ last of your magic is drained!# 3079 Advancing to point!# 3080 Cover my retreat, %s!# 3081 These may be due for repairs.# -3082 Nothing's irreplacable here!# +3082 Nothing's irreplaceable here!# 3083 You can really feel the breeze down here.# 3084 I can't betray my allies like that.# 3085 You have to keep what few friends you have down here.# @@ -3554,10 +3554,10 @@ last of your magic is drained!# 3202 What a horrible night to have a curse...# 3203 Blecch! This is disgusting!# 3204 Unarmed# -3205 %s is stunned from your blow!# -3206 The %s is stunned from your blow!# -3207 You are stunned from %s's blow!# -3208 You are stunned from the %s's blow!# +3205 %s is stunned by your blow!# +3206 The %s is stunned by your blow!# +3207 You are stunned by %s's blow!# +3208 You are stunned by the %s's blow!# 3209 DEPRECATED# 3210 Your blade cuts deep into %s!# 3211 Your blade cuts deep into the %s!# @@ -3797,7 +3797,7 @@ last of your magic is drained!# 3421 DEPRECATED_SPELL_NAME# 3422 DEPRECATED_SPELL_NAME# 3423 You sense a lack of food nearby.# -3424 Your nose starts to tingle and you smell food!# +3424 Your nose starts to tingle, and you smell food!# 3425 Your nose twitches, but there are no new scents.# 3426 %s is sprayed with poison!# 3427 The %s is sprayed with poison!# @@ -3864,7 +3864,7 @@ last of your magic is drained!# 3488 Your level is not high enough to use that spell.# 3489 DEPRECATED_SPELL_NAME# 3490 Your veins feel warm!# -3491 Your bloodflow returns to normal.# +3491 Your blood flow returns to normal.# 3492 DEPRECATED# 3493 DEPRECATED# 3494 You hear a trap trigger in the distance.# @@ -4037,14 +4037,14 @@ last of your magic is drained!# 3668 Crafted %s!# 3669 You can't salvage equipped items!# 3670 Tinker# -3671 Your gyrobot detects curious footsteps towards your noisemaker!# +3671 Your gyrobot detects curious footsteps toward your noisemaker!# 3672 DEPRECATED# 3673 Your %s needs to be higher quality to use this command.# 3674 DEPRECATED# 3675 DEPRECATED# 3676 The %s locks its rotor.# 3677 The %s returns to its patrol.# -3678 Your %s needs to be higher quality detect more object types.# +3678 Your %s needs to be higher quality to detect more object types.# 3679 Your Tinkering skill is not high enough to decipher any more gyrobot findings.# 3680 Your Tinkering skill is not high enough to decipher @@ -4560,7 +4560,7 @@ formalities of gold, now!# 4140 Not enough materials to craft# 4141 Not enough materials to repair# 4142 Not enough materials to upgrade# -4143 Irrepairable, best salvaged# +4143 Irreparable, best salvaged# 4144 Skill too low to repair (%d/%d)# 4145 Skill too low to upgrade (%d/%d)# 4146 Cannot salvage this while using it!# @@ -4749,10 +4749,10 @@ favorite customers.# 4301 The shrine responds to your touch!# 4302 Your shapeshift fades and you return to your abnormal form.# 4303 The polymorph wears out.# -4304 You are no longer an enemy of the Merchant's Guild.# -4305 You are now deemed an enemy of the Merchant's Guild +4304 You are no longer an enemy of the Merchants' Guild.# +4305 You are now deemed an enemy of the Merchants' Guild for inciting violence!# -4306 You are now deemed an enemy of the Merchant's Guild +4306 You are now deemed an enemy of the Merchants' Guild for accessory to violence!# 4307 You see a shrine.# 4308 You see a statue.# @@ -4968,9 +4968,9 @@ The next input you activate will be bound to this action.# 5117 Enemy Health Bar Scaling# 5118 Control size of in-world popups for enemy health bars.# 5119 Popup Scaling# -5120 Control size of in-world popups for items, gravestones and NPC dialogue.# +5120 Control size of in-world popups for items, gravestones, and NPC dialogue.# 5121 Popup Scaling (Splitscreen)# -5122 Control size of in-world popups for items, gravestones and NPC dialogue in splitscreen.# +5122 Control size of in-world popups for items, gravestones, and NPC dialogue in splitscreen.# 5123 Item Tooltip Height# 5124 Adjust the vertical position of in-world item tooltip popups.# 5125 Crosshair Opacity# @@ -5020,17 +5020,17 @@ The next input you activate will be bound to this action.# 5167 Field of View# 5168 Adjust the vertical field-of-view of the in-game camera.# 5169 FPS limit# -5170 Limit the frame-rate of the game window. Do not set this higher than your refresh rate. (Recommended: Auto)# +5170 Limit the frame rate of the game window. Do not set this higher than your refresh rate. (Recommended: Auto)# 5171 High Dynamic Range (HDR)# 5172 Increases color contrast of the rendered world with both brightened and darkened areas.# 5173 Camera Interpolation# 5174 Smooth player camera by interpolating camera movements over additional frames.# 5175 Vertical Splitscreen# -5176 For splitscreen with two-players: divide the screen along a vertical line rather than a horizontal one.# +5176 For splitscreen with two players: divide the screen along a vertical line rather than a horizontal one.# 5177 Clipped Splitscreen# -5178 For splitscreen with two-players: reduce each viewport by 20% to preserve aspect ratio.# +5178 For splitscreen with two players: reduce each viewport by 20% to preserve aspect ratio.# 5179 Staggered Splitscreen# -5180 For splitscreen with two-players: stagger each viewport so they each rest in a corner of the display.# +5180 For splitscreen with two players: stagger each viewport so they each rest in a corner of the display.# 5181 Auto# 5182 Output# @@ -5042,16 +5042,16 @@ The next input you activate will be bound to this action.# 5188 Gameplay Volume# 5189 Adjust the volume of most game sound effects.# 5190 Ambient Volume# -5191 Adjust the volume of ominous subterranean sound-cues.# +5191 Adjust the volume of ominous subterranean sound cues.# 5192 Environment Volume# 5193 Adjust the volume of flowing water and lava.# 5194 Notification Volume# -5195 Adjust the volume of callouts, chat messages, skill increases and level up notifications.# +5195 Adjust the volume of callouts, chat messages, skill increases, and level up notifications.# 5196 Music Volume# 5197 Adjust the volume of the game's soundtrack.# 5198 Options# 5199 Minimap Pings# -5200 Toggle the ability to hear pings on the minimap# +5200 Toggle the ability to hear pings on the minimap.# 5201 Player Monster Sounds# 5202 Toggle the chance to emit monstrous mumbles when playing a non-human character.# 5203 Out-of-Focus Audio# @@ -5084,7 +5084,7 @@ The next input you activate will be bound to this action.# 5229 Modern# 5230 Classic# 5231 Hotbar Layout# -5232 Modern: Grouped 3x3 slot layout using held buttons. Classic: Flat 10 slot layout with simpler controls.# +5232 Modern: Grouped 3x3 slot layout using held buttons. Classic: Flat 10-slot layout with simpler controls.# 5233 Turn Sensitivity X# 5234 Affect the horizontal sensitivity of the control stick used for turning.# 5235 Turn Sensitivity Y# @@ -5117,12 +5117,12 @@ The next input you activate will be bound to this action.# 5260 Greatly increases the difficulty of all combat encounters.# 5261 Classic Mode# 5262 Toggle this option to make the game end after the battle with Baron Herx.# -5263 Keep Inventory after Death# +5263 Keep Inventory After Death# 5264 When a player dies, they retain their inventory when revived on the next level.# 5265 Extra Life# -5266 Start the game with an Amulet of Life-saving, to prevent one death.# +5266 Start the game with an Amulet of Life-Saving, to prevent one death.# 5267 Cheats# -5268 Toggle the ability to activate cheatcodes during gameplay.# +5268 Toggle the ability to activate cheat codes during gameplay.# 5269 HIGHSCORES# 5270 LEADERBOARDS# @@ -5476,7 +5476,7 @@ Address# 5535 TUTORIALS# 5536 Take on 10 challenges that teach and test your adventuring -skills, preparing you to take on the dungeon# +skills, preparing you to take on the dungeon.# 5537 Trial# 5538 Best Time# 5539 Hub# @@ -5813,9 +5813,9 @@ to complete the test!# 5832 You will be revived if your party makes it to the next level.# 5833 You placed at position (%d) -in local highscores!# +in local high scores!# 5834 You failed to place -in local highscores.# +in local high scores.# 5835 Dismiss# 5836 Back to Hub# @@ -6095,7 +6095,7 @@ Upload# 6019 Populate Hotbar# 6020 Preload Music Files# -6021 Preloading improves playback performance, but increases system memory usage. (Applied next game launch)# +6021 Preloading improves playback performance but increases system memory usage. (Applied next game launch)# 6022 Holiday Themes# 6023 Festive Content# 6024 Toggle holiday themed content on or off as desired. Bah, humbug!# From bf600876856ee32a21e510004ce497ccdb444814 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 20 Jun 2024 00:59:02 +1000 Subject: [PATCH 020/244] * misc compendium event fixes * map tile drawing for compendium, opengl code takes map as param for compendium map * enslaved ghouls add npc tag --- src/actheadstone.cpp | 3 +- src/actmonster.cpp | 1 + src/draw.hpp | 4 +- src/game.cpp | 8 +- src/init_game.cpp | 9 +- src/interface/consolecommand.cpp | 6 + src/main.hpp | 8 +- src/mod_tools.cpp | 162 +++++++-- src/monster_vampire.cpp | 1 + src/opengl.cpp | 30 +- src/ui/GameUI.cpp | 578 +++++++++++++++++++++++++++++-- src/ui/GameUI.hpp | 2 +- src/ui/MainMenu.cpp | 147 +++++++- 13 files changed, 870 insertions(+), 89 deletions(-) diff --git a/src/actheadstone.cpp b/src/actheadstone.cpp index 49aeb05a3..0e50fadea 100644 --- a/src/actheadstone.cpp +++ b/src/actheadstone.cpp @@ -116,7 +116,7 @@ void actHeadstone(Entity* my) Language::get(485 + HEADSTONE_MESSAGE % 17)); Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GRAVE_EPITAPHS_READ, "gravestone", 1); - Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GRAVE_EPITAPHS_PERCENT, "gravestone", (1 << HEADSTONE_MESSAGE)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GRAVE_EPITAPHS_PERCENT, "gravestone", (1 << (HEADSTONE_MESSAGE % 17))); triggeredPlayer = i; @@ -151,6 +151,7 @@ void actHeadstone(Entity* my) if ( tmpStats ) { strcpy(tmpStats->name, "enslaved ghoul"); + tmpStats->setAttribute("special_npc", "enslaved ghoul"); } if ( triggeredPlayer >= 0 ) { diff --git a/src/actmonster.cpp b/src/actmonster.cpp index d63d8ce88..18462f17d 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -8506,6 +8506,7 @@ void actMonster(Entity* my) const auto dist = sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY); if ( myStats->getAttribute("monster_portrait") != "" ) { + my->yaw = 0.0; monsterAnimate(my, myStats, 0.2); } else diff --git a/src/draw.hpp b/src/draw.hpp index 5ac06e591..f1d43fc46 100644 --- a/src/draw.hpp +++ b/src/draw.hpp @@ -421,7 +421,7 @@ extern view_t menucam; #define REALCOLORS 0 #define ENTITYUIDS 1 void beginGraphics(); -void glBeginCamera(view_t* camera, bool useHDR); +void glBeginCamera(view_t* camera, bool useHDR, map_t& map); void glDrawVoxel(view_t* camera, Entity* entity, int mode); void glDrawSprite(view_t* camera, Entity* entity, int mode); void glDrawWorldUISprite(view_t* camera, Entity* entity, int mode); @@ -429,7 +429,7 @@ void glDrawWorldDialogueSprite(view_t* camera, void* worldDialogue, int mode); void glDrawEnemyBarSprite(view_t* camera, int mode, int playerViewport, void* enemyHPBarDetails); void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode, bool useTextAsImgPath = false, bool rotate = false); void glDrawWorld(view_t* camera, int mode); -void glEndCamera(view_t* camera, bool useHDR); +void glEndCamera(view_t* camera, bool useHDR, map_t& map); unsigned int GO_GetPixelU32(int x, int y, view_t& camera); extern bool hdrEnabled; diff --git a/src/game.cpp b/src/game.cpp index 17721d067..5a736b8f6 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -6094,7 +6094,7 @@ void drawAllPlayerCameras() { // do occlusion culling from the perspective of this camera DebugStats.drawWorldT2 = std::chrono::high_resolution_clock::now(); occlusionCulling(map, camera); - glBeginCamera(&camera, true); + glBeginCamera(&camera, true, map); // shared minimap progress if ( !splitscreen/*gameplayCustomManager.inUse() && gameplayCustomManager.minimapShareProgress && !splitscreen*/ ) @@ -6237,7 +6237,7 @@ void drawAllPlayerCameras() { DebugStats.drawWorldT5 = std::chrono::high_resolution_clock::now(); drawEntities3D(&camera, REALCOLORS); - glEndCamera(&camera, true); + glEndCamera(&camera, true, map); // undo ghost fog if (players[c]->ghost.isActive()) { @@ -7075,10 +7075,10 @@ int main(int argc, char** argv) light = addLight(menucam.x, menucam.y, "mainmenu"); occlusionCulling(map, menucam); beginGraphics(); - glBeginCamera(&menucam, true); + glBeginCamera(&menucam, true, map); glDrawWorld(&menucam, REALCOLORS); drawEntities3D(&menucam, REALCOLORS); - glEndCamera(&menucam, true); + glEndCamera(&menucam, true, map); list_RemoveNode(light->node); } diff --git a/src/init_game.cpp b/src/init_game.cpp index 6bde2e97c..abceea255 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -93,6 +93,7 @@ void initGameDatafiles(bool moddedReload) } setupSpells(); CompendiumEntries.readMonstersFromFile(); + Compendium_t::Events_t::itemDisplayedEventsList.clear(); Compendium_t::Events_t::readEventsFromFile(); CompendiumEntries.readCodexFromFile(); CompendiumEntries.readWorldFromFile(); @@ -100,7 +101,8 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readMagicFromFile(); Compendium_t::Events_t::readEventsTranslations(); Compendium_t::Events_t::loadItemsSaveData(); - CompendiumEntries.readMonsterLimbsFromFile(); + CompendiumEntries.readModelLimbsFromFile("monster"); + CompendiumEntries.readModelLimbsFromFile("world"); } void initGameDatafilesAsync(bool moddedReload) @@ -769,7 +771,10 @@ void deinitGame() { free(shoparea); } - + if ( CompendiumEntries.compendiumMap.tiles ) + { + free(CompendiumEntries.compendiumMap.tiles); + } for (int i = 0; i < MAXPLAYERS; ++i) { delete players[i]; diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 13f172f99..36e5d8484 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4863,6 +4863,12 @@ namespace ConsoleCommands { } }); + static ConsoleCommand ccmd_reloadcompendiumlimbs("/reloadcompendiumlimbs", "reloads compendium entries", []CCMD{ + CompendiumEntries.compendiumObjectLimbs.clear(); + CompendiumEntries.readModelLimbsFromFile("monster"); + CompendiumEntries.readModelLimbsFromFile("world"); + }); + static ConsoleCommand ccmd_reloadcompendiummonsters("/reloadcompendiummonsters", "reloads compendium entries", []CCMD{ CompendiumEntries.readMonstersFromFile(); }); diff --git a/src/main.hpp b/src/main.hpp index 7be5c0eb3..f0c408be7 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -454,11 +454,11 @@ typedef struct map_t char author[32]; // author of the map unsigned int width, height, skybox; // size of the map + skybox Sint32 flags[16]; - Sint32* tiles; + Sint32* tiles = nullptr; std::unordered_map entities_map; - list_t* entities; - list_t* creatures; //A list of Entity* pointers. - list_t* worldUI; //A list of Entity* pointers. + list_t* entities = nullptr; + list_t* creatures = nullptr; //A list of Entity* pointers. + list_t* worldUI = nullptr; //A list of Entity* pointers. bool* trapexcludelocations = nullptr; bool* monsterexcludelocations = nullptr; bool* lootexcludelocations = nullptr; diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 19d213bf1..3ebc0781a 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10718,7 +10718,6 @@ void Compendium_t::readItemsFromFile() CompendiumItems_t::contentsMap.clear(); Compendium_t::Events_t::itemEventLookup.clear(); Compendium_t::Events_t::eventItemLookup.clear(); - Compendium_t::Events_t::itemDisplayedEventsList.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -10815,7 +10814,12 @@ void Compendium_t::readItemsFromFile() const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[itemType]; + if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) + == vec.end() ) + { + vec.push_back((Compendium_t::EventTags)find2->second.id); + } } } } @@ -11089,11 +11093,21 @@ void Compendium_t::readMagicFromFile() const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType == SPELL_ITEM ) { - Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventSpellOffset + item.spellID].push_back((Compendium_t::EventTags)find2->second.id); + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventSpellOffset + item.spellID]; + if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) + == vec.end() ) + { + vec.push_back((Compendium_t::EventTags)find2->second.id); + } } else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[itemType]; + if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) + == vec.end() ) + { + vec.push_back((Compendium_t::EventTags)find2->second.id); + } } } } @@ -11221,6 +11235,10 @@ void Compendium_t::readWorldFromFile() jsonVecToVec(w["blurb"], obj.blurb); jsonVecToVec(w["details"], obj.details); obj.imagePath = w["img"].GetString(); + if ( w.HasMember("models") ) + { + jsonVecToVec(w["models"], obj.models); + } Compendium_t::Events_t::eventWorldIDLookup[name] = obj.id; if ( w.HasMember("events") ) @@ -11239,7 +11257,7 @@ void Compendium_t::readWorldFromFile() } } } - /*if ( w.HasMember("events_display") ) + if ( w.HasMember("events_display") ) { for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) { @@ -11250,11 +11268,16 @@ void Compendium_t::readWorldFromFile() auto find2 = Compendium_t::Events_t::events.find(find->second); if ( find2 != Compendium_t::Events_t::events.end() ) { - Compendium_t::Events_t::itemDisplayedEventsList[itemType].push_back((Compendium_t::EventTags)find2->second.id); + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventWorldOffset + obj.id]; + if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) + == vec.end() ) + { + vec.push_back((Compendium_t::EventTags)find2->second.id); + } } } } - }*/ + } } } @@ -11367,6 +11390,7 @@ void Compendium_t::readMonstersFromFile() auto& m = itr->value; auto& monster = monsters[itr->name.GetString()]; monster.monsterType = monsterType; + monster.unique_npc = m.HasMember("unique_npc") ? m["unique_npc"].GetString() : ""; jsonVecToVec(m["blurb"], monster.blurb); auto& stats = m["stats"]; jsonVecToVec(stats["hp"], monster.hp); @@ -11449,16 +11473,9 @@ void Compendium_t::Events_t::readEventsTranslations() { EventTags tag = eventIdLookup[find->first]; auto& entry = eventLangEntries[tag]; - if ( itr2->value.HasMember("default") ) + for ( auto itr3 = itr2->value.MemberBegin(); itr3 != itr2->value.MemberEnd(); ++itr3 ) { - entry["default"] = itr2->value["default"].GetString(); - } - if ( itr2->value.HasMember("overrides") ) - { - for ( auto itr3 = itr2->value["overrides"].MemberBegin(); itr3 != itr2->value["overrides"].MemberEnd(); ++itr3 ) - { - entry[itr3->name.GetString()] = itr3->value.GetString(); - } + entry[itr3->name.GetString()] = itr3->value.GetString(); } } } @@ -12577,12 +12594,12 @@ void Compendium_t::Events_t::sendClientDataOverNet(const int playernum) } } -void Compendium_t::readMonsterLimbsFromFile() +void Compendium_t::readModelLimbsFromFile(std::string section) { - std::string fullpath = "data/compendium/monster_models/"; + std::string fullpath = "data/compendium/" + section + "_models/"; for ( auto f : directoryContents(fullpath.c_str(), false, true) ) { - std::string inputPath = "/data/compendium/monster_models/" + f; + std::string inputPath = fullpath + f; std::string path = PHYSFS_getRealDir(inputPath.c_str()) ? PHYSFS_getRealDir(inputPath.c_str()) : ""; if ( path != "" ) { @@ -12601,19 +12618,91 @@ void Compendium_t::readMonsterLimbsFromFile() rapidjson::Document d; d.ParseStream(is); - if ( !d.HasMember("version") || !d.HasMember("limbs") || !d.HasMember("statue_id") ) + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("limbs") ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); return; } int version = d["version"].GetInt(); - Uint32 statueId = d["statue_id"].GetUint(); std::string filename = f.substr(0, f.find(".json")); - auto& allLimbs = compendiumMonsterLimbs[filename]; + auto& allLimbs = compendiumObjectLimbs[filename]; allLimbs.clear(); + compendiumObjectMapTiles.erase(filename); + int w = 0; + int h = 0; int index = 0; + if ( d.HasMember("map_tiles") ) + { + auto& m = compendiumObjectMapTiles[filename]; + if ( d["map_tiles"].HasMember("floor") + && d["map_tiles"].HasMember("mid") + && d["map_tiles"].HasMember("top") + && d["map_tiles"].HasMember("width") + && d["map_tiles"].HasMember("height") ) + { + if ( d["map_tiles"]["floor"].IsArray() + && d["map_tiles"]["mid"].IsArray() + && d["map_tiles"]["top"].IsArray() ) + { + auto& floor = d["map_tiles"]["floor"].GetArray(); + auto& mid = d["map_tiles"]["mid"].GetArray(); + auto& top = d["map_tiles"]["top"].GetArray(); + w = d["map_tiles"]["width"].GetInt(); + h = d["map_tiles"]["height"].GetInt(); + if ( floor.Size() == mid.Size() && + floor.Size() == top.Size() && floor.Size() == (w * h) ) + { + m.first.width = w; + m.first.height = h; + if ( d["map_tiles"].HasMember("ceiling") ) + { + m.first.ceiling = d["map_tiles"]["ceiling"].GetInt(); + } + auto& tiles = m.second; + tiles.resize(w * h * MAPLAYERS); + for ( int z = 0; z < MAPLAYERS; ++z ) + { + int x = 0; + int y = 0; + + auto& arr = (z == 0) ? floor : ((z == 1) ? mid : top); + for ( auto tile = arr.Begin(); tile != arr.End(); ++tile ) + { + int index = z + (y * MAPLAYERS) + (x * MAPLAYERS * h); + tiles[index] = tile->GetInt(); + + // fix animated tiles so they always start on the correct index + constexpr int numTileAtlases = sizeof(AnimatedTile::indices) / sizeof(AnimatedTile::indices[0]); + if ( animatedtiles[tiles[index]] ) { + auto find = tileAnimations.find(tiles[index]); + if ( find == tileAnimations.end() ) { + // this is not the correct index! + for ( const auto& pair : tileAnimations ) { + const auto& animation = pair.second; + for ( int i = 0; i < numTileAtlases; ++i ) { + if ( animation.indices[i] == tiles[index] ) { + tiles[index] = animation.indices[0]; + } + } + } + } + } + + ++x; + if ( x >= w ) + { + x = 0; + ++y; + } + } + + } + } + } + } + } for ( auto itr = d["limbs"].Begin(); itr != d["limbs"].End(); ++itr ) { /*if ( d.HasMember("height_offset") ) @@ -12632,6 +12721,31 @@ void Compendium_t::readMonsterLimbsFromFile() { limb.x = (*itr)["x"].GetDouble(); limb.y = (*itr)["y"].GetDouble(); + limb.x += 8.0; + limb.y += 8.0; + + if ( w > 0 && h > 0 ) + { + // position in center of map + if ( w % 2 == 1 ) + { + limb.x += 16.0 * (w / 2); + } + else + { + limb.x += 16.0 * ((w - 1) / 2); + limb.x += 8.0; + } + if ( h % 2 == 1 ) + { + limb.y += 16.0 * (h / 2); + } + else + { + limb.y += 16.0 * ((h - 1) / 2); + limb.y += 8.0; + } + } } limb.z = (*itr)["z"].GetDouble(); limb.focalx = (*itr)["focalx"].GetDouble(); @@ -12640,6 +12754,10 @@ void Compendium_t::readMonsterLimbsFromFile() limb.pitch = (*itr)["pitch"].GetDouble(); limb.roll = (*itr)["roll"].GetDouble(); limb.yaw = (*itr)["yaw"].GetDouble(); + if ( (*itr).HasMember("yaw_degrees") ) + { + limb.yaw += PI * (*itr)["yaw_degrees"].GetInt() / 180.0; + } limb.sprite = (*itr)["sprite"].GetInt(); ++index; diff --git a/src/monster_vampire.cpp b/src/monster_vampire.cpp index 4ea5a41d2..befc32347 100644 --- a/src/monster_vampire.cpp +++ b/src/monster_vampire.cpp @@ -97,6 +97,7 @@ void initVampire(Entity* my, Stat* myStats) if ( followerStats ) { strcpy(followerStats->name, "enslaved ghoul"); + followerStats->setAttribute("special_npc", "enslaved ghoul"); followerStats->leader_uid = entity->parent; } entity->seedEntityRNG(rng.getU32()); diff --git a/src/opengl.cpp b/src/opengl.cpp index f1fac24f8..0fe9510ed 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -572,7 +572,14 @@ vec4_t unproject( -------------------------------------------------------------------------------*/ -static void fillSmoothLightmap(int which) { +static void fillSmoothLightmap(int which, map_t& map) { +#ifndef EDITOR + if ( &map == &CompendiumEntries.compendiumMap ) + { + return; + } +#endif + auto lightmap = lightmaps[which].data(); auto lightmapSmoothed = lightmapsSmoothed[which].data(); @@ -618,7 +625,7 @@ static inline bool testTileOccludes(const map_t& map, int index) { return (t0 & 0xffffffff00000000) && (t0 & 0x00000000ffffffff) && t1; } -static void loadLightmapTexture(int which) { +static void loadLightmapTexture(int which, map_t& map) { auto lightmapSmoothed = lightmapsSmoothed[which].data(); // allocate lightmap pixel data @@ -629,13 +636,12 @@ static void loadLightmapTexture(int which) { #ifdef EDITOR const bool fullbright = false; #else - const bool fullbright = conductGameChallenges[CONDUCT_CHEATS_ENABLED] ? *cvar_fullBright : false; + const bool fullbright = (&map == &CompendiumEntries.compendiumMap) ? true :// compendium virtual map is always fullbright + (conductGameChallenges[CONDUCT_CHEATS_ENABLED] ? *cvar_fullBright : false); #endif // build lightmap texture data const float div = 1.f / 255.f; - const int xoff = MAPLAYERS * map.height; - const int yoff = MAPLAYERS; if (fullbright) { for (int y = 0; y < map.height; ++y) { for (int x = 0; x < map.width; ++x) { @@ -643,6 +649,8 @@ static void loadLightmapTexture(int which) { } } } else { + const int xoff = MAPLAYERS * map.height; + const int yoff = MAPLAYERS; for (int y = 0; y < map.height; ++y) { for (int x = 0, index = y * yoff; x < map.width; ++x, index += xoff) { if (!testTileOccludes(map, index)) { @@ -939,7 +947,7 @@ bool hdrEnabled = true; static int oldViewport[4]; -void glBeginCamera(view_t* camera, bool useHDR) +void glBeginCamera(view_t* camera, bool useHDR, map_t& map) { if (!camera) { return; @@ -1014,8 +1022,8 @@ void glBeginCamera(view_t* camera, bool useHDR) break; } } - fillSmoothLightmap(lightmapIndex); - loadLightmapTexture(lightmapIndex); + fillSmoothLightmap(lightmapIndex, map); + loadLightmapTexture(lightmapIndex, map); // upload uniforms uploadUniforms(voxelShader, (float*)&proj, (float*)&view, (float*)&mapDims); @@ -1034,7 +1042,7 @@ void glBeginCamera(view_t* camera, bool useHDR) #include #include -void glEndCamera(view_t* camera, bool useHDR) +void glEndCamera(view_t* camera, bool useHDR, map_t& map) { if (!camera) { return; @@ -2232,10 +2240,10 @@ unsigned int GO_GetPixelU32(int x, int y, view_t& camera) if (dirty) { GL_CHECK_ERR(glClearColor(0.f, 0.f, 0.f, 0.f)); GL_CHECK_ERR(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - glBeginCamera(&camera, false); + glBeginCamera(&camera, false, map); glDrawWorld(&camera, ENTITYUIDS); drawEntities3D(&camera, ENTITYUIDS); - glEndCamera(&camera, false); + glEndCamera(&camera, false, map); } GLubyte pixel[4]; diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 16aa2193b..76daba9dd 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -21617,7 +21617,7 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) view.winw = pos.w; view.winh = pos.h; - glBeginCamera(&view, false); + glBeginCamera(&view, false, map); bool b = item->flags[BRIGHT]; if ( !dark ) { item->flags[BRIGHT] = true; } if ( !item->flags[INVISIBLE] ) @@ -21639,11 +21639,306 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) // blending gets disabled after objects are drawn, so re-enable it. GL_CHECK_ERR(glEnable(GL_BLEND)); } - glEndCamera(&view, false); + glEndCamera(&view, false, map); ::fov = ofov; } -void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark) + +void glDrawWorldTile(view_t* camera, int mode, map_t& map) +{ + if ( !camera ) + { + return; + } + + float getLightAtModifier = 1.f; + + // bind core shader + auto& shader = worldShader; + shader.bind(); + + + // upload uniforms for core shader + if ( &shader != &worldDarkShader ) { + const GLfloat light[4] = { (float)getLightAtModifier, (float)getLightAtModifier, (float)getLightAtModifier, 1.f }; + GL_CHECK_ERR(glUniform4fv(shader.uniform("uLightFactor"), 1, light)); + const float cameraPos[4] = { (float)camera->x * 32.f, -(float)camera->z, (float)camera->y * 32.f, 1.f }; + GL_CHECK_ERR(glUniform4fv(shader.uniform("uCameraPos"), 1, cameraPos)); + } + + std::vector chunks; + chunks.emplace_back(); + auto& chunk = chunks.back(); + chunk.build(map, map.flags[MAP_FLAG_CEILINGTILE] > 0, 0, 0, map.width, map.height); + for ( auto& chunk : chunks ) + { + worldShader.bind(); + chunk.draw(); + } +} + +void actObjectPreviewFlame(Entity* my) +{ + if ( my->skill[0] > 0 ) + { + my->skill[0]--; + if ( my->skill[0] <= 0 ) + { + list_RemoveNode(my->mynode); + return; + } + } + my->x += my->vel_x; + my->y += my->vel_y; + my->z += my->vel_z; +} + +Entity* createDrawObjectCustomParticle(Entity* parentent, int sprite, real_t scale, real_t spreadReduce) +{ + if ( !parentent ) + { + return nullptr; + } + Entity* entity = newEntity(sprite, 1, &parentent->children, nullptr); //Particle entity. + + int size = 50 / spreadReduce; + entity->x = parentent->x + (local_rng.rand() % size - size / 2) / 20.f; + entity->y = parentent->y + (local_rng.rand() % size - size / 2) / 20.f; + entity->z = parentent->z + (local_rng.rand() % size - size / 2) / 20.f; + entity->scalex = scale; + entity->scaley = scale; + entity->scalez = scale; + entity->sizex = 1; + entity->sizey = 1; + entity->yaw = parentent->yaw; + entity->pitch = parentent->pitch; + entity->roll = parentent->roll; + entity->flags[NOUPDATE] = true; + entity->flags[PASSABLE] = true; + entity->flags[UNCLICKABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[UPDATENEEDED] = false; + entity->behavior = &actMagicParticle; + return entity; +} + +#define SPEARTRAP_INIT my->skill[0] +#define SPEARTRAP_STATUS my->skill[3] +#define SPEARTRAP_OUTTIME my->skill[4] +#define SPEARTRAP_STARTHEIGHT my->fskill[0] +#define SPEARTRAP_VELZ my->vel_z +void actObjectPreviewSpikeTrap(Entity* my) +{ + if ( !SPEARTRAP_INIT ) + { + SPEARTRAP_INIT = 1; + SPEARTRAP_STARTHEIGHT = my->z; + } + + my->skill[5]++; + if ( my->skill[5] >= TICKS_PER_SECOND * 1 ) + { + my->skill[5] = 0; + SPEARTRAP_STATUS = SPEARTRAP_STATUS ? 0 : 1; + SPEARTRAP_OUTTIME = 0; + } + + if ( !SPEARTRAP_STATUS || SPEARTRAP_OUTTIME > 60 ) + { + // retract spears + if ( my->z < SPEARTRAP_STARTHEIGHT ) + { + SPEARTRAP_VELZ += .25; + my->z = std::min(SPEARTRAP_STARTHEIGHT, my->z + SPEARTRAP_VELZ); + } + else + { + SPEARTRAP_VELZ = 0; + } + } + else + { + // shoot out spears + my->z = fmax(SPEARTRAP_STARTHEIGHT - 20, my->z - 4); + } +} + +void actObjectPreviewMagic(Entity* my) +{ + if ( my->skill[0] > 0 ) + { + my->skill[0]--; + if ( my->skill[0] <= 0 ) + { + list_RemoveNode(my->mynode); + return; + } + } + my->x += my->vel_x; + my->y += my->vel_y; + my->z += my->vel_z; + + createDrawObjectCustomParticle(my, my->sprite, 0.7, 4); +} + +void actObjectPreviewArrow(Entity* my) +{ + if ( my->skill[0] > 0 ) + { + my->skill[0]--; + if ( my->skill[0] <= 0 ) + { + list_RemoveNode(my->mynode); + return; + } + } + my->x += my->vel_x; + my->y += my->vel_y; + my->z += my->vel_z; + + int sprite = 160; + switch ( my->sprite ) + { + case 924: + sprite = 160; + break; + case 925: + sprite = 158; + break; + case 926: + sprite = 156; + break; + case 927: + sprite = SPRITE_FLAME; + break; + case 928: + sprite = 159; + break; + case 929: + sprite = 155; + break; + case 930: + sprite = 157; + break; + default: + break; + } + + if ( Entity* particle = createDrawObjectCustomParticle(my, sprite, 0.5, 4) ) + { + particle->flags[SPRITE] = true; + } +} + +#define BOULDER_STOPPED my->skill[0] +#define BOULDER_NOGROUND my->skill[3] +void actObjectPreviewBoulder(Entity* my) +{ + bool noground = false; + int x = std::min(std::max(0, (int)(my->x / 16)), CompendiumEntries.compendiumMap.width); + int y = std::min(std::max(0, (int)(my->y / 16)), CompendiumEntries.compendiumMap.height); + if ( x >= CompendiumEntries.compendiumMap.width || y >= CompendiumEntries.compendiumMap.height ) + { + noground = true; + } + + // gravity + bool nobounce = true; + if ( !BOULDER_NOGROUND ) + { + if ( noground ) + { + BOULDER_NOGROUND = true; + } + } + if ( my->z < 0 || BOULDER_NOGROUND ) + { + my->vel_z = std::min(my->vel_z + .1, 3.0); + my->vel_x *= 0.85f; + my->vel_y *= 0.85f; + nobounce = true; + if ( my->z >= 128 ) + { + list_RemoveNode(my->mynode); + return; + } + } + else + { + if ( fabs(my->vel_z) > 1 ) + { + my->vel_z = -(my->vel_z / 2) * (1 / 1.0); + nobounce = true; + } + else + { + my->vel_z = 0; + nobounce = false; + } + my->z = 0; + } + my->z += my->vel_z; + if ( nobounce ) + { + if ( !BOULDER_STOPPED ) + { + my->x += my->vel_x; + my->y += my->vel_y; + double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2)); + my->pitch += dist * .06; + my->roll = PI / 2; + } + } + else if ( !BOULDER_STOPPED ) + { + // horizontal velocity + my->vel_x += cos(my->yaw) * .1; + my->vel_y += sin(my->yaw) * .1; + real_t maxSpeed = 1.5; + /*if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE ) + { + maxSpeed = 2.5; + }*/ + maxSpeed *= 1.0; + if ( my->vel_x > maxSpeed ) + { + my->vel_x = maxSpeed; + } + if ( my->vel_x < -maxSpeed ) + { + my->vel_x = -maxSpeed; + } + if ( my->vel_y > maxSpeed ) + { + my->vel_y = maxSpeed; + } + if ( my->vel_y < -maxSpeed ) + { + my->vel_y = -maxSpeed; + } + + real_t ox = my->x; + real_t oy = my->y; + my->x += my->vel_x; + my->y += my->vel_y; + my->x = std::min(my->x, (real_t)CompendiumEntries.compendiumMap.width * 16.0 + 8.0); + my->y = std::min(my->y, (real_t)CompendiumEntries.compendiumMap.height * 16.0 + 8.0); + + double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2)); + if ( my->x != (ox + my->vel_x) || my->y != (oy + my->vel_y) ) + { + BOULDER_STOPPED = 1; + } + else + { + my->pitch += dist * .06; + my->roll = PI / 2; + } + } +} + +Uint32 drawObjectLastTick = 0; +void drawObjectPreview(std::string name, std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark) { static int fov = 50; static real_t ang = 0.0; @@ -21689,24 +21984,35 @@ void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monste ::fov = fov; std::vector* limbsArray = nullptr; - if ( CompendiumEntries.compendiumMonsterLimbs.find(modelsPath) != CompendiumEntries.compendiumMonsterLimbs.end() ) + if ( CompendiumEntries.compendiumObjectLimbs.find(modelsPath) != CompendiumEntries.compendiumObjectLimbs.end() ) { - limbsArray = &CompendiumEntries.compendiumMonsterLimbs[modelsPath]; - monster = &(limbsArray->at(0)); + limbsArray = &CompendiumEntries.compendiumObjectLimbs[modelsPath]; + if ( limbsArray->size() == 0 ) + { + return; + } + object = &(limbsArray->at(0)); } - if ( !monster ) + if ( !object ) { return; } - view.x = monster->x / 16.0 + ((.92 + zoom) * cos(offsetyaw - + ang + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0))); - view.y = monster->y / 16.0 + ((.92 + zoom) * sin(offsetyaw - + ang + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0))); - view.z = monster->z * 2 + z; + bool doTick = false; + if ( drawObjectLastTick != ticks ) + { + drawObjectLastTick = ticks; + doTick = true; + } + + view.x = object->x / 16.0 + ((.92 + zoom) * cos(offsetyaw + + ang + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.y = object->y / 16.0 + ((.92 + zoom) * sin(offsetyaw + + ang + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.z = object->z * 2 + z; view.ang = (offsetyaw - PI - + (*cvar_compendium_portrait_static_angle ? monster->yaw : 0) + ang); //5 * PI / 4; + + (*cvar_compendium_portrait_static_angle ? object->yaw : 0) + ang); //5 * PI / 4; view.vang = PI / 20 + vang; view.winx = pos.x; @@ -21715,19 +22021,213 @@ void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monste view.winw = pos.w; view.winh = pos.h; - glBeginCamera(&view, false); - bool b = monster->flags[BRIGHT]; - if ( !dark ) { monster->flags[BRIGHT] = true; } - if ( !monster->flags[INVISIBLE] ) + + auto& tmpMap = CompendiumEntries.compendiumMap; + glBeginCamera(&view, false, tmpMap); + + auto findMap = CompendiumEntries.compendiumObjectMapTiles.find(modelsPath); + if ( findMap != CompendiumEntries.compendiumObjectMapTiles.end() ) { - glDrawVoxel(&view, monster, REALCOLORS); + strcpy(tmpMap.name, "compendium"); + auto& props = findMap->second.first; + tmpMap.width = props.width; + tmpMap.height = props.height; + tmpMap.flags[MAP_FLAG_CEILINGTILE] = props.ceiling; + if ( tmpMap.tiles ) + { + free(tmpMap.tiles); + tmpMap.tiles = nullptr; + } + tmpMap.tiles = (Sint32*)malloc(sizeof(Sint32) * tmpMap.width * tmpMap.height * MAPLAYERS); + if ( tmpMap.tiles ) + { + constexpr int numTileAtlases = sizeof(AnimatedTile::indices) / sizeof(AnimatedTile::indices[0]); + for ( int x = 0; x < tmpMap.width; ++x ) + { + for ( int y = 0; y < tmpMap.height; ++y ) + { + for ( int z = 0; z < 3; ++z ) + { + int index = z + (y * MAPLAYERS) + (x * MAPLAYERS * tmpMap.height); + tmpMap.tiles[index] = 0; + + if ( index < findMap->second.second.size() ) + { + tmpMap.tiles[index] = findMap->second.second[index]; + } + + int tile = tmpMap.tiles[index]; + auto find = tileAnimations.find(tile); + if ( find != tileAnimations.end() ) + { + const int atlasIndex = (ticks % (numTileAtlases * 10)) / 10; + tmpMap.tiles[index] = find->second.indices[atlasIndex]; + } + } + } + } + } + glDrawWorldTile(&view, REALCOLORS, tmpMap); + } + + bool b = object->flags[BRIGHT]; + if ( !dark ) { object->flags[BRIGHT] = true; } + if ( !object->flags[INVISIBLE] ) + { + glDrawVoxel(&view, object, REALCOLORS); } - monster->flags[BRIGHT] = b; + object->flags[BRIGHT] = b; int c = 0; if ( limbsArray ) { + if ( doTick ) + { + for ( auto& limb : *limbsArray ) + { + if ( limb.sprite == 3 ) // torch + { + Entity* flame = newEntity(SPRITE_FLAME, 0, &limb.children, nullptr); + flame->x = limb.x; + flame->y = limb.y; + flame->z = limb.z; + flame->fskill[1] = limb.x; + flame->fskill[2] = limb.y; + flame->fskill[3] = limb.z; + flame->sizex = 6; + flame->sizey = 6; + flame->yaw = (local_rng.rand() % 360) * PI / 180.0; + flame->pitch = (local_rng.rand() % 360) * PI / 180.0; + flame->roll = (local_rng.rand() % 360) * PI / 180.0; + real_t vel = (local_rng.rand() % 10) / 10.0; + flame->skill[0] = 5; // life-span + flame->vel_x = vel * cos(flame->yaw) * .1; + flame->vel_y = vel * sin(flame->yaw) * .1; + flame->vel_z = -.25; + flame->flags[SPRITE] = true; + flame->behavior = &actObjectPreviewFlame; + + flame->x += 0.25 * cos(limb.yaw); + flame->y += 0.25 * sin(limb.yaw); + flame->z -= 2.5 - 7; + } + else if ( limb.sprite == 252 ) + { + if ( list_Size(&limb.children) == 0 ) + { + Entity* entity = newEntity(245, 0, &limb.children, nullptr); + entity->x = limb.x; + entity->y = limb.y; + entity->z = -64; + entity->behavior = &actObjectPreviewBoulder; + } + } + else if ( limb.sprite == 166 ) + { + if ( list_Size(&limb.children) == 0 ) + { + std::vector arrows = { + 924, + 925, + 926, + 927, + 928, + 929, + 930 + }; + Entity* entity = newEntity(arrows[local_rng.rand() % arrows.size()], 0, &limb.children, nullptr); + entity->x = limb.x; + entity->y = limb.y; + entity->z = limb.z; + entity->behavior = &actObjectPreviewArrow; + entity->vel_x = 4 * cos(limb.yaw); + entity->vel_y = 4 * sin(limb.yaw); + entity->skill[0] = TICKS_PER_SECOND * 1.5; + entity->yaw = limb.yaw; + } + } + else if ( limb.sprite == 168 ) + { + if ( list_Size(&limb.children) == 0 ) + { + std::vector magic = { + 168, + 170, + 171, + 172, + 173 + }; + Entity* entity = newEntity(magic[local_rng.rand() % magic.size()], 0, &limb.children, nullptr); + entity->x = limb.x; + entity->y = limb.y; + entity->z = limb.z; + entity->behavior = &actObjectPreviewMagic; + entity->yaw = limb.yaw + (limb.skill[1] * PI / 2); + limb.skill[1]++; + if ( limb.skill[1] >= 4 ) + { + limb.skill[1] = 0; + } + entity->vel_x = 4 * cos(entity->yaw); + entity->vel_y = 4 * sin(entity->yaw); + entity->skill[0] = TICKS_PER_SECOND; + } + } + else if ( limb.sprite == 644 ) + { + if ( list_Size(&limb.children) == 0 ) + { + std::vector magic = { + 168, + 170, + 171, + 172, + 173 + }; + Entity* entity = newEntity(magic[local_rng.rand() % magic.size()], 0, &limb.children, nullptr); + entity->x = limb.x; + entity->y = limb.y; + entity->z = limb.z; + entity->behavior = &actObjectPreviewMagic; + entity->pitch = PI / 2; + entity->vel_z = 2; + entity->skill[0] = TICKS_PER_SECOND; + } + } + else if ( limb.sprite == 283 ) + { + if ( list_Size(&limb.children) == 0 ) + { + Entity* entity = newEntity(282, 0, &limb.children, nullptr); + entity->x = limb.x; + entity->y = limb.y; + entity->z = 16; + entity->focalz = 7; + entity->behavior = &actObjectPreviewSpikeTrap; + } + } + + for ( auto node = limb.children.first; node; ) + { + Entity* entity = (Entity*)node->element; + node = node->next; + for ( auto node2 = entity->children.first; node2; ) + { + Entity* entity2 = (Entity*)node2->element; + node2 = node2->next; + if ( entity2->behavior ) + { + (entity2->behavior)(entity2); + } + } + if ( entity->behavior ) + { + (entity->behavior)(entity); + } + } + } + } for ( auto itr = limbsArray->begin(); itr != limbsArray->end(); ++itr ) { if ( c == 0 ) @@ -21742,13 +22242,45 @@ void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monste if ( !dark ) { entity->flags[BRIGHT] = true; } glDrawVoxel(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + + for ( auto node = entity->children.first; node; node = node->next ) + { + Entity* entity = (Entity*)node->element; + bool b = entity->flags[BRIGHT]; + if ( !dark ) { entity->flags[BRIGHT] = true; } + if ( entity->flags[SPRITE] ) + { + glDrawSprite(&view, entity, REALCOLORS); + } + else + { + glDrawVoxel(&view, entity, REALCOLORS); + } + entity->flags[BRIGHT] = b; + + for ( auto node2 = entity->children.first; node2; node2 = node2->next ) + { + Entity* entity = (Entity*)node2->element; + bool b = entity->flags[BRIGHT]; + if ( !dark ) { entity->flags[BRIGHT] = true; } + if ( entity->flags[SPRITE] ) + { + glDrawSprite(&view, entity, REALCOLORS); + } + else + { + glDrawVoxel(&view, entity, REALCOLORS); + } + entity->flags[BRIGHT] = b; + } + } } c++; } } else { - for ( node_t* node = monster->children.first; node != nullptr; node = node->next ) + for ( node_t* node = object->children.first; node != nullptr; node = node->next ) { if ( c == 0 ) { @@ -21786,7 +22318,7 @@ void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monste // blending gets disabled after objects are drawn, so re-enable it. GL_CHECK_ERR(glEnable(GL_BLEND)); } - glEndCamera(&view, false); + glEndCamera(&view, false, tmpMap); ::fov = ofov; } @@ -21824,7 +22356,7 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset view.winw = pos.w; view.winh = pos.h; - glBeginCamera(&view, false); + glBeginCamera(&view, false, map); bool b = playerEntity->flags[BRIGHT]; if (!dark) { playerEntity->flags[BRIGHT] = true; } if ( !playerEntity->flags[INVISIBLE] ) @@ -21916,7 +22448,7 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset // blending gets disabled after objects are drawn, so re-enable it. GL_CHECK_ERR(glEnable(GL_BLEND)); } - glEndCamera(&view, false); + glEndCamera(&view, false, map); } ::fov = ofov; } diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index 5acec9082..aee2744bd 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -24,7 +24,7 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spe void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); -void drawMonsterPreview(std::string name, std::string modelsPath, Entity* monster, SDL_Rect pos, real_t offsetyaw, bool dark = false); +void drawObjectPreview(std::string name, std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark = false); void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index c40cf4cff..d0edd161e 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32543,8 +32543,9 @@ namespace MainMenu { #endif static Entity* compendiumMonster = nullptr; - static std::pair compendiumMonsterCurrent = { "", ""}; // contents name, then model filename + static std::pair compendiumEntityCurrent = { "", ""}; // contents name, then model filename static std::vector compendiumMonsterLimbs; + static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName = ""); static Entity* createCompendiumMonster(Monster creature, real_t x, real_t y) { if ( compendiumMonster ) @@ -32608,6 +32609,9 @@ namespace MainMenu { } auto& entry = CompendiumEntries.worldObjects[name]; + compendiumEntityCurrent.first = name; + compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; + if ( Frame* page_left = parent->findFrame("page_left") ) { if ( auto blurb = page_left->findField("blurb") ) @@ -32624,6 +32628,9 @@ namespace MainMenu { if ( Frame* page_right = parent->findFrame("page_right") ) { + // find records for this item + populateRecordsSectionItems(page_right, entry.id + Compendium_t::Events_t::kEventWorldOffset, name.c_str()); + if ( page_right = page_right->findFrame("page_right_inner") ) { if ( auto details = page_right->findField("details") ) @@ -32732,15 +32739,10 @@ namespace MainMenu { } } - static void populateRecordsSectionItems(Frame* page_right, int itemType) + static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName) { if ( !page_right ) { return; } - if ( itemType < 0 || (itemType >= NUMITEMS && itemType < Compendium_t::Events_t::kEventSpellOffset) ) - { - return; - } - if ( auto page_right_overlay = page_right->findFrame("page_right_overlay") ) { page_right_overlay->setDisabled(false); @@ -32761,11 +32763,44 @@ namespace MainMenu { auto rec4Val = page_right_overlay->findField("record 4 val"); rec4Val->setDisabled(true); - auto find = Compendium_t::Events_t::itemDisplayedEventsList.find(itemType); - if ( find != Compendium_t::Events_t::itemDisplayedEventsList.end() ) + bool foundEvents = false; + std::vector displayedEvents; + if ( entryType >= Compendium_t::Events_t::kEventMonsterOffset + && entryType < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) + { + if ( (entryType - Compendium_t::Events_t::kEventMonsterOffset) + == Compendium_t::Events_t::monsterUniqueIDLookup["ghost"] ) + { + displayedEvents = { + Compendium_t::EventTags::CPDM_GHOST_SPAWNED, + Compendium_t::EventTags::CPDM_GHOST_PUSHES, + Compendium_t::EventTags::CPDM_GHOST_TELEPORTS, + Compendium_t::EventTags::CPDM_GHOST_PINGS + }; + } + else + { + displayedEvents = { + Compendium_t::EventTags::CPDM_KILLED_SOLO, + Compendium_t::EventTags::CPDM_KILLED_MULTIPLAYER, + Compendium_t::EventTags::CPDM_KILLED_PARTY, + Compendium_t::EventTags::CPDM_KILLED_BY + }; + } + } + else + { + auto find = Compendium_t::Events_t::itemDisplayedEventsList.find(entryType); + if ( find != Compendium_t::Events_t::itemDisplayedEventsList.end() ) + { + displayedEvents = Compendium_t::Events_t::itemDisplayedEventsList[entryType]; + } + } + + if ( displayedEvents.size() > 0 ) { int index = -1; - for ( auto tag : find->second ) + for ( auto tag : displayedEvents ) { ++index; Field* txt = nullptr; @@ -32795,15 +32830,70 @@ namespace MainMenu { if ( txt ) { txt->setDisabled(false); - txt->setText(Compendium_t::Events_t::eventLangEntries[tag]["default"].c_str()); + std::string itemname = "default"; + if ( entryType >= 0 && entryType < NUMITEMS ) + { + if ( Compendium_t::Events_t::eventLangEntries[tag].find(itemNameStrings[entryType + 2]) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = itemNameStrings[entryType + 2]; + } + } + else if ( entryType >= Compendium_t::Events_t::kEventMonsterOffset + && entryType < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) + { + if ( Compendium_t::Events_t::eventLangEntries[tag].find(entryName) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = entryName; + } + } + else if ( entryType >= Compendium_t::Events_t::kEventWorldOffset + && entryType < Compendium_t::Events_t::kEventWorldOffset + 1000 ) + { + if ( Compendium_t::Events_t::eventLangEntries[tag].find(entryName) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = entryName; + } + } + else if ( entryType >= Compendium_t::Events_t::kEventSpellOffset + && entryType < Compendium_t::Events_t::kEventSpellOffset + 1000 ) + { + int spellID = entryType - Compendium_t::Events_t::kEventSpellOffset; + if ( spellID >= 0 && spellID < NUM_SPELLS ) + { + if ( Compendium_t::Events_t::eventLangEntries[tag].find(ItemTooltips.spellItems[spellID].internalName) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = ItemTooltips.spellItems[spellID].internalName; + } + } + } + txt->setText(Compendium_t::Events_t::eventLangEntries[tag][itemname].c_str()); } if ( val ) { val->setDisabled(false); - auto find = Compendium_t::Events_t::playerEvents[tag].find(itemType); + auto find = Compendium_t::Events_t::playerEvents[tag].find(entryType); if ( find != Compendium_t::Events_t::playerEvents[tag].end() ) { - val->setText(std::to_string(find->second.value).c_str()); + if ( find->second.type == Compendium_t::Events_t::BITFIELD ) + { + int total = 0; + for ( int i = 0; i < 32; ++i ) + { + if ( find->second.value & (1 << i) ) + { + ++total; + } + } + val->setText(std::to_string(total).c_str()); + } + else + { + val->setText(std::to_string(find->second.value).c_str()); + } } else { @@ -33173,8 +33263,8 @@ namespace MainMenu { return; } auto& entry = CompendiumEntries.monsters[name]; - compendiumMonsterCurrent.first = name; - compendiumMonsterCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; + compendiumEntityCurrent.first = name; + compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; if ( true /*compendiumMonster */ ) { if ( true /*auto myStats = compendiumMonster->getStats()*/ ) @@ -33194,6 +33284,21 @@ namespace MainMenu { } if ( Frame* page_right = parent->findFrame("page_right") ) { + // find records for this item + if ( Compendium_t::Events_t::monsterUniqueIDLookup.find(entry.unique_npc) + != Compendium_t::Events_t::monsterUniqueIDLookup.end() ) + { + populateRecordsSectionItems(page_right, + Compendium_t::Events_t::monsterUniqueIDLookup[entry.unique_npc] + Compendium_t::Events_t::kEventMonsterOffset, + entry.unique_npc.c_str()); + } + else + { + populateRecordsSectionItems(page_right, + entry.monsterType + Compendium_t::Events_t::kEventMonsterOffset, + monstertypename[entry.monsterType]); + } + if ( page_right = page_right->findFrame("page_right_inner") ) { if ( auto txt = page_right->findField("hp") ) @@ -33455,7 +33560,6 @@ namespace MainMenu { } } } - } } } @@ -34439,7 +34543,11 @@ namespace MainMenu { model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { if ( compendium_current == "monsters" ) { - drawMonsterPreview(compendiumMonsterCurrent.first, compendiumMonsterCurrent.second, compendiumMonster, pos, 0.0); + drawObjectPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, compendiumMonster, pos, 0.0); + } + else if ( compendium_current == "world" ) + { + drawObjectPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, nullptr, pos, 0.0); } else if ( compendium_current == "items" ) { @@ -34455,9 +34563,10 @@ namespace MainMenu { { compendiumMonster->attack(compendiumMonster->getAttackPose(), 0, nullptr); }*/ - - if ( ticks % TICKS_PER_SECOND/2 == 0 ) + static ConsoleVariable cvar_compendiumautoreload("/compendiumautoreload", true); + if ( *cvar_compendiumautoreload && (ticks % TICKS_PER_SECOND/2 == 0) ) { + consoleCommand("/reloadcompendiumlimbs"); if ( compendium_current == "monsters" ) { CompendiumEntries.readMonstersFromFile(); From 2eebd61665ea689acd1f93224cbf87eceab69438 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 20 Jun 2024 01:09:04 +1000 Subject: [PATCH 021/244] * monster recruit event for compendium --- src/actmonster.cpp | 1 + src/actthrown.cpp | 4 ++++ src/item_usage_funcs.cpp | 1 + src/magic/magic.cpp | 5 +++++ src/mod_tools.hpp | 16 +++++++++++++--- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 18462f17d..1ebcb8540 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1890,6 +1890,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], FollowerMenu[monsterclicked].recentEntity = my; } + Compendium_t::Events_t::eventUpdateMonster(monsterclicked, Compendium_t::CPDM_RECRUITED, my, 1); if ( (stats[monsterclicked]->type != HUMAN && stats[monsterclicked]->type != AUTOMATON) && myStats->type == HUMAN ) { steamAchievementClient(monsterclicked, "BARONY_ACH_PITY_FRIEND"); diff --git a/src/actthrown.cpp b/src/actthrown.cpp index cfbcbdb7a..8a1311ac7 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -944,6 +944,10 @@ void actThrown(Entity* my) parent->increaseSkill(PRO_LEADERSHIP); messagePlayerMonsterEvent(parent->skill[2], makeColorRGB(0, 255, 0), *hitstats, Language::get(3252), Language::get(3251), MSG_COMBAT); + if ( hit.entity->monsterAllyIndex != parent->skill[2] ) + { + Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); + } hit.entity->monsterAllyIndex = parent->skill[2]; if ( multiplayer == SERVER ) { diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index c36da28c0..793e8fdac 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -3875,6 +3875,7 @@ void item_ScrollSummon(Item* item, int player) } // change the color of the hit entity. + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_RECRUITED, monster, 1); monster->flags[USERFLAG2] = true; serverUpdateEntityFlag(monster, USERFLAG2); if ( monsterChangesColorWhenAlly(monsterStats) ) diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 798454f09..c592359ef 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -180,6 +180,10 @@ bool spellEffectDominate(Entity& my, spellElement_t& element, Entity& caster, En if ( parent->behavior == &actPlayer ) { messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(2428), Language::get(2427), MSG_COMBAT); + if ( hit.entity->monsterAllyIndex != parent->skill[2] ) + { + Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); + } } hit.entity->monsterAllyIndex = parent->skill[2]; @@ -1542,6 +1546,7 @@ void spellEffectCharmMonster(Entity& my, spellElement_t& element, Entity* parent { whoToFollow->increaseSkill(PRO_LEADERSHIP); messagePlayerMonsterEvent(whoToFollow->skill[2], color, *hitstats, Language::get(3137), Language::get(3138), MSG_COMBAT); + Compendium_t::Events_t::eventUpdateMonster(whoToFollow->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); hit.entity->monsterAllyIndex = whoToFollow->skill[2]; if ( multiplayer == SERVER ) { diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 87b5b346c..9429e040d 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3457,6 +3457,7 @@ struct Compendium_t struct Monster_t { int monsterType = NOTHING; + std::string unique_npc = ""; std::vector blurb; std::vector hp; std::vector spd; @@ -3476,15 +3477,23 @@ struct Compendium_t std::map monsters; void readMonstersFromFile(); void exportCurrentMonster(Entity* monster); - void readMonsterLimbsFromFile(); - std::map> compendiumMonsterLimbs; - + void readModelLimbsFromFile(std::string section); + std::map> compendiumObjectLimbs; + struct CompendiumMap_t + { + Uint32 width = 0; + Uint32 height = 0; + Uint32 ceiling = -1; + }; + std::map>> compendiumObjectMapTiles; + map_t compendiumMap; struct CompendiumWorld_t { struct World_t { int modelIndex = -1; std::string imagePath = ""; + std::vector models; std::vector blurb; std::vector details; int id = -1; @@ -3671,6 +3680,7 @@ struct Compendium_t CPDM_PITS_DEATHS, CPDM_PITS_ITEMS_VALUE_LOST, CPDM_KILLED_MULTIPLAYER, + CPDM_RECRUITED, CPDM_EVENT_TAGS_MAX }; From eacafffaa04af0679899e592924e04e1e0d35cc2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 20 Jun 2024 01:09:30 +1000 Subject: [PATCH 022/244] * editor fix selected tile display --- src/editor.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 9d8ce75b4..4fba79b44 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2471,11 +2471,11 @@ int main(int argc, char** argv) } occlusionCulling(map, camera); beginGraphics(); - glBeginCamera(&camera, false); + glBeginCamera(&camera, false, map); glDrawWorld(&camera, REALCOLORS); //drawFloors(&camera); drawEntities3D(&camera, REALCOLORS); - glEndCamera(&camera, false); + glEndCamera(&camera, false, map); printTextFormatted(font8x8_bmp, 8, yres - 64, "x = %3.3f\ny = %3.3f\nz = %3.3f\nang = %3.3f\nfps = %3.1f", camera.x, camera.y, camera.z, camera.ang, fps); list_RemoveNode(light->node); for ( node = map.entities->first; node != NULL; node = node->next ) @@ -2503,8 +2503,8 @@ int main(int argc, char** argv) // draw selected tile / hovering tile pos.x = xres - 48; pos.y = 320; - pos.w = 0; - pos.h = 0; + pos.w = 32; + pos.h = 32; if ( selectedTile >= 0 && selectedTile < numtiles ) { if ( tiles[selectedTile] != NULL ) @@ -2522,8 +2522,8 @@ int main(int argc, char** argv) } pos.x = xres - 48; pos.y = 360; - pos.w = 0; - pos.h = 0; + pos.w = 32; + pos.h = 32; if ( drawx >= 0 && drawx < map.width && drawy >= 0 && drawy < map.height ) { c = map.tiles[drawlayer + drawy * MAPLAYERS + drawx * MAPLAYERS * map.height]; @@ -2547,8 +2547,10 @@ int main(int argc, char** argv) { drawImage(sprites[0], NULL, &pos); } - printText(font8x8_bmp, xres - 124, 332, "Selected:"); - printText(font8x8_bmp, xres - 124, 372, " Above:"); + printTextFormatted(font8x8_bmp, xres - 124, 324, "Selected:\n\n%9d", + (selectedTile >= 0 && selectedTile < numtiles) ? selectedTile : 0); + printTextFormatted(font8x8_bmp, xres - 124, 364, " Hovered:\n\n%9d", + (drawx >= 0 && drawx < map.width&& drawy >= 0 && drawy < map.height) ? c : 0); // Print the name of the selected tool below the Tool Buttons switch ( selectedTool ) From 15b4df81e0762983827d5d384069b662f16b9232 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 28 Jun 2024 13:03:33 +1000 Subject: [PATCH 023/244] * compendium camera customising * compendium static img support --- src/actmonster.cpp | 1 - src/interface/consolecommand.cpp | 1 + src/mod_tools.cpp | 103 ++++++- src/mod_tools.hpp | 29 +- src/ui/GameUI.cpp | 414 +++++++++++++++++++++------ src/ui/GameUI.hpp | 3 +- src/ui/MainMenu.cpp | 462 ++++++++++++++++++++++++++----- 7 files changed, 837 insertions(+), 176 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 1ebcb8540..ba791c75b 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -8508,7 +8508,6 @@ void actMonster(Entity* my) if ( myStats->getAttribute("monster_portrait") != "" ) { my->yaw = 0.0; - monsterAnimate(my, myStats, 0.2); } else { diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 36e5d8484..9e90eac35 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4867,6 +4867,7 @@ namespace ConsoleCommands { CompendiumEntries.compendiumObjectLimbs.clear(); CompendiumEntries.readModelLimbsFromFile("monster"); CompendiumEntries.readModelLimbsFromFile("world"); + CompendiumEntries.readModelLimbsFromFile("codex"); }); static ConsoleCommand ccmd_reloadcompendiummonsters("/reloadcompendiummonsters", "reloads compendium entries", []CCMD{ diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 3ebc0781a..2fa01b463 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11173,6 +11173,14 @@ void Compendium_t::readCodexFromFile() jsonVecToVec(w["blurb"], obj.blurb); jsonVecToVec(w["details"], obj.details); obj.imagePath = w["img"].GetString(); + if ( w.HasMember("rendered_imgs") ) + { + jsonVecToVec(w["rendered_imgs"], obj.renderedImagePaths); + } + if ( w.HasMember("models") ) + { + jsonVecToVec(w["models"], obj.models); + } } } @@ -11346,6 +11354,7 @@ void Compendium_t::readMonstersFromFile() Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); } for ( auto pair : Compendium_t::Events_t::monsterUniqueIDLookup ) @@ -11363,6 +11372,7 @@ void Compendium_t::readMonstersFromFile() Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); } } @@ -11392,6 +11402,10 @@ void Compendium_t::readMonstersFromFile() monster.monsterType = monsterType; monster.unique_npc = m.HasMember("unique_npc") ? m["unique_npc"].GetString() : ""; jsonVecToVec(m["blurb"], monster.blurb); + if ( m.HasMember("img") ) + { + monster.imagePath = m["img"].GetString(); + } auto& stats = m["stats"]; jsonVecToVec(stats["hp"], monster.hp); jsonVecToVec(stats["ac"], monster.ac); @@ -12626,8 +12640,9 @@ void Compendium_t::readModelLimbsFromFile(std::string section) int version = d["version"].GetInt(); std::string filename = f.substr(0, f.find(".json")); - auto& allLimbs = compendiumObjectLimbs[filename]; - allLimbs.clear(); + auto& entry = compendiumObjectLimbs[filename]; + entry.entities.clear(); + entry.baseCamera = CompendiumView_t(); compendiumObjectMapTiles.erase(filename); int w = 0; @@ -12703,6 +12718,54 @@ void Compendium_t::readModelLimbsFromFile(std::string section) } } } + if ( d.HasMember("camera") ) + { + entry.baseCamera.inUse = true; + auto& c = d["camera"]; + if ( c.HasMember("ang_degrees") ) + { + entry.baseCamera.ang = PI * c["ang_degrees"].GetInt() / 180.0; + } + if ( c.HasMember("vang_degrees") ) + { + entry.baseCamera.vang = PI * c["vang_degrees"].GetInt() / 180.0; + } + entry.baseCamera.rotateLimit = false; + if ( c.HasMember("rotate_limit_degrees_min") ) + { + entry.baseCamera.rotateLimitMin = PI * c["rotate_limit_degrees_min"].GetInt() / 180.0; + entry.baseCamera.rotateLimit = true; + } + if ( c.HasMember("rotate_limit_degrees_max") ) + { + entry.baseCamera.rotateLimitMax = PI * c["rotate_limit_degrees_max"].GetInt() / 180.0; + entry.baseCamera.rotateLimit = true; + } + if ( c.HasMember("rotate_degrees") ) + { + entry.baseCamera.rotate = PI * c["rotate_degrees"].GetInt() / 180.0; + } + else + { + entry.baseCamera.rotate = entry.baseCamera.rotateLimitMax - (entry.baseCamera.rotateLimitMax - entry.baseCamera.rotateLimitMin) / 2; + } + if ( c.HasMember("rotate_speed") ) + { + entry.baseCamera.rotateSpeed = c["rotate_speed"].GetDouble(); + } + if ( c.HasMember("zoom") ) + { + entry.baseCamera.zoom = c["zoom"].GetDouble(); + } + if ( c.HasMember("height") ) + { + entry.baseCamera.height = c["height"].GetDouble(); + } + if ( c.HasMember("pan") ) + { + entry.baseCamera.pan = c["pan"].GetDouble(); + } + } for ( auto itr = d["limbs"].Begin(); itr != d["limbs"].End(); ++itr ) { /*if ( d.HasMember("height_offset") ) @@ -12710,12 +12773,12 @@ void Compendium_t::readModelLimbsFromFile(std::string section) statue.heightOffset = d["height_offset"].GetDouble(); }*/ - allLimbs.push_back(Entity(-1, 0, nullptr, nullptr)); - auto& limb = allLimbs[allLimbs.size() - 1]; + entry.entities.push_back(Entity(-1, 0, nullptr, nullptr)); + auto& limb = entry.entities[entry.entities.size() - 1]; if ( index > 0 ) { - limb.x = allLimbs[0].x - (*itr)["x"].GetDouble(); - limb.y = allLimbs[0].y - (*itr)["y"].GetDouble(); + limb.x = entry.entities[0].x - (*itr)["x"].GetDouble(); + limb.y = entry.entities[0].y - (*itr)["y"].GetDouble(); } else { @@ -12758,6 +12821,18 @@ void Compendium_t::readModelLimbsFromFile(std::string section) { limb.yaw += PI * (*itr)["yaw_degrees"].GetInt() / 180.0; } + if ( (*itr).HasMember("scalex") ) + { + limb.scalex = (*itr)["scalex"].GetDouble(); + } + if ( (*itr).HasMember("scaley") ) + { + limb.scaley = (*itr)["scaley"].GetDouble(); + } + if ( (*itr).HasMember("scalez") ) + { + limb.scalez = (*itr)["scalez"].GetDouble(); + } limb.sprite = (*itr)["sprite"].GetInt(); ++index; @@ -12789,8 +12864,17 @@ void Compendium_t::exportCurrentMonster(Entity* monster) rapidjson::Document exportDocument; exportDocument.SetObject(); CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(1)); - CustomHelpers::addMemberToRoot(exportDocument, "statue_id", rapidjson::Value(local_rng.rand())); - CustomHelpers::addMemberToRoot(exportDocument, "height_offset", rapidjson::Value(0)); + + rapidjson::Value cameraObject(rapidjson::kObjectType); + cameraObject.AddMember("ang_degrees", 0, exportDocument.GetAllocator()); + cameraObject.AddMember("vang_degrees", 0, exportDocument.GetAllocator()); + cameraObject.AddMember("zoom", 0.0, exportDocument.GetAllocator()); + cameraObject.AddMember("height", 0.0, exportDocument.GetAllocator()); + cameraObject.AddMember("rotate_limit_degrees_min", 0, exportDocument.GetAllocator()); + cameraObject.AddMember("rotate_limit_degrees_max", 0, exportDocument.GetAllocator()); + cameraObject.AddMember("rotate_speed", 0.0, exportDocument.GetAllocator()); + CustomHelpers::addMemberToRoot(exportDocument, "camera", cameraObject); + rapidjson::Value limbsObject(rapidjson::kObjectType); rapidjson::Value limbsArray(rapidjson::kArrayType); @@ -12831,6 +12915,9 @@ void Compendium_t::exportCurrentMonster(Entity* monster) limbsObj.AddMember("focaly", rapidjson::Value(limb->focaly), exportDocument.GetAllocator()); limbsObj.AddMember("focalz", rapidjson::Value(limb->focalz), exportDocument.GetAllocator()); limbsObj.AddMember("sprite", rapidjson::Value(limb->sprite), exportDocument.GetAllocator()); + limbsObj.AddMember("scalex", rapidjson::Value(limb->scalex), exportDocument.GetAllocator()); + limbsObj.AddMember("scaley", rapidjson::Value(limb->scaley), exportDocument.GetAllocator()); + limbsObj.AddMember("scalez", rapidjson::Value(limb->scalez), exportDocument.GetAllocator()); limbsArray.PushBack(limbsObj, exportDocument.GetAllocator()); ++index; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 9429e040d..484d283e5 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3452,6 +3452,21 @@ extern EquipmentModelOffsets_t EquipmentModelOffsets; struct Compendium_t { + struct CompendiumView_t + { + real_t ang = 0.0; + real_t vang = 0.0; + real_t pan = 0.0; + real_t zoom = 0.0; + real_t height = 0.0; + real_t rotate = 0.0; + int rotateState = 0; + real_t rotateLimitMin = 0.0; + real_t rotateLimitMax = 0.0; + real_t rotateSpeed = 1.0; + bool rotateLimit = true; + bool inUse = false; + }; struct CompendiumMonsters_t { struct Monster_t @@ -3478,7 +3493,15 @@ struct Compendium_t void readMonstersFromFile(); void exportCurrentMonster(Entity* monster); void readModelLimbsFromFile(std::string section); - std::map> compendiumObjectLimbs; + CompendiumView_t defaultCamera; + struct ObjectLimbs_t + { + CompendiumView_t baseCamera; + CompendiumView_t currentCamera; + std::vector entities; + }; + std::map compendiumObjectLimbs; + CompendiumView_t currentView; struct CompendiumMap_t { Uint32 width = 0; @@ -3510,8 +3533,11 @@ struct Compendium_t { int modelIndex = -1; std::string imagePath = ""; + std::vector renderedImagePaths; std::vector blurb; std::vector details; + std::vector models; + CompendiumView_t view; }; static std::vector> contents; static std::map contentsMap; @@ -3731,6 +3757,7 @@ struct Compendium_t static std::map> itemDisplayedEventsList; static std::map> eventItemLookup; static std::map> eventMonsterLookup; + static std::map> eventClassLookup; static std::map> eventWorldLookup; static std::map eventWorldIDLookup; static std::map> eventLangEntries; diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 76daba9dd..9adc6d588 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -21459,45 +21459,74 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) if ( item->sprite < 0 ) { return; } static int fov = 50; - static real_t ang = 0.0; - static real_t vang = 0.0; - static real_t zoom = 0.0; - static real_t z = 0.0; - static int idleAngRotationDir = 0; - static real_t idleAngRotation = 0.0; + bool sprite = item->flags[SPRITE]; + + std::vector* limbsArray = nullptr; + Compendium_t::CompendiumView_t* camera = &CompendiumEntries.defaultCamera; + std::string lookup = "items_single"; + if ( item->flags[SPRITE] && item->skill[10] == SPELL_ITEM ) + { + lookup = "spells_single"; + } + + if ( CompendiumEntries.compendiumObjectLimbs.find(lookup) != CompendiumEntries.compendiumObjectLimbs.end() ) + { + auto& entry = CompendiumEntries.compendiumObjectLimbs[lookup]; + limbsArray = &entry.entities; + if ( limbsArray->size() == 0 ) + { + return; + } + camera = &entry.currentCamera; + } + else + { + return; + } + if ( keystatus[SDLK_KP_1] ) { - vang -= 0.01; + camera->vang -= 0.01; } if ( keystatus[SDLK_KP_3] ) { - vang += 0.01; + camera->vang += 0.01; } if ( keystatus[SDLK_KP_4] ) { - ang -= 0.01; + camera->ang -= 0.01; } if ( keystatus[SDLK_KP_6] ) { - ang += 0.01; + camera->ang += 0.01; } if ( keystatus[SDLK_KP_8] ) { - z += 0.5; + camera->height += 0.5; } if ( keystatus[SDLK_KP_2] ) { - z -= 0.5; + camera->height -= 0.5; } if ( keystatus[SDLK_KP_7] ) { - zoom -= 0.01; + camera->zoom -= 0.01; } if ( keystatus[SDLK_KP_9] ) { - zoom += 0.01; + camera->zoom += 0.01; } - if ( keystatus[SDLK_KP_0] ) + + if ( !sprite ) + { + camera->rotateState = 0; + } + + item->scalex = limbsArray->at(0).scalex; + item->scaley = limbsArray->at(0).scaley; + item->scalez = limbsArray->at(0).scalez; + + /*if ( keystatus[SDLK_KP_0] ) { keystatus[SDLK_KP_0] = 0; item->roll += PI / 4; @@ -21549,52 +21578,48 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) { item->focalz += 0.125; } - } - - if ( !item->flags[SPRITE] ) - { - idleAngRotationDir = 0; - item->scalex = 1.0; - item->scaley = 1.0; - item->scalez = 1.0; - } - else - { - item->scalex = 0.25; - item->scaley = 0.25; - item->scalez = 0.25; - } + }*/ - if ( idleAngRotationDir == 0 ) + if ( camera->rotateState == 0 ) { - if ( item->flags[SPRITE] ) + if ( sprite ) { - idleAngRotation += 0.0015; - if ( idleAngRotation >= 0.6 * PI ) + camera->rotate += 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) { - idleAngRotation = 0.6 * PI; - idleAngRotationDir = 1; + if ( camera->rotate >= camera->rotateLimitMax ) + { + camera->rotate = camera->rotateLimitMax; + camera->rotateState = 1; + } } } else { - idleAngRotation += 0.005; + camera->rotate += 0.0015 * camera->rotateSpeed; } } else { - if ( item->flags[SPRITE] ) + if ( sprite ) { - idleAngRotation -= 0.0015; - if ( idleAngRotation <= 0.4 * PI ) + camera->rotate -= 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) { - idleAngRotation = 0.4 * PI; - idleAngRotationDir = 0; + if ( camera->rotate <= camera->rotateLimitMin ) + { + camera->rotate = camera->rotateLimitMin; + camera->rotateState = 0; + } + } + else + { + camera->rotateState = 0; } } else { - idleAngRotation -= 0.0015; + camera->rotate -= 0.0015 * camera->rotateSpeed; } } @@ -21602,14 +21627,16 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) auto ofov = ::fov; ::fov = fov; - view.x = item->x / 16.0 + ((.92 + zoom) * cos(offsetyaw - + ang + idleAngRotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); - view.y = item->y / 16.0 + ((.92 + zoom) * sin(offsetyaw - + ang + idleAngRotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); - view.z = item->z * 2 + z; + const real_t rotation = camera->rotate + (sprite ? (PI / 2) : 0.0); + + view.x = item->x / 16.0 + ((.92 + camera->zoom) * cos(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); + view.y = item->y / 16.0 + ((.92 + camera->zoom) * sin(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? item->yaw : 0))); + view.z = item->z * 2 + camera->height; view.ang = (offsetyaw - PI - + (*cvar_compendium_portrait_static_angle ? item->yaw : 0) + ang + idleAngRotation); //5 * PI / 4; - view.vang = PI / 20 + vang; + + (*cvar_compendium_portrait_static_angle ? item->yaw : 0) + camera->ang + rotation); //5 * PI / 4; + view.vang = PI / 20 + camera->vang; view.winx = pos.x; // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 @@ -21617,6 +21644,7 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) view.winw = pos.w; view.winh = pos.h; + GL_CHECK_ERR(glClear(GL_DEPTH_BUFFER_BIT)); glBeginCamera(&view, false, map); bool b = item->flags[BRIGHT]; if ( !dark ) { item->flags[BRIGHT] = true; } @@ -21643,6 +21671,181 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) ::fov = ofov; } +void drawSpritesPreview(std::string name, std::string modelsPath, SDL_Rect pos, real_t offsetyaw, bool dark) +{ + static int fov = 50; + + bool sprite = false; + Compendium_t::CompendiumCodex_t::Codex_t* codexEntry = nullptr; + auto find = CompendiumEntries.codex.find(name); + if ( find != CompendiumEntries.codex.end() ) + { + codexEntry = &find->second; + sprite = codexEntry->renderedImagePaths.size() > 0; + } + + if ( !codexEntry ) + { + return; + } + + std::vector* limbsArray = nullptr; + Entity* object = nullptr; + Compendium_t::CompendiumView_t* camera = &CompendiumEntries.defaultCamera; + + if ( CompendiumEntries.compendiumObjectLimbs.find(modelsPath) != CompendiumEntries.compendiumObjectLimbs.end() ) + { + auto& entry = CompendiumEntries.compendiumObjectLimbs[modelsPath]; + limbsArray = &entry.entities; + if ( limbsArray->size() == 0 ) + { + return; + } + object = &(limbsArray->at(0)); + camera = &entry.currentCamera; + } + + if ( !object ) + { + return; + } + + if ( keystatus[SDLK_KP_1] ) + { + camera->vang -= 0.01; + } + if ( keystatus[SDLK_KP_3] ) + { + camera->vang += 0.01; + } + if ( keystatus[SDLK_KP_4] ) + { + camera->ang -= 0.01; + } + if ( keystatus[SDLK_KP_6] ) + { + camera->ang += 0.01; + } + if ( keystatus[SDLK_KP_8] ) + { + camera->height += 0.5; + } + if ( keystatus[SDLK_KP_2] ) + { + camera->height -= 0.5; + } + if ( keystatus[SDLK_KP_7] ) + { + camera->zoom -= 0.01; + } + if ( keystatus[SDLK_KP_9] ) + { + camera->zoom += 0.01; + } + + if ( !sprite ) + { + camera->rotateState = 0; + } + + if ( camera->rotateState == 0 ) + { + if ( sprite ) + { + camera->rotate += 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) + { + if ( camera->rotate >= camera->rotateLimitMax ) + { + camera->rotate = camera->rotateLimitMax; + camera->rotateState = 1; + } + } + } + else + { + camera->rotate += 0.0015 * camera->rotateSpeed; + } + } + else + { + if ( sprite ) + { + camera->rotate -= 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) + { + if ( camera->rotate <= camera->rotateLimitMin ) + { + camera->rotate = camera->rotateLimitMin; + camera->rotateState = 0; + } + } + else + { + camera->rotateState = 0; + } + } + else + { + camera->rotate -= 0.0015 * camera->rotateSpeed; + } + } + + view_t& view = monsterPortraitView; + auto ofov = ::fov; + ::fov = fov; + + const real_t rotation = camera->rotate + (sprite ? (PI / 2) : 0.0); + + view.x = object->x / 16.0 + ((.92 + camera->zoom) * cos(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.y = object->y / 16.0 + ((.92 + camera->zoom) * sin(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.z = object->z * 2 + camera->height; + view.ang = (offsetyaw - PI + + (*cvar_compendium_portrait_static_angle ? object->yaw : 0) + camera->ang + rotation); //5 * PI / 4; + view.vang = PI / 20 + camera->vang; + + view.winx = pos.x; + // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 + view.winy = pos.y + (yres - Frame::virtualScreenY); + + view.winw = pos.w; + view.winh = pos.h; + GL_CHECK_ERR(glClear(GL_DEPTH_BUFFER_BIT)); + glBeginCamera(&view, false, map); + + size_t index = 0; + for ( auto& e : *limbsArray ) + { + bool b = e.flags[BRIGHT]; + if ( !dark ) { e.flags[BRIGHT] = true; } + if ( !e.flags[INVISIBLE] ) + { + if ( sprite ) + { + if ( index < codexEntry->renderedImagePaths.size() ) + { + if ( codexEntry->renderedImagePaths[index] != "" ) + { + glDrawSpriteFromImage(&view, &e, codexEntry->renderedImagePaths[index], + REALCOLORS, true, true); + } + } + } + } + + e.flags[BRIGHT] = b; + ++index; + } + + if ( drawingGui ) { + // blending gets disabled after objects are drawn, so re-enable it. + GL_CHECK_ERR(glEnable(GL_BLEND)); + } + glEndCamera(&view, false, map); + ::fov = ofov; +} void glDrawWorldTile(view_t* camera, int mode, map_t& map) { @@ -21938,82 +22141,112 @@ void actObjectPreviewBoulder(Entity* my) } Uint32 drawObjectLastTick = 0; -void drawObjectPreview(std::string name, std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark) +void drawObjectPreview(std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark) { static int fov = 50; - static real_t ang = 0.0; - static real_t vang = 0.0; - static real_t zoom = 0.0; - static real_t z = 0.0; + std::vector* limbsArray = nullptr; + Compendium_t::CompendiumView_t* camera = &CompendiumEntries.defaultCamera; + if ( CompendiumEntries.compendiumObjectLimbs.find(modelsPath) != CompendiumEntries.compendiumObjectLimbs.end() ) + { + auto& entry = CompendiumEntries.compendiumObjectLimbs[modelsPath]; + limbsArray = &entry.entities; + if ( limbsArray->size() == 0 ) + { + return; + } + object = &(limbsArray->at(0)); + camera = &entry.currentCamera; + } + + + if ( !object ) + { + return; + } + + view_t& view = monsterPortraitView; + auto ofov = ::fov; + ::fov = fov; + + bool doTick = false; + if ( drawObjectLastTick != ticks ) + { + drawObjectLastTick = ticks; + doTick = true; + } + if ( keystatus[SDLK_KP_1] ) { - vang -= 0.01; + camera->vang -= 0.01; } if ( keystatus[SDLK_KP_3] ) { - vang += 0.01; + camera->vang += 0.01; } if ( keystatus[SDLK_KP_4] ) { - ang -= 0.01; + camera->ang -= 0.01; } if ( keystatus[SDLK_KP_6] ) { - ang += 0.01; + camera->ang += 0.01; } if ( keystatus[SDLK_KP_8] ) { - z += 0.5; + camera->height += 0.5; } if ( keystatus[SDLK_KP_2] ) { - z -= 0.5; + camera->height -= 0.5; } if ( keystatus[SDLK_KP_7] ) { - zoom -= 0.01; + camera->zoom -= 0.01; } if ( keystatus[SDLK_KP_9] ) { - zoom += 0.01; + camera->zoom += 0.01; } - - view_t& view = monsterPortraitView; - auto ofov = ::fov; - ::fov = fov; - - std::vector* limbsArray = nullptr; - if ( CompendiumEntries.compendiumObjectLimbs.find(modelsPath) != CompendiumEntries.compendiumObjectLimbs.end() ) + if ( camera->rotateState == 0 ) { - limbsArray = &CompendiumEntries.compendiumObjectLimbs[modelsPath]; - if ( limbsArray->size() == 0 ) + camera->rotate += 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) { - return; + if ( camera->rotate >= camera->rotateLimitMax ) + { + camera->rotate = camera->rotateLimitMax; + camera->rotateState = 1; + } } - object = &(limbsArray->at(0)); } - - if ( !object ) + else { - return; + camera->rotate -= 0.0015 * camera->rotateSpeed; + if ( camera->rotateLimit ) + { + if ( camera->rotate <= camera->rotateLimitMin ) + { + camera->rotate = camera->rotateLimitMin; + camera->rotateState = 0; + } + } + else + { + camera->rotateState = 0; + } } - bool doTick = false; - if ( drawObjectLastTick != ticks ) - { - drawObjectLastTick = ticks; - doTick = true; - } + const real_t rotation = camera->rotate; - view.x = object->x / 16.0 + ((.92 + zoom) * cos(offsetyaw - + ang + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); - view.y = object->y / 16.0 + ((.92 + zoom) * sin(offsetyaw - + ang + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); - view.z = object->z * 2 + z; + view.x = object->x / 16.0 + ((.92 + camera->zoom) * cos(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.y = object->y / 16.0 + ((.92 + camera->zoom) * sin(offsetyaw + + camera->ang + rotation + (*cvar_compendium_portrait_static_angle ? object->yaw : 0))); + view.z = object->z * 2 + camera->height; view.ang = (offsetyaw - PI - + (*cvar_compendium_portrait_static_angle ? object->yaw : 0) + ang); //5 * PI / 4; - view.vang = PI / 20 + vang; + + (*cvar_compendium_portrait_static_angle ? object->yaw : 0) + camera->ang + rotation); //5 * PI / 4; + view.vang = PI / 20 + camera->vang; view.winx = pos.x; // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8 @@ -22023,6 +22256,7 @@ void drawObjectPreview(std::string name, std::string modelsPath, Entity* object, view.winh = pos.h; auto& tmpMap = CompendiumEntries.compendiumMap; + GL_CHECK_ERR(glClear(GL_DEPTH_BUFFER_BIT)); glBeginCamera(&view, false, tmpMap); auto findMap = CompendiumEntries.compendiumObjectMapTiles.find(modelsPath); diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index aee2744bd..9d6b7e220 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -24,7 +24,8 @@ bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spe void resetInventorySlotFrames(const int player); void createPlayerInventorySlotFrameElements(Frame* slotFrame); void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark = false); -void drawObjectPreview(std::string name, std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark = false); +void drawObjectPreview(std::string modelsPath, Entity* object, SDL_Rect pos, real_t offsetyaw, bool dark = false); +void drawSpritesPreview(std::string name, std::string modelsPath, SDL_Rect pos, real_t offsetyaw, bool dark = false); void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark = false); extern view_t playerPortraitView[MAXPLAYERS]; void toggleShopBuybackView(const int player); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index d0edd161e..b62dbf90d 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32543,9 +32543,23 @@ namespace MainMenu { #endif static Entity* compendiumMonster = nullptr; + static bool compendiumMonsterOverride = false; + static Uint32 compendiumRNG = 0; static std::pair compendiumEntityCurrent = { "", ""}; // contents name, then model filename static std::vector compendiumMonsterLimbs; static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName = ""); + static void refreshCompendiumCamera(std::string& modelsPath) + { + auto find = CompendiumEntries.compendiumObjectLimbs.find(modelsPath); + if ( find != CompendiumEntries.compendiumObjectLimbs.end() ) + { + if ( find->second.baseCamera.inUse ) + { + find->second.currentCamera = find->second.baseCamera; + } + } + CompendiumEntries.defaultCamera = Compendium_t::CompendiumView_t(); + } static Entity* createCompendiumMonster(Monster creature, real_t x, real_t y) { if ( compendiumMonster ) @@ -32562,7 +32576,7 @@ namespace MainMenu { entity->x = x; entity->y = y; entity->z = 6; - entity->yaw = (local_rng.rand() % 360) * PI / 180.0; + entity->yaw = 0.0; entity->behavior = &actMonster; entity->flags[UPDATENEEDED] = false; entity->flags[INVISIBLE] = true; @@ -32610,10 +32624,38 @@ namespace MainMenu { auto& entry = CompendiumEntries.worldObjects[name]; compendiumEntityCurrent.first = name; - compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; + compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + + refreshCompendiumCamera(compendiumEntityCurrent.second); if ( Frame* page_left = parent->findFrame("page_left") ) { + if ( auto image_viewer = page_left->findFrame("image_viewer") ) + { + image_viewer->setDisabled(entry.imagePath == ""); + if ( auto img = image_viewer->findImage("img") ) + { + img->path = entry.imagePath; + img->disabled = true; + if ( img->path != "" ) + { + img->disabled = false; + if ( auto imgGet = Image::get(img->path.c_str()) ) + { + img->pos.w = imgGet->getWidth(); + img->pos.h = imgGet->getHeight(); + + img->pos.x = image_viewer->getSize().w / 2 - img->pos.w / 2; + img->pos.y = image_viewer->getSize().h / 2 - img->pos.h / 2; + } + } + } + } + if ( auto model_viewer = page_left->findFrame("model_viewer") ) + { + model_viewer->setDisabled(entry.imagePath != ""); + } + if ( auto blurb = page_left->findField("blurb") ) { std::string txt = ""; @@ -32708,6 +32750,18 @@ namespace MainMenu { static void refreshCompendiumEntryItemsBlurb(std::string name, Frame* parent) { + if ( Frame* page_left = parent->findFrame("page_left") ) + { + if ( auto image_viewer = page_left->findFrame("image_viewer") ) + { + image_viewer->setDisabled(true); + } + if ( auto model_viewer = page_left->findFrame("model_viewer") ) + { + model_viewer->setDisabled(false); + } + } + if ( compendium_current == "items" ) { if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) @@ -32722,6 +32776,7 @@ namespace MainMenu { return; } } + auto& entry = compendium_current == "items" ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; if ( Frame* page_left = parent->findFrame("page_left") ) @@ -32920,10 +32975,12 @@ namespace MainMenu { txt->setColor(compendiumContentsSelectedColor); auto find = ItemTooltips.itemNameStringToItemID.find(toSelect.getName()); int itemType = find != ItemTooltips.itemNameStringToItemID.end() ? find->second : -1; + std::string modelsPath = "items_single"; if ( strstr(toSelect.getName(), "spell_") ) { itemType = SPELL_ITEM; Compendium_t::compendiumItemModel.flags[SPRITE] = true; + modelsPath = "spells_single"; } if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { @@ -32955,6 +33012,8 @@ namespace MainMenu { Compendium_t::compendiumItemModel.skill[10] = itemType; Compendium_t::compendiumItemModel.skill[14] = appearance; + refreshCompendiumCamera(modelsPath); + // find records for this item populateRecordsSectionItems(page_right_inner.getParent(), itemLookupIndex); } @@ -33224,8 +33283,39 @@ namespace MainMenu { } auto& entry = CompendiumEntries.codex[name]; + compendiumEntityCurrent.first = name; + compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + + refreshCompendiumCamera(compendiumEntityCurrent.second); + if ( Frame* page_left = parent->findFrame("page_left") ) { + if ( auto image_viewer = page_left->findFrame("image_viewer") ) + { + image_viewer->setDisabled(entry.imagePath == ""); + if ( auto img = image_viewer->findImage("img") ) + { + img->path = entry.imagePath; + img->disabled = true; + if ( img->path != "" ) + { + img->disabled = false; + if ( auto imgGet = Image::get(img->path.c_str()) ) + { + img->pos.w = imgGet->getWidth(); + img->pos.h = imgGet->getHeight(); + + img->pos.x = image_viewer->getSize().w / 2 - img->pos.w / 2; + img->pos.y = image_viewer->getSize().h / 2 - img->pos.h / 2; + } + } + } + } + if ( auto model_viewer = page_left->findFrame("model_viewer") ) + { + model_viewer->setDisabled(entry.imagePath != ""); + } + if ( auto blurb = page_left->findField("blurb") ) { std::string txt = ""; @@ -33264,13 +33354,49 @@ namespace MainMenu { } auto& entry = CompendiumEntries.monsters[name]; compendiumEntityCurrent.first = name; - compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[local_rng.rand() % entry.models.size()]; + if ( compendiumMonsterOverride ) + { + compendiumEntityCurrent.second = ""; + } + else + { + compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + } + + refreshCompendiumCamera(compendiumEntityCurrent.second); + if ( true /*compendiumMonster */ ) { if ( true /*auto myStats = compendiumMonster->getStats()*/ ) { if ( Frame* page_left = parent->findFrame("page_left") ) { + if ( auto image_viewer = page_left->findFrame("image_viewer") ) + { + image_viewer->setDisabled(entry.imagePath == ""); + if ( auto img = image_viewer->findImage("img") ) + { + img->path = entry.imagePath; + img->disabled = true; + if ( img->path != "" ) + { + img->disabled = false; + if ( auto imgGet = Image::get(img->path.c_str()) ) + { + img->pos.w = imgGet->getWidth(); + img->pos.h = imgGet->getHeight(); + + img->pos.x = image_viewer->getSize().w / 2 - img->pos.w / 2; + img->pos.y = image_viewer->getSize().h / 2 - img->pos.h / 2; + } + } + } + } + if ( auto model_viewer = page_left->findFrame("model_viewer") ) + { + model_viewer->setDisabled(entry.imagePath != ""); + } + if ( auto blurb = page_left->findField("blurb") ) { std::string txt = ""; @@ -33617,6 +33743,8 @@ namespace MainMenu { page_left_title->setText(entry.first.c_str()); } content = entry.second; + + compendiumRNG = local_rng.rand(); } if ( compendium_current == "codex" ) @@ -33644,6 +33772,11 @@ namespace MainMenu { if ( auto myStats = monster->getStats() ) { myStats->setAttribute("monster_portrait", "true"); + (void)actMonster(monster); + monster->yaw = 0.0; + myStats->EFFECTS[EFF_ASLEEP] = false; + monsterAnimate(compendiumMonster, myStats, 0.0); + monsterAnimate(compendiumMonster, myStats, 0.0); } } refreshCompendiumEntryMonster(content, compendiumFrame); @@ -34145,6 +34278,51 @@ namespace MainMenu { [](int argc, const char** argv) { CompendiumEntries.exportCurrentMonster(compendiumMonster); }); + static ConsoleVariable cvar_compendiumautoreload("/compendiumautoreload", true); + + static void compendiumDebugRefresh() + { + consoleCommand("/reloadcompendiumlimbs"); + if ( compendium_current == "monsters" ) + { + CompendiumEntries.readMonstersFromFile(); + refreshCompendiumEntryMonster(Compendium_t::CompendiumMonsters_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + else if ( compendium_current == "world" ) + { + CompendiumEntries.readWorldFromFile(); + refreshCompendiumEntryWorld(Compendium_t::CompendiumWorld_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + else if ( compendium_current == "codex" ) + { + CompendiumEntries.readCodexFromFile(); + refreshCompendiumEntryCodex(Compendium_t::CompendiumCodex_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + } + else if ( compendium_current == "items" ) + { + CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); + refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + std::string modelsPath = "items_single"; + if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) + { + modelsPath = "spells_single"; + } + refreshCompendiumCamera(modelsPath); + } + else if ( compendium_current == "magic" ) + { + CompendiumEntries.readItemsFromFile(); + CompendiumEntries.readMagicFromFile(); + refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + std::string modelsPath = "items_single"; + if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) + { + modelsPath = "spells_single"; + } + refreshCompendiumCamera(modelsPath); + } + } static void openCompendium() { auto dimmer = main_menu_frame->addFrame("dimmer"); @@ -34516,6 +34694,16 @@ namespace MainMenu { "*images/ui/Main Menus/AdventureArchives/C_Subject_Frame_00.png", "page left top img"); auto blurbImg = page_left->addImage(SDL_Rect{ left_top_img->pos.x, left_top_img->pos.y + left_top_img->pos.h + 12, 382, 122 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Lore_Frame_00.png", "page left bottom img"); + page_left->setTickCallback([](Widget& widget) { + if ( auto debugBtnFrame = static_cast(&widget)->findFrame("debug frame") ) + { + debugBtnFrame->setDisabled(false); + if ( keystatus[SDLK_g] ) + { + debugBtnFrame->setDisabled(true); + } + } + }); auto page_left_title = window->addField("page_left_title", 128); page_left_title->setFont("fonts/kongtext.ttf#16#0"); @@ -34543,11 +34731,15 @@ namespace MainMenu { model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { if ( compendium_current == "monsters" ) { - drawObjectPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, compendiumMonster, pos, 0.0); + drawObjectPreview(compendiumEntityCurrent.second, compendiumMonster, pos, 0.0); } else if ( compendium_current == "world" ) { - drawObjectPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, nullptr, pos, 0.0); + drawObjectPreview(compendiumEntityCurrent.second, nullptr, pos, 0.0); + } + else if ( compendium_current == "codex" ) + { + drawSpritesPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, pos, 0.0); } else if ( compendium_current == "items" ) { @@ -34559,91 +34751,211 @@ namespace MainMenu { } }); model_viewer->setTickCallback([](Widget& widget) { - /*if ( ticks % TICKS_PER_SECOND == 0 ) - { - compendiumMonster->attack(compendiumMonster->getAttackPose(), 0, nullptr); - }*/ - static ConsoleVariable cvar_compendiumautoreload("/compendiumautoreload", true); if ( *cvar_compendiumautoreload && (ticks % TICKS_PER_SECOND/2 == 0) ) { - consoleCommand("/reloadcompendiumlimbs"); - if ( compendium_current == "monsters" ) - { - CompendiumEntries.readMonstersFromFile(); - refreshCompendiumEntryMonster(Compendium_t::CompendiumMonsters_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); - } - else if ( compendium_current == "world" ) + compendiumDebugRefresh(); + } + }); + + auto image_viewer = page_left->addFrame("image_viewer"); + image_viewer->setSize(model_viewer->getSize()); + { + auto img = image_viewer->addImage(SDL_Rect{0, 0, 0, 0}, 0xFFFFFFFF, "", "img"); + img->disabled = true; + } + image_viewer->setDisabled(true); + image_viewer->setTickCallback([](Widget& widget) { + if ( *cvar_compendiumautoreload && (ticks % TICKS_PER_SECOND / 2 == 0) ) + { + if ( auto parent = static_cast(widget.getParent()) ) { - CompendiumEntries.readWorldFromFile(); - refreshCompendiumEntryWorld(Compendium_t::CompendiumWorld_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + if ( auto model_viewer = parent->findFrame("model_viewer") ) + { + if ( model_viewer->isDisabled() ) + { + compendiumDebugRefresh(); + } + } } - else if ( compendium_current == "codex" ) + } + }); + + // debug btns + { + auto debugBtnFrame = page_left->addFrame("debug frame"); + debugBtnFrame->setSize(model_viewer->getSize()); + + Button* btn = debugBtnFrame->addButton("anim 1"); + btn->setSize(SDL_Rect{ debugBtnFrame->getSize().w - 26, debugBtnFrame->getSize().h - 26, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("ANM"); + btn->setTickCallback([](Widget& widget) { + auto btn = static_cast(&widget); + static Uint32 t = 0; + if ( btn->isCurrentlyPressed() && t != ticks && ticks % 5 == 0 ) { - CompendiumEntries.readCodexFromFile(); - refreshCompendiumEntryCodex(Compendium_t::CompendiumCodex_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + t = ticks; + if ( compendiumMonster ) + { + monsterAnimate(compendiumMonster, compendiumMonster->getStats(), 0.2); + } } - else if ( compendium_current == "items" ) + }); + + btn = debugBtnFrame->addButton("tmp"); + btn->setSize(SDL_Rect{ 0, debugBtnFrame->getSize().h - 26, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("MOD"); + btn->setOntop(true); + btn->setCallback([](Button& button) { + compendiumMonsterOverride = !compendiumMonsterOverride; + }); + btn->setTickCallback([](Widget& widget) { + auto btn = static_cast(&widget); + if ( compendiumMonsterOverride ) { - CompendiumEntries.readItemsFromFile(); - CompendiumEntries.readMagicFromFile(); - refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + btn->setTextColor(makeColorRGB(0, 255, 0)); + btn->setTextHighlightColor(makeColorRGB(0, 255, 0)); + auto parent = static_cast(btn->getParent()); + if ( auto btn = parent->findButton("anim 1") ) + { + btn->setDisabled(false); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("anim 2") ) + { + btn->setDisabled(false); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("anim 3") ) + { + btn->setDisabled(false); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("exp") ) + { + btn->setDisabled(false); + btn->setInvisible(btn->isDisabled()); + } } - else if ( compendium_current == "magic" ) + else { - CompendiumEntries.readItemsFromFile(); - CompendiumEntries.readMagicFromFile(); - refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + btn->setTextColor(makeColorRGB(128, 128, 128)); + btn->setTextHighlightColor(makeColorRGB(128, 128, 128)); + auto parent = static_cast(btn->getParent()); + if ( auto btn = parent->findButton("anim 1") ) + { + btn->setDisabled(true); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("anim 2") ) + { + btn->setDisabled(true); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("anim 3") ) + { + btn->setDisabled(true); + btn->setInvisible(btn->isDisabled()); + } + if ( auto btn = parent->findButton("exp") ) + { + btn->setDisabled(true); + btn->setInvisible(btn->isDisabled()); + } } - } - /*if ( keystatus[SDLK_g] ) - { - keystatus[SDLK_g] = 0; - if ( compendiumMonster ) + }); + + btn = debugBtnFrame->addButton("anim 2"); + btn->setSize(SDL_Rect{ debugBtnFrame->getSize().w - 26, debugBtnFrame->getSize().h - 26 * 2 - 4, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("RST"); + btn->setOntop(true); + btn->setTickCallback([](Widget& widget) { + auto btn = static_cast(&widget); + static Uint32 t = 0; + if ( btn->isCurrentlyPressed() && t != ticks && ticks % 5 == 0 ) { - if ( auto myStats = compendiumMonster->getStats() ) + t = ticks; + if ( compendiumMonster ) { - int type = myStats->type; - if ( keystatus[SDLK_LCTRL] ) - { - type -= 1; - if ( type >= NUMMONSTERS ) - { - type = NUMMONSTERS - 1; - } - while ( type == NOTHING || type == OCTOPUS || type == CRAB ) - { - type--; - } - if ( type < 0 ) - { - type = NUMMONSTERS - 1; - } - } - else if ( !keystatus[SDLK_LSHIFT] ) - { - type += 1; - if ( type >= NUMMONSTERS ) - { - type = HUMAN; - } - while ( type == NOTHING || type == OCTOPUS || type == CRAB ) - { - type++; - } - } - if ( auto monster = createCompendiumMonster((Monster)type, 8, 8) ) - { - if ( auto myStats = monster->getStats() ) - { - myStats->setAttribute("monster_portrait", "true"); - } - refreshCompendiumEntryMonster(main_menu_frame->findFrame("compendium")); - } + monsterAnimate(compendiumMonster, compendiumMonster->getStats(), 0.0); } } - }*/ + }); - }); + btn = debugBtnFrame->addButton("exp"); + btn->setSize(SDL_Rect{ 0, debugBtnFrame->getSize().h - 26 * 2 - 4, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("EXP"); + btn->setOntop(true); + btn->setCallback([](Button& button) { + consoleCommand("/compendium_export_monster"); + soundActivate(); + }); + + btn = debugBtnFrame->addButton("anim 3"); + btn->setSize(SDL_Rect{ debugBtnFrame->getSize().w - 26, debugBtnFrame->getSize().h - 26 * 3 - 8, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("ATK"); + btn->setOntop(true); + btn->setCallback([](Button& button) { + compendiumMonster->attack(compendiumMonster->getAttackPose(), 0, nullptr); + }); + + btn = debugBtnFrame->addButton("tmp"); + btn->setSize(SDL_Rect{ 0, debugBtnFrame->getSize().h - 26 * 3 - 8, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setText("REF"); + btn->setOntop(true); + btn->setCallback([](Button& button) { + compendiumDebugRefresh(); + }); + + btn = debugBtnFrame->addButton("tmp"); + btn->setSize(SDL_Rect{ 0, debugBtnFrame->getSize().h - 26 * 4 - 12, 26, 26 }); + btn->setBackground("*images/ui/Inventory/chests/Button_X_00.png"); + btn->setBackgroundHighlighted("*images/ui/Inventory/chests/Button_XHigh_00.png"); + btn->setBackgroundActivated("*images/ui/Inventory/chests/Button_XPress_00.png"); + btn->setFont(smallfont_outline); + btn->setTextColor(makeColorRGB(255, 255, 255)); + btn->setText("ATO"); + btn->setOntop(true); + btn->setCallback([](Button& button) { + *cvar_compendiumautoreload = !*cvar_compendiumautoreload; + }); + btn->setTickCallback([](Widget& widget) { + auto btn = static_cast(&widget); + if ( *cvar_compendiumautoreload ) + { + btn->setTextColor(makeColorRGB(0, 255, 0)); + btn->setTextHighlightColor(makeColorRGB(0, 255, 0)); + } + else + { + btn->setTextColor(makeColorRGB(128, 128, 128)); + btn->setTextHighlightColor(makeColorRGB(128, 128, 128)); + } + }); + } auto page_right = window->addFrame("page_right"); page_right->setSize(SDL_Rect{ background->pos.x + 480 + 40, background->pos.y + 38, 386, 472}); From 98b9bb4fe00de424e603e75284c68d2c6a33f556 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 28 Jun 2024 20:45:41 +1000 Subject: [PATCH 024/244] * fix monster curve generation --- src/maps.cpp | 6 ++++ src/mod_tools.hpp | 75 +++++++++++++++++++++++++++++++---------------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index 9e53e1a20..f4a2cc5c2 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4096,6 +4096,7 @@ void assignActions(map_t* map) } bool customMonsterCurveExists = false; + monsterCurveCustomManager.followersToGenerateForLeaders.clear(); if ( !monsterCurveCustomManager.inUse() ) { monsterCurveCustomManager.readFromFile(mapseed); @@ -7257,6 +7258,11 @@ void assignActions(map_t* map) list_RemoveNode(chest->mynode); } + if ( monsterCurveCustomManager.inUse() ) + { + monsterCurveCustomManager.generateFollowersForLeaders(); + } + keepInventoryGlobal = svFlags & SV_FLAG_KEEPINVENTORY; } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 484d283e5..51fd7da28 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -1375,6 +1375,16 @@ class MonsterCurveCustomManager }; std::vector allLevelCurves; + + struct FollowerGenerateDetails_t + { + real_t x = 0.0; + real_t y = 0.0; + int leaderType = NOTHING; + Uint32 uid = 0; + std::string followerName = ""; + }; + std::vector followersToGenerateForLeaders; inline bool inUse() { return usingCustomManager; }; void readFromFile(Uint32 seed) @@ -1630,38 +1640,53 @@ class MonsterCurveCustomManager std::string followerName = statEntry->getFollowerVariant(); if ( followerName.compare("") && followerName.compare("none") ) { - MonsterStatCustomManager::StatEntry* followerEntry = monsterStatCustomManager.readFromFile(followerName.c_str()); - if ( followerEntry ) + followersToGenerateForLeaders.push_back(FollowerGenerateDetails_t()); + auto& entry = followersToGenerateForLeaders.back(); + entry.followerName = followerName; + entry.x = entity->x; + entry.y = entity->y; + entry.uid = entity->getUID(); + entry.leaderType = myStats->type; + } + --statEntry->numFollowers; + } + delete statEntry; + } + } + + void generateFollowersForLeaders() + { + for ( auto& entry : followersToGenerateForLeaders ) + { + MonsterStatCustomManager::StatEntry* followerEntry = monsterStatCustomManager.readFromFile(entry.followerName.c_str()); + if ( followerEntry ) + { + Entity* summonedFollower = summonMonsterNoSmoke(static_cast(followerEntry->type), entry.x, entry.y); + if ( summonedFollower ) + { + if ( summonedFollower->getStats() ) { - Entity* summonedFollower = summonMonster(static_cast(followerEntry->type), entity->x, entity->y); - if ( summonedFollower ) - { - if ( summonedFollower->getStats() ) - { - followerEntry->setStatsAndEquipmentToMonster(summonedFollower->getStats()); - summonedFollower->getStats()->leader_uid = entity->getUID(); - } - summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); - } - delete followerEntry; + followerEntry->setStatsAndEquipmentToMonster(summonedFollower->getStats()); + summonedFollower->getStats()->leader_uid = entry.uid; } - else + summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); + } + delete followerEntry; + } + else + { + Entity* summonedFollower = summonMonsterNoSmoke(static_cast(entry.leaderType), entry.x, entry.y); + if ( summonedFollower ) + { + if ( summonedFollower->getStats() ) { - Entity* summonedFollower = summonMonster(myStats->type, entity->x, entity->y); - if ( summonedFollower ) - { - if ( summonedFollower->getStats() ) - { - summonedFollower->getStats()->leader_uid = entity->getUID(); - } - summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); - } + summonedFollower->getStats()->leader_uid = entry.uid; } + summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); } - --statEntry->numFollowers; } - delete statEntry; } + followersToGenerateForLeaders.clear(); } void writeSampleToDocument() From 5f8107e81bac5ffbc4e0b2625c8eee3eb0eb88f9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 28 Jun 2024 20:48:10 +1000 Subject: [PATCH 025/244] * small fix --- src/mod_tools.hpp | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 51fd7da28..36ef014c2 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -1656,33 +1656,36 @@ class MonsterCurveCustomManager void generateFollowersForLeaders() { - for ( auto& entry : followersToGenerateForLeaders ) + if ( multiplayer != CLIENT ) { - MonsterStatCustomManager::StatEntry* followerEntry = monsterStatCustomManager.readFromFile(entry.followerName.c_str()); - if ( followerEntry ) + for ( auto& entry : followersToGenerateForLeaders ) { - Entity* summonedFollower = summonMonsterNoSmoke(static_cast(followerEntry->type), entry.x, entry.y); - if ( summonedFollower ) + MonsterStatCustomManager::StatEntry* followerEntry = monsterStatCustomManager.readFromFile(entry.followerName.c_str()); + if ( followerEntry ) { - if ( summonedFollower->getStats() ) + Entity* summonedFollower = summonMonsterNoSmoke(static_cast(followerEntry->type), entry.x, entry.y); + if ( summonedFollower ) { - followerEntry->setStatsAndEquipmentToMonster(summonedFollower->getStats()); - summonedFollower->getStats()->leader_uid = entry.uid; + if ( summonedFollower->getStats() ) + { + followerEntry->setStatsAndEquipmentToMonster(summonedFollower->getStats()); + summonedFollower->getStats()->leader_uid = entry.uid; + } + summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); } - summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); + delete followerEntry; } - delete followerEntry; - } - else - { - Entity* summonedFollower = summonMonsterNoSmoke(static_cast(entry.leaderType), entry.x, entry.y); - if ( summonedFollower ) + else { - if ( summonedFollower->getStats() ) + Entity* summonedFollower = summonMonsterNoSmoke(static_cast(entry.leaderType), entry.x, entry.y); + if ( summonedFollower ) { - summonedFollower->getStats()->leader_uid = entry.uid; + if ( summonedFollower->getStats() ) + { + summonedFollower->getStats()->leader_uid = entry.uid; + } + summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); } - summonedFollower->seedEntityRNG(monster_curve_rng.getU32()); } } } From 936c70e81539f554fe96ce6c7c68832f1b8d6ff2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 10 Jul 2024 00:21:34 +1000 Subject: [PATCH 026/244] * fix weight display not using getCharacterWeight() --- src/ui/GameUI.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 9adc6d588..8e426b7d7 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -19775,16 +19775,7 @@ void Player::CharacterSheet_t::updateAttributes() if ( auto field = attributesInnerFrame->findField("weight text stat") ) { - Sint32 weight = 0; - for ( node_t* node = stats[player.playernum]->inventory.first; node != NULL; node = node->next ) - { - Item* item = (Item*)node->element; - if ( item ) - { - weight += item->getWeight(); - } - } - weight += stats[player.playernum]->getGoldWeight(); + Sint32 weight = player.movement.getCharacterWeight(); snprintf(buf, sizeof(buf), "%d", weight); if ( strcmp(buf, field->getText()) ) { From d5d55d740f195c24d3f08ba1ea4038f48fc7f8c6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 10 Jul 2024 00:32:16 +1000 Subject: [PATCH 027/244] * codex records mostly wrapped --- src/actarrow.cpp | 12 + src/actladder.cpp | 6 + src/actmonster.cpp | 10 + src/actplayer.cpp | 36 +++ src/actthrown.cpp | 19 ++ src/entity.cpp | 205 ++++++++++++++- src/game.cpp | 8 + src/interface/interface.cpp | 3 + src/magic/castSpell.cpp | 5 + src/menu.cpp | 45 +++- src/mod_tools.cpp | 483 ++++++++++++++++++++++++++++++++++-- src/mod_tools.hpp | 161 +++++++++++- src/net.cpp | 6 + src/player.hpp | 4 + src/ui/MainMenu.cpp | 116 +++++++-- 15 files changed, 1077 insertions(+), 42 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index 7a6b966c8..e18d9b47d 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -685,6 +685,13 @@ void actArrow(Entity* my) if ( parent->behavior == &actPlayer ) { + if ( oldHP > hitstats->HP ) + { + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RANGED_DMG_TOTAL, "missiles", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RANGED_HITS, "missiles", 1); + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_CLASS_RANGED_HITS_RUN, "missiles", 1); + } + if ( itemTypeIsQuiver((ItemType)my->arrowQuiverType) ) { Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_MAX, (ItemType)my->arrowQuiverType, damage); @@ -798,6 +805,11 @@ void actArrow(Entity* my) { parent->awardXP( hit.entity, true, true ); spawnBloodVialOnMonsterDeath(hit.entity, hitstats, parent); + + if ( parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RANGED_KILLS, "missiles", 1); + } } // alert the monster diff --git a/src/actladder.cpp b/src/actladder.cpp index d15f83474..cdc6e01a2 100644 --- a/src/actladder.cpp +++ b/src/actladder.cpp @@ -553,6 +553,9 @@ void actWinningPortal(Entity* my) { continue; } + + Compendium_t::Events_t::sendClientDataOverNet(c); + strcpy((char*)net_packet->data, "WING"); net_packet->data[4] = victory; net_packet->data[5] = cutscene; @@ -780,6 +783,9 @@ void Entity::actExpansionEndGamePortal() { continue; } + + Compendium_t::Events_t::sendClientDataOverNet(c); + strcpy((char*)net_packet->data, "WING"); net_packet->data[4] = victory; net_packet->data[5] = 0; diff --git a/src/actmonster.cpp b/src/actmonster.cpp index ba791c75b..a0f70125e 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -408,6 +408,9 @@ void ShopkeeperPlayerHostility_t::setWantedLevel(ShopkeeperPlayerHostility_t::Pl { messagePlayerColor(h.player, MESSAGE_STATUS, makeColorRGB(255, 0, 0), Language::get(4305)); } + + Compendium_t::Events_t::eventUpdateCodex(h.player, Compendium_t::CPDM_WANTED_RUNS, "wanted", 1); + Compendium_t::Events_t::eventUpdateCodex(h.player, Compendium_t::CPDM_WANTED_TIMES_RUN, "wanted", 1); } if ( h.wantedLevel != wantedLevel ) { @@ -458,6 +461,8 @@ void ShopkeeperPlayerHostility_t::setWantedLevel(ShopkeeperPlayerHostility_t::Pl { setWantedLevel(*h2, WantedLevel::WANTED_FOR_ACCESSORY, shopkeeper, false); ++h2->numAccessories; + Compendium_t::Events_t::eventUpdateCodex(h.player, Compendium_t::CPDM_WANTED_INFLUENCE, "wanted", 1); + Compendium_t::Events_t::eventUpdateCodex(h2->player, Compendium_t::CPDM_WANTED_CRIMES_RUN, "wanted", 1); continue; } } @@ -487,6 +492,8 @@ void ShopkeeperPlayerHostility_t::setWantedLevel(ShopkeeperPlayerHostility_t::Pl { setWantedLevel(*h2, WantedLevel::WANTED_FOR_ACCESSORY, shopkeeper, false); ++h2->numAccessories; + Compendium_t::Events_t::eventUpdateCodex(h.player, Compendium_t::CPDM_WANTED_INFLUENCE, "wanted", 1); + Compendium_t::Events_t::eventUpdateCodex(h2->player, Compendium_t::CPDM_WANTED_CRIMES_RUN, "wanted", 1); } } } @@ -505,6 +512,7 @@ void ShopkeeperPlayerHostility_t::onShopkeeperDeath(Entity* my, Stat* myStats, E { setWantedLevel(*h, WantedLevel::WANTED_FOR_KILL, my, true); ++h->numKills; + Compendium_t::Events_t::eventUpdateCodex(h->player, Compendium_t::CPDM_WANTED_CRIMES_RUN, "wanted", 1); } } else if ( attacker->behavior == &actMonster ) @@ -527,6 +535,7 @@ void ShopkeeperPlayerHostility_t::onShopkeeperHit(Entity* my, Stat* myStats, Ent { setWantedLevel(*h, WantedLevel::WANTED_FOR_AGGRESSION, my, true); ++h->numAggressions; + Compendium_t::Events_t::eventUpdateCodex(h->player, Compendium_t::CPDM_WANTED_CRIMES_RUN, "wanted", 1); } } } @@ -1891,6 +1900,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], } Compendium_t::Events_t::eventUpdateMonster(monsterclicked, Compendium_t::CPDM_RECRUITED, my, 1); + Compendium_t::Events_t::eventUpdateCodex(monsterclicked, Compendium_t::CPDM_RACE_RECRUITS, "races", 1); if ( (stats[monsterclicked]->type != HUMAN && stats[monsterclicked]->type != AUTOMATON) && myStats->type == HUMAN ) { steamAchievementClient(monsterclicked, "BARONY_ACH_PITY_FRIEND"); diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 2a5447fc0..f7e63835e 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -3324,6 +3324,17 @@ void Player::PlayerMovement_t::handlePlayerMovement(bool useRefreshRateDelta) } real_t speedFactor = getSpeedFactor(weightratio, statGetDEX(stats[PLAYER_NUM], players[PLAYER_NUM]->entity)); + + if ( ticks % TICKS_PER_SECOND == 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_MAX, "wgt", getCharacterWeight()); + if ( speedFactor >= getMaximumSpeed() ) + { + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_MAX_MOVE_100, "wgt", getCharacterWeight()); + } + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_SLOWEST, "wgt", (int)(speedFactor * 100.0)); + } + static ConsoleVariable cvar_debugspeedfactor("/player_showspeedfactor", false); if ( *cvar_debugspeedfactor && ticks % 50 == 0 ) { @@ -4528,6 +4539,9 @@ void actPlayer(Entity* my) PLAYER_INIT = 1; my->flags[BURNABLE] = true; + players[PLAYER_NUM]->compendiumProgress.playerDistAccum = 0.0; + players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; + Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = my->x; nametag->y = my->y; @@ -8073,6 +8087,28 @@ void actPlayer(Entity* my) my->y += PLAYER_VELY; dist = sqrt(PLAYER_VELX * PLAYER_VELX + PLAYER_VELY * PLAYER_VELY); } + + if ( dist >= 0.01 ) + { + players[PLAYER_NUM]->compendiumProgress.playerDistAccum += dist; + } + if ( stats[PLAYER_NUM]->sneaking ) + { + players[PLAYER_NUM]->compendiumProgress.playerSneakTime++; + } + if ( ticks % (TICKS_PER_SECOND * 5) == 0 ) + { + if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_TUTORIAL ) + { + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_DISTANCE_MAX_RUN, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerDistAccum); + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_DISTANCE_MAX_FLOOR, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerDistAccum, false, -1, true); + } + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_DISTANCE_TRAVELLED, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerDistAccum); + players[PLAYER_NUM]->compendiumProgress.playerDistAccum = 0.0; + + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_SNEAK_TIME, "sneaking", players[PLAYER_NUM]->compendiumProgress.playerSneakTime); + players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; + } } if ( !players[PLAYER_NUM]->isLocalPlayer() && multiplayer == SERVER ) diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 8a1311ac7..292ce1e0c 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -812,12 +812,22 @@ void actThrown(Entity* my) char whatever[256] = ""; if ( !friendlyHit ) { + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); if ( parent && parent->behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_MAX, item->type, damage); + if ( cat == THROWN ) + { + if ( oldHP > hitstats->HP ) + { + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_THROWN_DMG_TOTAL, "thrown", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_THROWN_TOTAL_HITS, "thrown", 1); + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_CLASS_THROWN_HITS_RUN, "thrown", 1); + } + } } } @@ -947,6 +957,7 @@ void actThrown(Entity* my) if ( hit.entity->monsterAllyIndex != parent->skill[2] ) { Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RACE_RECRUITS, "races", 1); } hit.entity->monsterAllyIndex = parent->skill[2]; if ( multiplayer == SERVER ) @@ -1336,6 +1347,14 @@ void actThrown(Entity* my) { parent->awardXP(hit.entity, true, true); spawnBloodVialOnMonsterDeath(hit.entity, hitstats, parent); + + if ( parent->behavior == &actPlayer ) + { + if ( cat == THROWN ) + { + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_THROWN_KILLS, "thrown", 1); + } + } } bool doAlert = true; diff --git a/src/entity.cpp b/src/entity.cpp index d3e04c25d..6020d1ddb 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1854,6 +1854,30 @@ bool Entity::increaseSkill(int skill, bool notify) } } myStats->EXP += 2; + + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_XP_MAX_IN_FLOOR, "xp", 2, false, -1, true); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_XP_MAX_INSTANCE, "xp", 2); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_XP_SKILLS, "xp", 2); + + const char* skillstr = Compendium_t::getSkillStringForCompendium(skill); + if ( strcmp(skillstr, "") ) + { + if ( myStats->getProficiency(skill) == 100 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_LEGENDS, skillstr, 1); + } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_UPS, skillstr, 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_UPS_RUN_MAX, skillstr, 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_MAX, skillstr, myStats->getProficiency(skill)); + } + if ( skill == PRO_STEALTH ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SNEAK_SKILLUP_FLOOR, "sneaking", 1, false, -1, true); + } + } + increased = true; } @@ -2343,6 +2367,10 @@ void Entity::setHP(int amount) if ( this->behavior == &actPlayer && entitystats->OLDHP >= entitystats->HP ) { inputs.addRumbleForPlayerHPLoss(skill[2], amount); + if ( healthDiff > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_HP_LOST_RUN, "hp", healthDiff); + } } if ( multiplayer == SERVER ) @@ -2534,6 +2562,7 @@ void Entity::drainMP(int amount, bool notifyOverexpend) } int overdrawn = 0; + Sint32 oldMP = entitystats->MP; entitystats->MP -= amount; int player = -1; for ( int i = 0; i < MAXPLAYERS; ++i ) @@ -2603,6 +2632,12 @@ void Entity::drainMP(int amount, bool notifyOverexpend) } } + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MP_SPENT_RUN, "mp", oldMP - entitystats->MP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MP_SPENT_TOTAL, "mp", oldMP - entitystats->MP); + } + if ( overdrawn < 0 ) { if ( player >= 0 && notifyOverexpend ) @@ -2680,6 +2715,11 @@ bool Entity::safeConsumeMP(int amount) } } } + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_MP_SPENT_RUN, "mp", amount); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_MP_SPENT_TOTAL, "mp", amount); + } this->modMP(-amount); return true; } @@ -2966,6 +3006,8 @@ void Entity::handleEffects(Stat* myStats) { // players only. this->playerStatIncrease(client_classes[player], increasestat); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_LVL_GAINED, "leveling up", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_LVL_MAX, "leveling up", myStats->LVL); } else if ( behavior == &actMonster && monsterAllySummonRank != 0 ) { @@ -3154,11 +3196,15 @@ void Entity::handleEffects(Stat* myStats) { StatUps.at(StatUps.size() - 1).increaseStat += 1; myStats->STR++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "str", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "str", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "str", myStats->STR); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_STR_MAX, "str", myStats->STR); break; case STAT_DEX: // DEX StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->DEX, 1)); @@ -3170,11 +3216,15 @@ void Entity::handleEffects(Stat* myStats) { StatUps.at(StatUps.size() - 1).increaseStat += 1; myStats->DEX++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "dex", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "dex", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "dex", myStats->DEX); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_DEX_MAX, "dex", myStats->DEX); break; case STAT_CON: // CON StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CON, 1)); @@ -3186,11 +3236,15 @@ void Entity::handleEffects(Stat* myStats) { StatUps.at(StatUps.size() - 1).increaseStat += 1; myStats->CON++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "con", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "con", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "con", myStats->CON); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CON_MAX, "con", myStats->CON); break; case STAT_INT: // INT StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->INT, 1)); @@ -3202,11 +3256,15 @@ void Entity::handleEffects(Stat* myStats) { StatUps.at(StatUps.size() - 1).increaseStat += 1; myStats->INT++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "int", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "int", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "int", myStats->INT); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_INT_MAX, "int", myStats->INT); break; case STAT_PER: // PER StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->PER, 1)); @@ -3223,6 +3281,9 @@ void Entity::handleEffects(Stat* myStats) //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "per", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "per", myStats->PER); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_PER_MAX, "per", myStats->PER); break; case STAT_CHR: // CHR StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CHR, 1)); @@ -3234,11 +3295,15 @@ void Entity::handleEffects(Stat* myStats) { StatUps.at(StatUps.size() - 1).increaseStat += 1; myStats->CHR++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "chr", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "chr", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "chr", myStats->CHR); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CHR_MAX, "chr", myStats->CHR); break; } } @@ -3826,8 +3891,18 @@ void Entity::handleEffects(Stat* myStats) this->char_heal = 0; if ( hpMod > 0 ) { + Sint32 oldHP = myStats->HP; this->modHP(hpMod); naturalHeal = true; + if ( behavior == &actPlayer ) + { + if ( oldHP < myStats->HP ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_HP_RUN, "rgn", myStats->HP - oldHP); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_HP_SUM, "rgn", myStats->HP - oldHP); + } + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_HP_RATE_MAX, "rgn", healthRegenInterval); + } } } } @@ -3909,7 +3984,16 @@ void Entity::handleEffects(Stat* myStats) this->char_energize = 0; if ( mpMod > 0 ) { + Sint32 oldMP = myStats->MP; this->modMP(mpMod); + if ( oldMP < myStats->MP ) + { + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_MP_RUN, "rgn", myStats->MP - oldMP); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_MP_SUM, "rgn", myStats->MP - oldMP); + } + } } } } @@ -4002,7 +4086,17 @@ void Entity::handleEffects(Stat* myStats) this->char_energize = 0; if ( mpMod > 0 ) { + Sint32 oldMP = myStats->MP; this->modMP(mpMod); + if ( behavior == &actPlayer ) + { + if ( oldMP < myStats->MP ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_MP_RUN, "rgn", myStats->MP - oldMP); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_MP_SUM, "rgn", myStats->MP - oldMP); + } + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_RGN_MP_RATE_MAX, "rgn", manaRegenInterval); + } } } } @@ -6945,6 +7039,10 @@ void Entity::attack(int pose, int charge, Entity* target) if ( myStats->weapon->status != BROKEN ) { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(659)); + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEGRADED, myStats->weapon->type, 1); + } } else { @@ -7681,6 +7779,10 @@ void Entity::attack(int pose, int charge, Entity* target) else { messagePlayer(player, MESSAGE_COMBAT, Language::get(665)); + if ( behavior == &actPlayer && myStats->weapon ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEGRADED, myStats->weapon->type, 1); + } } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -7846,6 +7948,10 @@ void Entity::attack(int pose, int charge, Entity* target) else { messagePlayer(player, MESSAGE_COMBAT, Language::get(665)); + if ( behavior == &actPlayer && myStats->weapon ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEGRADED, myStats->weapon->type, 1); + } } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -8487,7 +8593,8 @@ void Entity::attack(int pose, int charge, Entity* target) int olddamage = damage; - damage *= std::max(charge, MAXCHARGE / 2) / ((double)(MAXCHARGE / 2)); + const int chargeMult = std::max(charge, MAXCHARGE / 2) / ((double)(MAXCHARGE / 2)); + damage *= chargeMult; bool parashuProc = false; if ( myStats->weapon && !shapeshifted ) { @@ -8532,7 +8639,38 @@ void Entity::attack(int pose, int charge, Entity* target) } } + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); // do the damage + + if ( hitstats->HP < oldHP ) + { + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_DMG_TOTAL, "melee", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_HITS, "melee", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_MELEE_HITS_RUN, "melee", 1); + if ( chargeMult > 1 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRITS_DMG_TOTAL, "crits", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRIT_HITS, "crits", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_CRITS_HITS_RUN, "crits", 1); + } + if ( flanking ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_DMG, "flanking", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_HITS, "flanking", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_HITS_RUN, "flanking", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_DMG_RUN, "flanking", oldHP - hitstats->HP); + } + else if ( backstab ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_BACKSTAB_HITS, "backstabs", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_HITS_RUN, "backstabs", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_DMG_RUN, "backstabs", oldHP - hitstats->HP); + } + } + } + bool skillIncreased = false; // skill increase // can raise skills up to skill level 20 on dummybots... @@ -8795,11 +8933,19 @@ void Entity::attack(int pose, int charge, Entity* target) if ( (*weaponToBreak)->status != BROKEN ) { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(679)); + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEGRADED, (*weaponToBreak)->type, 1); + } } else { playSoundEntity(this, 76, 64); messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(680)); + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, (*weaponToBreak)->type, 1); + } } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -9169,6 +9315,7 @@ void Entity::attack(int pose, int charge, Entity* target) if ( behavior == &actPlayer ) // redundant; but if this code ever changes... { steamAchievementClient(skill[2], "BARONY_ACH_ONE_PUNCH_MAN"); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_SKILL_LEGENDARY_PROCS, "unarmed skill", 1); } } hit.entity->modHP(-5); // do extra damage. @@ -9298,6 +9445,10 @@ void Entity::attack(int pose, int charge, Entity* target) } } knockbackInflicted = true; + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_SKILL_LEGENDARY_PROCS, "polearm skill", 1); + } } hit.entity->modHP(-capstoneDamage); // do the damage } @@ -9315,6 +9466,10 @@ void Entity::attack(int pose, int charge, Entity* target) paralyzeStatusInflicted = true; playSoundEntity(hit.entity, 172, 64); //TODO: Paralyze spell sound. spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 170); + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_SKILL_LEGENDARY_PROCS, "mace skill", 1); + } } } hit.entity->modHP(-capstoneDamage); // do the damage @@ -9331,6 +9486,10 @@ void Entity::attack(int pose, int charge, Entity* target) slowStatusInflicted = true; playSoundEntity(hit.entity, 396 + local_rng.rand() % 3, 64); spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 171); + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_SKILL_LEGENDARY_PROCS, "axe skill", 1); + } } hit.entity->modHP(-capstoneDamage); // do the damage // don't re-notify if already inflicted slow from Parashu. @@ -9352,6 +9511,10 @@ void Entity::attack(int pose, int charge, Entity* target) serverSpawnGibForClient(gib); } hit.entity->modHP(-capstoneDamage); // do the damage + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_SKILL_LEGENDARY_PROCS, "sword skill", 1); + } } else { @@ -9818,6 +9981,21 @@ void Entity::attack(int pose, int charge, Entity* target) } } awardXP(hit.entity, true, true); + + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_KILLS, "melee", 1); + if ( chargeMult > 1 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRIT_KILLS, "crits", 1); + } + if ( backstab ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_BACKSTAB_KILLS, "backstabs", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_KILLS_RUN, "backstabs", 1); + } + } + if ( player >= 0 && myStats->weapon && this->checkEnemy(hit.entity) ) { if ( myStats->weapon->ownerUid == hit.entity->getUID() ) @@ -9996,6 +10174,16 @@ void Entity::attack(int pose, int charge, Entity* target) } } awardXP(hit.entity, true, true); + + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_KILLS, "melee", 1); + if ( chargeMult > 1 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRIT_KILLS, "crits", 1); + } + } + if ( player >= 0 && myStats->weapon && this->checkEnemy(hit.entity) ) { if ( myStats->weapon->ownerUid == hit.entity->getUID() ) @@ -11059,6 +11247,10 @@ void Entity::attack(int pose, int charge, Entity* target) else { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(705)); + if ( behavior == &actPlayer && myStats->weapon ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEGRADED, myStats->weapon->type, 1); + } } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -12143,6 +12335,13 @@ void Entity::awardXP(Entity* src, bool share, bool root) } } } + + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_MAX_IN_FLOOR, "xp", gain, false, -1, true); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_MAX_INSTANCE, "xp", gain); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_KILLS, "xp", gain); + } destStats->EXP += gain; } @@ -17669,6 +17868,10 @@ void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum) { messagePlayer(playerhit, MESSAGE_EQUIPMENT, Language::get(681), armor.getName()); } + if ( playerhit >= 0 ) + { + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_DEGRADED, armor.type, 1); + } } else { diff --git a/src/game.cpp b/src/game.cpp index 5a736b8f6..df12e4ae1 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1636,6 +1636,10 @@ void gameLogic(void) gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); + if ( ticks % TICKS_PER_SECOND == 25 ) + { + Compendium_t::Events_t::updateEventsInMainLoop(clientnum); + } achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); //if( TICKS_PER_SECOND ) @@ -3037,6 +3041,10 @@ void gameLogic(void) gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); + if ( ticks % TICKS_PER_SECOND == 25 ) + { + Compendium_t::Events_t::updateEventsInMainLoop(clientnum); + } achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); // ask for entity delete update diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index e0cc3d9b2..fd86bafb8 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -6241,6 +6241,7 @@ void GenericGUIMenu::repairItem(Item* item) item->status = static_cast(std::min(item->status + 2 + itemEffectItemBeatitude, static_cast(EXCELLENT))); } } + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_REPAIRS, item->type, 1); messagePlayer(gui_player, MESSAGE_MISC, Language::get(872), item->getName()); } closeGUI(); @@ -10124,6 +10125,8 @@ bool GenericGUIMenu::tinkeringRepairItem(Item* item) if ( tinkeringConsumeMaterialsForRepair(item, false) ) { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_REPAIRS, item->type, 1); + int repairedStatus = std::min(static_cast(item->status + 1), EXCELLENT); item->status = static_cast(repairedStatus); messagePlayer(gui_player, MESSAGE_MISC, Language::get(872), item->getName()); diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 9a89d60a8..486be5524 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -554,6 +554,10 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool if ( player >= 0 ) { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_FAILURES, SPELL_ITEM, 1, false, spell->ID); + if ( !usingSpellbook && !using_magicstaff && !trap ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_FIZZLES_RUN, "memorized", 1); + } } return NULL; } @@ -2462,6 +2466,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool else if ( !usingSpellbook && !using_magicstaff ) { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_CASTS, SPELL_ITEM, 1, false, spell->ID); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_CASTS_RUN, "memorized", 1); } } if ( using_magicstaff ) diff --git a/src/menu.cpp b/src/menu.cpp index 65d1c7368..0988b95b3 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8674,10 +8674,6 @@ void doNewGame(bool makeHighscore) { } Player::Minimap_t::mapDetails.clear(); - for ( int c = 0; c < MAXPLAYERS; ++c ) - { - players[c]->compendiumProgress.itemEvents.clear(); - } // disable cheats noclip = false; @@ -8723,6 +8719,24 @@ void doNewGame(bool makeHighscore) { mimic_generator.init(); } + Compendium_t::Events_t::clientReceiveData.clear(); + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + Compendium_t::Events_t::clientDataStrings[c].clear(); + bool bOldIntro = intro; + intro = false; + if ( !bWasOnMainMenu && !loadingsavegame ) + { + players[c]->compendiumProgress.updateFloorEvents(); + } + intro = bOldIntro; + if ( !loadingsavegame ) + { + players[c]->compendiumProgress.itemEvents.clear(); + players[c]->compendiumProgress.floorEvents.clear(); + } + } + // load dungeon if ( multiplayer != CLIENT ) { @@ -9403,7 +9417,6 @@ void doNewGame(bool makeHighscore) { } } - Compendium_t::Events_t::writeItemsSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif @@ -9435,9 +9448,20 @@ void doNewGame(bool makeHighscore) { if ( currentlevel == 0 && !secretlevel ) { Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER, "minehead", 1); + Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_CLASS_GAMES_STARTED, "class", 1); + Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_RACE_GAMES_STARTED, "races", 1); + if ( multiplayer == SERVER || multiplayer == CLIENT || (multiplayer == SINGLE && splitscreen) ) + { + Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_CLASS_GAMES_MULTI, "class", 1); + } + else + { + Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_CLASS_GAMES_SOLO, "class", 1); + } } } } + Compendium_t::Events_t::writeItemsSaveData(); } void doCredits() { @@ -9519,7 +9543,7 @@ void doEndgame(bool saveHighscore) { } } - Compendium_t::Events_t::onVictoryEvent(clientnum, endTutorial); + Compendium_t::Events_t::onEndgameEvent(clientnum, endTutorial, saveHighscore); // figure out the victory crawl texts... int movieCrawlType = -1; @@ -9856,6 +9880,8 @@ void doEndgame(bool saveHighscore) { deleteSaveGame(multiplayer); } + Compendium_t::Events_t::writeItemsSaveData(); + gameModeManager.currentSession.seededRun.reset(); gameModeManager.currentSession.challengeRun.reset(); @@ -10048,6 +10074,13 @@ void doEndgame(bool saveHighscore) { } #endif + Compendium_t::Events_t::clientReceiveData.clear(); + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + Compendium_t::Events_t::clientDataStrings[c].clear(); + players[c]->compendiumProgress.itemEvents.clear(); + players[c]->compendiumProgress.floorEvents.clear(); + } #ifdef LOCAL_ACHIEVEMENTS LocalAchievements.writeToFile(); #endif diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 2fa01b463..9213e5203 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10751,11 +10751,25 @@ void Compendium_t::readItemsFromFile() } if ( w.HasMember("events") ) { - std::vector alwaysTrackedEvents = { + std::set alwaysTrackedEvents = { "APPRAISED", "RUNS_COLLECTED" }; + for ( auto& item : obj.items_in_category ) + { + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + if ( ::items[itemType].item_slot != NO_EQUIP ) + { + alwaysTrackedEvents.insert("BROKEN"); + alwaysTrackedEvents.insert("DEGRADED"); + alwaysTrackedEvents.insert("REPAIRS"); + } + } + } + for ( auto& s : alwaysTrackedEvents ) { auto find = Compendium_t::Events_t::eventIdLookup.find(s); @@ -11020,11 +11034,25 @@ void Compendium_t::readMagicFromFile() if ( w.HasMember("events") ) { - std::vector alwaysTrackedEvents = { + std::set alwaysTrackedEvents = { "APPRAISED", "RUNS_COLLECTED" }; + for ( auto& item : obj.items_in_category ) + { + const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + if ( itemType != SPELL_ITEM && ::items[itemType].item_slot != NO_EQUIP ) + { + alwaysTrackedEvents.insert("BROKEN"); + alwaysTrackedEvents.insert("DEGRADED"); + alwaysTrackedEvents.insert("REPAIRS"); + } + } + } + for ( auto& s : alwaysTrackedEvents ) { auto find = Compendium_t::Events_t::eventIdLookup.find(s); @@ -11154,6 +11182,8 @@ void Compendium_t::readCodexFromFile() codex.clear(); CompendiumCodex_t::contents.clear(); CompendiumCodex_t::contentsMap.clear(); + Compendium_t::Events_t::eventCodexIDLookup.clear(); + Compendium_t::Events_t::eventCodexLookup.clear(); for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -11170,6 +11200,7 @@ void Compendium_t::readCodexFromFile() auto& w = itr->value; auto& obj = codex[name]; + obj.id = w["event_lookup"].GetInt(); jsonVecToVec(w["blurb"], obj.blurb); jsonVecToVec(w["details"], obj.details); obj.imagePath = w["img"].GetString(); @@ -11181,6 +11212,45 @@ void Compendium_t::readCodexFromFile() { jsonVecToVec(w["models"], obj.models); } + + Compendium_t::Events_t::eventCodexIDLookup[name] = obj.id; + if ( w.HasMember("events") ) + { + for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + Compendium_t::Events_t::eventCodexLookup[(Compendium_t::EventTags)find2->second.id].insert(name); + } + } + } + } + if ( w.HasMember("events_display") ) + { + for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) + { + std::string eventName = itr->GetString(); + auto find = Compendium_t::Events_t::eventIdLookup.find(eventName); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + { + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) + { + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventCodexOffset + obj.id]; + if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) + == vec.end() ) + { + vec.push_back((Compendium_t::EventTags)find2->second.id); + } + } + } + } + } } } @@ -11437,7 +11507,10 @@ std::map Compendium_t::Events_t::eventIdLo std::map> Compendium_t::Events_t::itemEventLookup; std::map> Compendium_t::Events_t::eventItemLookup; std::map> Compendium_t::Events_t::eventWorldLookup; +std::map> Compendium_t::Events_t::eventCodexLookup; std::map Compendium_t::Events_t::eventWorldIDLookup; +std::map Compendium_t::Events_t::eventCodexIDLookup; +std::map> Compendium_t::Events_t::eventClassIds; std::map> Compendium_t::Events_t::itemDisplayedEventsList; std::map> Compendium_t::Events_t::playerEvents; std::map> Compendium_t::Events_t::serverPlayerEvents[MAXPLAYERS]; @@ -11532,6 +11605,7 @@ void Compendium_t::Events_t::readEventsFromFile() events.clear(); eventIdLookup.clear(); + eventClassIds.clear(); int index = -1; for ( auto itr = d["tags"].Begin(); itr != d["tags"].End(); ++itr ) { @@ -11575,7 +11649,40 @@ void Compendium_t::Events_t::readEventsFromFile() } if ( itr2->value.HasMember("once_per_run") ) { - entry.oncePerRun = itr2->value["once_per_run"].GetBool(); + entry.eventTrackingType = itr2->value["once_per_run"].GetBool() ? EventTrackingType::ONCE_PER_RUN : EventTrackingType::ALWAYS_UPDATE; + } + if ( itr2->value.HasMember("unique_per_run") ) + { + entry.eventTrackingType = itr2->value["unique_per_run"].GetBool() ? EventTrackingType::UNIQUE_PER_RUN : EventTrackingType::ALWAYS_UPDATE; + } + if ( itr2->value.HasMember("unique_per_floor") ) + { + entry.eventTrackingType = itr2->value["unique_per_floor"].GetBool() ? EventTrackingType::UNIQUE_PER_FLOOR : EventTrackingType::ALWAYS_UPDATE; + } + if ( itr2->value.HasMember("attributes") ) + { + if ( itr2->value["attributes"].IsArray() ) + { + for ( auto arr_itr = itr2->value["attributes"].Begin(); arr_itr != itr2->value["attributes"].End(); ++arr_itr ) + { + if ( arr_itr->IsString() ) + { + entry.attributes.insert(arr_itr->GetString()); + } + } + } + else if ( itr2->value["attributes"].IsString() ) + { + entry.attributes.insert(itr2->value["attributes"].GetString()); + } + } + if ( entry.attributes.find("class") != entry.attributes.end() || entry.attributes.find("race") != entry.attributes.end() ) + { + size_t idStart = kEventCodexClassOffset + eventClassIds.size() * kEventClassesMax; + for ( int i = 0; i <= CLASS_HUNTER; ++i ) + { + eventClassIds[id][i] = (idStart + i); + } } } } @@ -11639,6 +11746,11 @@ void Compendium_t::Events_t::loadItemsSaveData() eventUpdateWorld(0, id, nullptr, value, true, itemType - kEventWorldOffset); continue; } + if ( itemType >= kEventCodexOffset && itemType <= kEventCodexOffsetMax ) + { + eventUpdateCodex(0, id, nullptr, value, true, itemType - kEventCodexOffset); + continue; + } if ( itemType < 0 || (itemType >= NUMITEMS && itemType < kEventSpellOffset) ) { continue; @@ -11753,9 +11865,12 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) if ( type == SUM ) { value += val; - if ( (Uint32)value >= 0x7FFFFFFF ) + if ( id != CPDM_SINKS_HEALTH_RESTORED ) { - value = 0x7FFFFFFF; + if ( (Uint32)value >= 0x7FFFFFFF ) + { + value = 0x7FFFFFFF; + } } return true; } @@ -11803,7 +11918,45 @@ void onCompendiumLevelExit(const int playernum, const char* level, const bool en } } -void Compendium_t::Events_t::onVictoryEvent(const int playernum, const bool tutorialend) +void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) +{ + if ( !(players[playernum] && players[playernum]->entity && stats[playernum]) ) + { + return; + } + + auto entity = players[playernum]->entity; + auto myStats = stats[playernum]; + { + real_t resistance = 100.0 * Entity::getDamageTableMultiplier(entity, *myStats, DAMAGE_TABLE_MAGIC); + resistance /= (Entity::getMagicResistance(myStats) + 1); + resistance = -(resistance - 100.0); + eventUpdateCodex(playernum, CPDM_RES_MAX, "res", (int)resistance); + eventUpdateCodex(playernum, CPDM_CLASS_RES_MAX, "res", (int)resistance); + } + + { + Sint32 ac = AC(myStats); + eventUpdateCodex(playernum, CPDM_AC_MAX, "ac", ac); + eventUpdateCodex(playernum, CPDM_CLASS_AC_MAX, "ac", ac); + + Sint32 con = myStats->CON; + myStats->CON = 0; + ac = AC(myStats); + eventUpdateCodex(playernum, CPDM_AC_MAX_FROM_BLESS, "ac", ac); + myStats->CON = con; + } + + { + eventUpdateCodex(playernum, CPDM_HP_MAX, "hp", myStats->MAXHP); + eventUpdateCodex(playernum, CPDM_CLASS_HP_MAX, "hp", myStats->MAXHP); + + eventUpdateCodex(playernum, CPDM_MP_MAX, "mp", myStats->MAXMP); + eventUpdateCodex(playernum, CPDM_CLASS_MP_MAX, "mp", myStats->MAXMP); + } +} + +void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore) { if ( players[playernum]->isLocalPlayer() ) { @@ -11811,29 +11964,68 @@ void Compendium_t::Events_t::onVictoryEvent(const int playernum, const bool tuto { if ( stats[playernum]->HP <= 0 ) { - Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); } } else { + players[playernum]->compendiumProgress.updateFloorEvents(); if ( victory ) { if ( currentlevel == 35 ) { onCompendiumLevelExit(playernum, "citadel sanctum", false); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON, "class", 1); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_MAX, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_MIN, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_RACE_GAMES_WON, "races", 1); } else if ( currentlevel == 24 ) { onCompendiumLevelExit(playernum, "hell", false); eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "hell", 1); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON_HELL, "class", 1); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MAX, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MIN, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_RACE_GAMES_WON_HELL, "races", 1); } else if ( currentlevel == 20 ) { onCompendiumLevelExit(playernum, "herx lair", false); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON_CLASSIC, "class", 1); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_CLASSIC_MAX, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_CLASSIC_MIN, "leveling up", stats[playernum]->LVL); + eventUpdateCodex(playernum, Compendium_t::CPDM_RACE_GAMES_WON_CLASSIC, "races", 1); + } + } + } + } +} + +void Player::CompendiumProgress_t::updateFloorEvents() +{ + for ( auto& p1 : floorEvents ) + { + if ( p1.first >= 0 && p1.first < Compendium_t::EventTags::CPDM_EVENT_TAGS_MAX ) + { + Compendium_t::EventTags tag = (Compendium_t::EventTags)p1.first; + for ( auto& p2 : p1.second ) + { + const char* category = p2.first.c_str(); + for ( auto& p3 : p2.second ) + { + int eventID = p3.first; + Sint32 value = p3.second; + if ( eventID >= Compendium_t::Events_t::kEventCodexOffset && eventID <= Compendium_t::Events_t::kEventCodexOffsetMax ) + { + Compendium_t::Events_t::eventUpdateCodex(player.playernum, tag, category, value, false); + } } } } } + + floorEvents.clear(); } void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname) @@ -11846,6 +12038,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p if ( players[playernum]->isLocalPlayer() ) { + players[playernum]->compendiumProgress.updateFloorEvents(); if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL || gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL_INIT ) { @@ -12135,7 +12328,7 @@ bool allowedCompendiumProgress() static ConsoleVariable cvar_compendiumDebugSave("/compendium_debug_save", false); void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, const ItemType type, - const Sint32 value, const bool loadingValue, const int spellID) + Sint32 value, const bool loadingValue, const int spellID) { if ( !allowedCompendiumProgress() ) { return; } if ( intro && !loadingValue ) { return; } @@ -12150,6 +12343,8 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } auto& def = find->second; + bool clientReceiveUpdateFromServer = false; + if ( !loadingValue ) { if ( def.clienttype == CLIENT_ONLY ) @@ -12169,6 +12364,7 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con if ( playernum == 0 ) { playernum = clientnum; // when a client receives an update from the server + clientReceiveUpdateFromServer = true; } else { @@ -12206,7 +12402,7 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con e[itemType] = EventVal_t(tag); } - if ( def.oncePerRun && !loadingValue ) + if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) { auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(itemType); if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) @@ -12224,6 +12420,20 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } else { + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) + { + if ( clientReceiveUpdateFromServer ) + { + // server is tracking the total for us, so don't add + players[playernum]->compendiumProgress.itemEvents[def.name][itemType] = value; + } + else + { + players[playernum]->compendiumProgress.itemEvents[def.name][itemType] += value; + } + value = players[playernum]->compendiumProgress.itemEvents[def.name][itemType]; + } + if ( val.applyValue(value) ) { if ( *cvar_compendiumDebugSave ) @@ -12238,7 +12448,7 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, - const Sint32 value, const bool loadingValue, const int entryID) + Sint32 value, const bool loadingValue, const int entryID) { if ( !allowedCompendiumProgress() ) { return; } if ( intro && !loadingValue ) { return; } @@ -12253,6 +12463,8 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t } auto& def = find->second; + bool clientReceiveUpdateFromServer = false; + if ( !loadingValue ) { if ( def.clienttype == CLIENT_ONLY ) @@ -12272,6 +12484,7 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t if ( playernum == 0 ) { playernum = clientnum; // when a client receives an update from the server + clientReceiveUpdateFromServer = true; } else { @@ -12340,7 +12553,7 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t e[monsterType] = EventVal_t(tag); } - if ( def.oncePerRun && !loadingValue ) + if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) { auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(monsterType); if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) @@ -12358,6 +12571,20 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t } else { + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) + { + if ( clientReceiveUpdateFromServer ) + { + // server is tracking the total for us, so don't add + players[playernum]->compendiumProgress.itemEvents[def.name][monsterType] = value; + } + else + { + players[playernum]->compendiumProgress.itemEvents[def.name][monsterType] += value; + } + value = players[playernum]->compendiumProgress.itemEvents[def.name][monsterType]; + } + if ( val.applyValue(value) ) { if ( *cvar_compendiumDebugSave ) @@ -12371,7 +12598,7 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t } } -void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag, const char* category, const Sint32 value, +void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue, const int entryID) { if ( !allowedCompendiumProgress() ) { return; } @@ -12391,6 +12618,8 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag } auto& def = find->second; + bool clientReceiveUpdateFromServer = false; + if ( !loadingValue ) { if ( def.clienttype == CLIENT_ONLY ) @@ -12410,6 +12639,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag if ( playernum == 0 ) { playernum = clientnum; // when a client receives an update from the server + clientReceiveUpdateFromServer = true; } else { @@ -12424,7 +12654,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag { worldID = entryID; bool foundCategory = false; - for ( auto cat : eventWorldLookup[tag] ) + for ( auto& cat : eventWorldLookup[tag] ) { auto find = eventWorldIDLookup.find(cat); if ( find != eventWorldIDLookup.end() ) @@ -12472,7 +12702,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag e[worldID] = EventVal_t(tag); } - if ( def.oncePerRun && !loadingValue ) + if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) { auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(worldID); if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) @@ -12490,6 +12720,231 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag } else { + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) + { + if ( clientReceiveUpdateFromServer ) + { + // server is tracking the total for us, so don't add + players[playernum]->compendiumProgress.itemEvents[def.name][worldID] = value; + } + else + { + players[playernum]->compendiumProgress.itemEvents[def.name][worldID] += value; + } + value = players[playernum]->compendiumProgress.itemEvents[def.name][worldID]; + } + + if ( val.applyValue(value) ) + { + if ( *cvar_compendiumDebugSave ) + { + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } + } + } + } +} + +void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag, const char* category, + Sint32 value, const bool loadingValue, const int entryID, const bool floorEvent) +{ + if ( !allowedCompendiumProgress() ) { return; } + if ( intro && !loadingValue ) { return; } + if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } + + if ( multiplayer == SINGLE && playernum != 0 ) { return; } + if ( multiplayer == SERVER && client_disconnected[playernum] ) + { + return; + } + + auto find = events.find(tag); + if ( find == events.end() ) + { + return; + } + auto& def = find->second; + + bool clientReceiveUpdateFromServer = false; + + if ( !loadingValue ) + { + if ( def.clienttype == CLIENT_ONLY ) + { + if ( multiplayer != SINGLE ) + { + if ( playernum != clientnum ) + { + return; + } + } + } + else if ( def.clienttype == SERVER_ONLY ) + { + if ( multiplayer == CLIENT ) + { + if ( playernum == 0 ) + { + playernum = clientnum; // when a client receives an update from the server + clientReceiveUpdateFromServer = true; + } + else + { + return; + } + } + } + } + + int codexID = -1; + if ( entryID >= 0 ) + { + codexID = entryID; + bool foundCategory = false; + if ( def.attributes.find("class") != def.attributes.end() + || def.attributes.find("race") != def.attributes.end() ) + { + auto findClassTag = eventClassIds.find(tag); + if ( findClassTag != eventClassIds.end() ) + { + for ( auto& pair : findClassTag->second ) + { + if ( pair.second == (codexID < kEventCodexOffset) ? (codexID + kEventCodexOffset) : codexID ) + { + foundCategory = true; + break; + } + } + } + } + else + { + for ( auto& cat : eventCodexLookup[tag] ) + { + auto find = eventCodexIDLookup.find(cat); + if ( find != eventCodexIDLookup.end() ) + { + if ( eventCodexIDLookup[cat] == codexID ) + { + foundCategory = true; + break; + } + } + } + } + if ( !foundCategory ) + { + return; + } + } + else + { + auto find2 = eventCodexLookup[tag].find(category); + if ( find2 == eventCodexLookup[tag].end() ) + { + return; + } + auto find = eventCodexIDLookup.find(category); + if ( find != eventCodexIDLookup.end() ) + { + codexID = find->second; + + if ( def.attributes.find("class") != def.attributes.end() ) + { + auto findClassTag = eventClassIds.find(tag); + if ( findClassTag != eventClassIds.end() ) + { + auto findClassId = findClassTag->second.find(client_classes[playernum]); + if ( findClassId != findClassTag->second.end() ) + { + codexID = findClassId->second; + } + else + { + codexID = -1; + } + } + } + else if ( def.attributes.find("race") != def.attributes.end() ) + { + auto findRaceTag = eventClassIds.find(tag); + if ( findRaceTag != eventClassIds.end() ) + { + int race = RACE_HUMAN; + if ( stats[playernum]->playerRace > 0 && stats[playernum]->appearance == 0 ) + { + race = stats[playernum]->playerRace; + } + auto findRaceId = findRaceTag->second.find(race); + if ( findRaceId != findRaceTag->second.end() ) + { + codexID = findRaceId->second; + } + else + { + codexID = -1; + } + } + } + } + } + + if ( codexID == -1 ) + { + return; + } + + if ( codexID < kEventCodexOffset ) + { + codexID += kEventCodexOffset; // convert to offset + } + + + auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; + if ( e.find(codexID) == e.end() ) + { + e[codexID] = EventVal_t(tag); + } + + if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) + { + auto find = players[playernum]->compendiumProgress.itemEvents[def.name].find(codexID); + if ( find != players[playernum]->compendiumProgress.itemEvents[def.name].end() ) + { + // already present, skip adding + return; + } + players[playernum]->compendiumProgress.itemEvents[def.name][codexID] += value; + } + + auto& val = e[codexID]; + if ( loadingValue ) + { + val.value = value; // reading from savefile + } + else + { + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) + { + if ( clientReceiveUpdateFromServer ) + { + // server is tracking the total for us, so don't add + players[playernum]->compendiumProgress.itemEvents[def.name][codexID] = value; + } + else + { + players[playernum]->compendiumProgress.itemEvents[def.name][codexID] += value; + } + value = players[playernum]->compendiumProgress.itemEvents[def.name][codexID]; + } + else if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_FLOOR && floorEvent ) + { + players[playernum]->compendiumProgress.floorEvents[tag][category][codexID] = value; + return; + } + if ( val.applyValue(value) ) { if ( *cvar_compendiumDebugSave ) diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 36ef014c2..e0d404858 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3565,6 +3565,7 @@ struct Compendium_t std::vector blurb; std::vector details; std::vector models; + int id = -1; CompendiumView_t view; }; static std::vector> contents; @@ -3735,9 +3736,139 @@ struct Compendium_t CPDM_PITS_ITEMS_VALUE_LOST, CPDM_KILLED_MULTIPLAYER, CPDM_RECRUITED, + CPDM_RACE_GAMES_STARTED, + CPDM_RACE_RECRUITS, + CPDM_RACE_GAMES_WON, + CPDM_DISTANCE_TRAVELLED, + CPDM_DISTANCE_MAX_RUN, + CPDM_XP_KILLS, + CPDM_XP_SKILLS, + CPDM_XP_MAX_IN_FLOOR, + CPDM_XP_MAX_INSTANCE, + CPDM_CLASS_LVL_MAX, + CPDM_CLASS_LVL_GAINED, + CPDM_CLASS_GAMES_STARTED, + CPDM_CLASS_GAMES_WON, + CPDM_CLASS_GAMES_SOLO, + CPDM_CLASS_GAMES_MULTI, + CPDM_STAT_MAX, + CPDM_CLASS_STAT_STR_MAX, + CPDM_STAT_INCREASES, + CPDM_STAT_DOUBLED, + CPDM_HP_MAX, + CPDM_CLASS_HP_MAX, + CPDM_MP_MAX, + CPDM_CLASS_MP_MAX, + CPDM_CLASS_SKILL_UPS, + CPDM_CLASS_SKILL_MAX, + CPDM_CLASS_SKILL_UPS_RUN_MAX, + CPDM_CLASS_STAT_DEX_MAX, + CPDM_CLASS_STAT_CON_MAX, + CPDM_CLASS_STAT_INT_MAX, + CPDM_CLASS_STAT_PER_MAX, + CPDM_CLASS_STAT_CHR_MAX, + CPDM_RES_MAX, + CPDM_CLASS_RES_MAX, + CPDM_RES_DMG_RESISTED, + CPDM_RES_DMG_RESISTED_RUN, + CPDM_AC_MAX, + CPDM_CLASS_AC_MAX, + CPDM_AC_MAX_FROM_BLESS, + CPDM_HP_LOST_RUN, + CPDM_MP_SPENT_RUN, + CPDM_MP_SPENT_TOTAL, + CPDM_CLASS_LVL_WON_MAX, + CPDM_CLASS_LVL_WON_MIN, + CPDM_CLASS_GAMES_WON_CLASSIC, + CPDM_CLASS_GAMES_WON_HELL, + CPDM_CLASS_LVL_WON_CLASSIC_MAX, + CPDM_CLASS_LVL_WON_HELL_MIN, + CPDM_RACE_GAMES_WON_CLASSIC, + CPDM_RACE_GAMES_WON_HELL, + CPDM_CLASS_LVL_WON_CLASSIC_MIN, + CPDM_CLASS_LVL_WON_HELL_MAX, + CPDM_DISTANCE_MAX_FLOOR, + CPDM_PWR_MAX, + CPDM_CLASS_PWR_MAX, + CPDM_RGN_HP_SUM, + CPDM_RGN_HP_RUN, + CPDM_RGN_MP_SUM, + CPDM_RGN_MP_RUN, + CPDM_RGN_HP_RATE_MAX, + CPDM_RGN_MP_RATE_MAX, + CPDM_CLASS_WGT_MAX, + CPDM_CLASS_WGT_MAX_MOVE_100, + CPDM_MELEE_HITS, + CPDM_MELEE_DMG_TOTAL, + CPDM_CLASS_MELEE_HITS_RUN, + CPDM_MELEE_KILLS, + CPDM_CRIT_HITS, + CPDM_CRITS_DMG_TOTAL, + CPDM_CLASS_CRITS_HITS_RUN, + CPDM_CRIT_KILLS, + CPDM_CLASS_WGT_SLOWEST, + CPDM_CLASS_SKILL_LEGENDS, + CPDM_SKILL_LEGENDARY_PROCS, + CPDM_DEGRADED, + CPDM_REPAIRS, + CPDM_RANGED_HITS, + CPDM_RANGED_DMG_TOTAL, + CPDM_CLASS_RANGED_HITS_RUN, + CPDM_RANGED_KILLS, + CPDM_THROWN_TOTAL_HITS, + CPDM_THROWN_DMG_TOTAL, + CPDM_CLASS_THROWN_HITS_RUN, + CPDM_THROWN_KILLS, + CPDM_FLANK_HITS, + CPDM_FLANK_DMG, + CPDM_CLASS_FLANK_HITS_RUN, + CPDM_CLASS_FLANK_DMG_RUN, + CPDM_BACKSTAB_HITS, + CPDM_CLASS_BACKSTAB_KILLS_RUN, + CPDM_CLASS_BACKSTAB_HITS_RUN, + CPDM_BACKSTAB_KILLS, + CPDM_CLASS_BACKSTAB_DMG_RUN, + CPDM_CLASS_BLOCK_DEFENDED, + CPDM_CLASS_BLOCK_UNDEFENDED, + CPDM_CLASS_BLOCK_DEFENDED_RUN, + CPDM_CLASS_BLOCK_UNDEFENDED_RUN, + CPDM_CLASS_SPELL_CASTS_RUN, + CPDM_CLASS_SPELL_FIZZLES_RUN, + CPDM_CLASS_SNEAK_TIME, + CPDM_CLASS_SNEAK_SKILLUP_FLOOR, + CPDM_WANTED_RUNS, + CPDM_WANTED_TIMES_RUN, + CPDM_WANTED_INFLUENCE, + CPDM_WANTED_CRIMES_RUN, CPDM_EVENT_TAGS_MAX }; + static const char* Compendium_t::getSkillStringForCompendium(const int skill) + { + switch ( skill ) + { + case PRO_LOCKPICKING: return "tinkering skill"; + case PRO_STEALTH: return "stealth skill"; + case PRO_TRADING: return "trading skill"; + case PRO_APPRAISAL: return "appraisal skill"; + case PRO_SWIMMING: return "swimming skill"; + case PRO_LEADERSHIP: return "leadership skill"; + case PRO_SPELLCASTING: return "casting skill"; + case PRO_MAGIC: return "magic skill"; + case PRO_RANGED: return "ranged skill"; + case PRO_SWORD: return "sword skill"; + case PRO_MACE: return "mace skill"; + case PRO_AXE: return "axe skill"; + case PRO_POLEARM: return "polearm skill"; + case PRO_SHIELD: return "blocking skill"; + case PRO_UNARMED: return "unarmed skill"; + case PRO_ALCHEMY: return "alchemy skill"; + default: + break; + } + return ""; + } + struct Events_t { enum Type @@ -3755,13 +3886,21 @@ struct Compendium_t CLIENT_AND_SERVER, CLIENT_UPDATETYPE_MAX }; + enum EventTrackingType + { + ALWAYS_UPDATE, + ONCE_PER_RUN, + UNIQUE_PER_RUN, + UNIQUE_PER_FLOOR + }; struct Event_t { Type type = SUM; - bool oncePerRun = false; + EventTrackingType eventTrackingType = ALWAYS_UPDATE; ClientUpdateType clienttype = CLIENT_ONLY; std::string name = ""; int id = CPDM_EVENT_TAGS_MAX; + std::set attributes; }; struct EventVal_t { @@ -3785,29 +3924,37 @@ struct Compendium_t static std::map> itemDisplayedEventsList; static std::map> eventItemLookup; static std::map> eventMonsterLookup; - static std::map> eventClassLookup; static std::map> eventWorldLookup; + static std::map> eventCodexLookup; static std::map eventWorldIDLookup; + static std::map eventCodexIDLookup; + static std::map> eventClassIds; + static const int kEventClassesMax = 40; static std::map> eventLangEntries; static void readEventsFromFile(); static void writeItemsSaveData(); static void loadItemsSaveData(); static void readEventsTranslations(); static void createDummyClientData(const int playernum); - static void eventUpdate(int playernum, const EventTags tag, const ItemType type, const Sint32 value, const bool loadingValue = false, const int spellID = -1); - static void eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, const Sint32 value, const bool loadingValue = false, const int entryID = -1); - static void eventUpdateWorld(int playernum, const EventTags tag, const char* category, const Sint32 value, const bool loadingValue = false, const int entryID = -1); + static void eventUpdate(int playernum, const EventTags tag, const ItemType type, Sint32 value, const bool loadingValue = false, const int spellID = -1); + static void eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, Sint32 value, const bool loadingValue = false, const int entryID = -1); + static void eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1); + static void eventUpdateCodex(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1, const bool floorEvent = false); static std::map> playerEvents; static std::map> serverPlayerEvents[MAXPLAYERS]; static void onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname); - static void onVictoryEvent(const int playernum, const bool tutorialend); + static void onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore); static void sendClientDataOverNet(const int playernum); + static void updateEventsInMainLoop(const int playernum); static std::map clientDataStrings[MAXPLAYERS]; - static std::map> clientReceiveData; // todo clean up on end + static std::map> clientReceiveData; static Uint8 clientSequence; static const int kEventSpellOffset = 10000; static const int kEventMonsterOffset = 1000; static const int kEventWorldOffset = 2000; + static const int kEventCodexOffset = 3000; + static const int kEventCodexClassOffset = 3500; + static const int kEventCodexOffsetMax = 9999; }; }; diff --git a/src/net.cpp b/src/net.cpp index 0ce3c47d2..984cc9dcb 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -5116,6 +5116,12 @@ static std::unordered_map clientPacketHandlers = { itemType - Compendium_t::Events_t::kEventWorldOffset); continue; } + if ( itemType >= Compendium_t::Events_t::kEventCodexOffset && itemType <= Compendium_t::Events_t::kEventCodexOffsetMax ) + { + Compendium_t::Events_t::eventUpdateCodex(0, (Compendium_t::EventTags)id, nullptr, value, false, + itemType - Compendium_t::Events_t::kEventCodexOffset); + continue; + } if ( itemType < 0 || (itemType >= NUMITEMS && itemType < Compendium_t::Events_t::kEventSpellOffset) ) { continue; diff --git a/src/player.hpp b/src/player.hpp index 48aacc4a4..a6c612fdf 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2269,9 +2269,13 @@ class Player Player& player; public: std::map> itemEvents; + std::map>> floorEvents; + real_t playerDistAccum = 0.0; + Uint32 playerSneakTime = 0; CompendiumProgress_t(Player& p) : player(p) {}; ~CompendiumProgress_t() {}; + void updateFloorEvents(); } compendiumProgress; static void soundMovement(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index b62dbf90d..79005d9fe 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -24664,6 +24664,9 @@ namespace MainMenu { if (client_disconnected[c]) { continue; } + + Compendium_t::Events_t::sendClientDataOverNet(c); + memcpy((char*)net_packet->data, "RSTR", 4); SDLNet_Write32(svFlags, &net_packet->data[4]); SDLNet_Write32(uniqueGameKey, &net_packet->data[8]); @@ -26587,6 +26590,9 @@ namespace MainMenu { if ( client_disconnected[c] ) { continue; } + + Compendium_t::Events_t::sendClientDataOverNet(c); + memcpy((char*)net_packet->data, "RSTR", 4); SDLNet_Write32(svFlags, &net_packet->data[4]); SDLNet_Write32(uniqueGameKey, &net_packet->data[8]); @@ -32912,6 +32918,15 @@ namespace MainMenu { itemname = entryName; } } + else if ( entryType >= Compendium_t::Events_t::kEventCodexOffset + && entryType <= Compendium_t::Events_t::kEventCodexOffsetMax ) + { + if ( Compendium_t::Events_t::eventLangEntries[tag].find(entryName) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = entryName; + } + } else if ( entryType >= Compendium_t::Events_t::kEventSpellOffset && entryType < Compendium_t::Events_t::kEventSpellOffset + 1000 ) { @@ -32930,29 +32945,99 @@ namespace MainMenu { if ( val ) { val->setDisabled(false); - auto find = Compendium_t::Events_t::playerEvents[tag].find(entryType); - if ( find != Compendium_t::Events_t::playerEvents[tag].end() ) + int value = 0; + if ( entryType >= Compendium_t::Events_t::kEventCodexOffset + && entryType <= Compendium_t::Events_t::kEventCodexOffsetMax + && Compendium_t::Events_t::eventClassIds.find(tag) != Compendium_t::Events_t::eventClassIds.end() ) { - if ( find->second.type == Compendium_t::Events_t::BITFIELD ) + val->setText("-"); + auto findEvent = Compendium_t::Events_t::events.find(tag); + if ( findEvent != Compendium_t::Events_t::events.end() ) { - int total = 0; - for ( int i = 0; i < 32; ++i ) + auto findTag = Compendium_t::Events_t::playerEvents.find(tag); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) { - if ( find->second.value & (1 << i) ) + bool firstValue = true; + int entryNum = -1; + for ( auto& pair : Compendium_t::Events_t::eventClassIds[tag] ) { - ++total; + auto find = findTag->second.find(pair.second); + if ( find != findTag->second.end() ) + { + if ( find->second.type == Compendium_t::Events_t::MAX + || find->second.type == Compendium_t::Events_t::SUM ) + { + if ( find->second.value > value ) + { + entryNum = pair.first; + value = find->second.value; + } + } + else if ( find->second.type == Compendium_t::Events_t::MIN ) + { + if ( firstValue || find->second.value < value ) + { + entryNum = pair.first; + value = find->second.value; + } + } + firstValue = false; + } + } + if ( !firstValue ) + { + std::string output = std::to_string(value); + if ( findEvent->second.attributes.find("class") != findEvent->second.attributes.end() ) + { + if ( entryNum >= 0 && entryNum < NUMCLASSES ) + { + output += ' '; + output += playerClassLangEntry(entryNum, 0); + val->setText(output.c_str()); + } + } + else if ( findEvent->second.attributes.find("race") != findEvent->second.attributes.end() ) + { + int type = getMonsterFromPlayerRace(entryNum); + output += ' '; + output += getMonsterLocalizedName((Monster)type); + val->setText(output.c_str()); + } } } - val->setText(std::to_string(total).c_str()); - } - else - { - val->setText(std::to_string(find->second.value).c_str()); } } else { - val->setText("-"); + auto findTag = Compendium_t::Events_t::playerEvents.find(tag); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + auto find = findTag->second.find(entryType); + if ( find != findTag->second.end() ) + { + if ( find->second.type == Compendium_t::Events_t::BITFIELD ) + { + int total = 0; + for ( int i = 0; i < 32; ++i ) + { + if ( find->second.value & (1 << i) ) + { + ++total; + } + } + value = total; + } + else + { + value = find->second.value; + } + } + val->setText(std::to_string(value).c_str()); + } + else + { + val->setText("-"); + } } } } @@ -33330,6 +33415,9 @@ namespace MainMenu { if ( Frame* page_right = parent->findFrame("page_right") ) { + // find records for this item + populateRecordsSectionItems(page_right, entry.id + Compendium_t::Events_t::kEventCodexOffset, name.c_str()); + if ( page_right = page_right->findFrame("page_right_inner") ) { if ( auto details = page_right->findField("details") ) @@ -33982,7 +34070,7 @@ namespace MainMenu { int statx = padx + 4; int staty = pady + 18 + 9; - auto statsTxt = page_right_inner->addField("details", 64); + auto statsTxt = page_right_inner->addField("details", 1024); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); statsTxt->setHJustify(Field::justify_t::LEFT); From c0661f9435cd6f026025ed21c0f03d74e2805507 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 10 Jul 2024 00:32:35 +1000 Subject: [PATCH 028/244] * add some braces for preventSleepRoll sleep spell --- src/magic/actmagic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 852fe4e00..7ba8e39f7 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -2419,7 +2419,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( hitstats && hitstats->EFFECTS[EFF_ASLEEP] ) { // check to see if we're reapplying the sleep effect. - int preventSleepRoll = local_rng.rand() % 4 - resistance; + int preventSleepRoll = (local_rng.rand() % 4) - resistance; if ( hit.entity->behavior == &actPlayer || (preventSleepRoll <= 0) ) { magicTrapReapplySleep = false; From 32d50abd069c87020fb8f4931fe6b61040fb588c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 19 Jul 2024 18:50:38 +1000 Subject: [PATCH 029/244] * Bonus INT for spells now in function * lots of custom event work for compendium, finish codex almost --- src/actarrow.cpp | 7 +- src/actplayer.cpp | 16 +- src/actthrown.cpp | 7 +- src/entity.cpp | 111 +++-- src/entity.hpp | 2 +- src/game.cpp | 10 +- src/interface/playerinventory.cpp | 66 +-- src/magic/actmagic.cpp | 51 ++- src/magic/castSpell.cpp | 69 ++- src/magic/magic.cpp | 22 +- src/magic/magic.hpp | 2 + src/magic/spell.cpp | 29 +- src/mod_tools.hpp | 38 ++ src/player.hpp | 3 + src/ui/GameUI.cpp | 58 ++- src/ui/MainMenu.cpp | 730 +++++++++++++++++++++++++++--- 16 files changed, 1047 insertions(+), 174 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index e18d9b47d..c70a1b953 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -520,7 +520,8 @@ void actArrow(Entity* my) // normal damage. } - real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, parent, parent ? parent->getStats() : nullptr); + int numBlessings = 0; + real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, parent, parent ? parent->getStats() : nullptr, numBlessings); int attackAfterReductions = static_cast(std::max(0.0, ((my->arrowPower * targetACEffectiveness - enemyAC))) + (1.0 - targetACEffectiveness) * my->arrowPower); int damage = attackAfterReductions; @@ -683,6 +684,10 @@ void actArrow(Entity* my) parent->killedByMonsterObituary(hit.entity); } + if ( hit.entity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(hit.entity->skill[2], Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", oldHP - hitstats->HP); + } if ( parent->behavior == &actPlayer ) { if ( oldHP > hitstats->HP ) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index f7e63835e..ec8f0b7f4 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -3332,7 +3332,6 @@ void Player::PlayerMovement_t::handlePlayerMovement(bool useRefreshRateDelta) { Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_MAX_MOVE_100, "wgt", getCharacterWeight()); } - Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_SLOWEST, "wgt", (int)(speedFactor * 100.0)); } static ConsoleVariable cvar_debugspeedfactor("/player_showspeedfactor", false); @@ -4541,6 +4540,8 @@ void actPlayer(Entity* my) players[PLAYER_NUM]->compendiumProgress.playerDistAccum = 0.0; players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeMoving = 0; + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped = 0; Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = my->x; @@ -7471,6 +7472,8 @@ void actPlayer(Entity* my) } if ( stats[PLAYER_NUM]->HP <= 0 ) { + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_WGT_SLOWEST, "wgt", players[PLAYER_NUM]->movement.getCharacterWeight()); + // die //TODO: Refactor. playSoundEntity(my, 28, 128); Entity* gib = spawnGib(my); @@ -8091,6 +8094,11 @@ void actPlayer(Entity* my) if ( dist >= 0.01 ) { players[PLAYER_NUM]->compendiumProgress.playerDistAccum += dist; + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeMoving++; + } + else + { + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped++; } if ( stats[PLAYER_NUM]->sneaking ) { @@ -8106,6 +8114,12 @@ void actPlayer(Entity* my) Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_DISTANCE_TRAVELLED, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerDistAccum); players[PLAYER_NUM]->compendiumProgress.playerDistAccum = 0.0; + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_MOVING_TIME, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerAliveTimeMoving); + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeMoving = 0; + + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_IDLING_TIME, "strafing", (int)players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped); + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped = 0; + Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_SNEAK_TIME, "sneaking", players[PLAYER_NUM]->compendiumProgress.playerSneakTime); players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; } diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 292ce1e0c..ca96c365c 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -757,7 +757,8 @@ void actThrown(Entity* my) enemyAC *= .5; } - real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, parent, parentStats); + int numBlessings = 0; + real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, parent, parentStats, numBlessings); int attackAfterReductions = static_cast(std::max(0.0, ((damage * targetACEffectiveness - enemyAC))) + (1.0 - targetACEffectiveness) * damage); damage = attackAfterReductions; } @@ -815,6 +816,10 @@ void actThrown(Entity* my) Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + if ( hit.entity->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(hit.entity->skill[2], Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", oldHP - hitstats->HP); + } if ( parent && parent->behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdate(parent->skill[2], diff --git a/src/entity.cpp b/src/entity.cpp index 6020d1ddb..ddc42ebf8 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1876,6 +1876,7 @@ bool Entity::increaseSkill(int skill, bool notify) { Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SNEAK_SKILLUP_FLOOR, "sneaking", 1, false, -1, true); } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_UPS_ALL_RUN, "skills", 1); } increased = true; @@ -2367,10 +2368,6 @@ void Entity::setHP(int amount) if ( this->behavior == &actPlayer && entitystats->OLDHP >= entitystats->HP ) { inputs.addRumbleForPlayerHPLoss(skill[2], amount); - if ( healthDiff > 0 ) - { - Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_HP_LOST_RUN, "hp", healthDiff); - } } if ( multiplayer == SERVER ) @@ -2446,7 +2443,16 @@ void Entity::modHP(int amount) return; } + Sint32 oldHP = entitystats->HP; this->setHP(entitystats->HP + amount); + if ( oldHP > entitystats->HP ) + { + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_HP_LOST_RUN, "hp", oldHP - entitystats->HP); + Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_HP_LOST_TOTAL, "hp", oldHP - entitystats->HP); + } + } } /*------------------------------------------------------------------------------- @@ -3187,124 +3193,138 @@ void Entity::handleEffects(Stat* myStats) switch ( increasestat[i] ) { case STAT_STR: // STR + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->STR, 1)); - myStats->STR++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->STR++; + increment++; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "str", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } + myStats->STR += increment; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "str", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "str", myStats->STR); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_STR_MAX, "str", myStats->STR); break; + } case STAT_DEX: // DEX + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->DEX, 1)); - myStats->DEX++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->DEX++; + increment++; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "dex", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "dex", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "dex", myStats->DEX); + myStats->DEX += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "dex", increment); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_DEX_MAX, "dex", myStats->DEX); break; + } case STAT_CON: // CON + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CON, 1)); - myStats->CON++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->CON++; + increment++; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "con", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "con", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "con", myStats->CON); + myStats->CON += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "con", increment); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CON_MAX, "con", myStats->CON); break; + } case STAT_INT: // INT + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->INT, 1)); - myStats->INT++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->INT++; + increment++; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "int", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "int", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "int", myStats->INT); + myStats->INT += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "int", increment); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_INT_MAX, "int", myStats->INT); break; + } case STAT_PER: // PER + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->PER, 1)); - myStats->PER++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->PER++; + increment++; rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "per", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "per", myStats->PER); + myStats->PER += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "per", increment); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_PER_MAX, "per", myStats->PER); break; + } case STAT_CHR: // CHR + { StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CHR, 1)); - myStats->CHR++; + int increment = 1; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { if ( local_rng.rand() % 5 == 0 ) { StatUps.at(StatUps.size() - 1).increaseStat += 1; - myStats->CHR++; + increment++; Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "chr", 1); rolledBonusStat = true; myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); } } - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "chr", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_MAX, "chr", myStats->CHR); + myStats->CHR += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "chr", increment); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CHR_MAX, "chr", myStats->CHR); break; + } + default: + break; } } @@ -5207,7 +5227,7 @@ void Entity::handleEffects(Stat* myStats) myStats->OLDHP = myStats->HP; } -real_t Entity::getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Entity* attacker, Stat* attackerStats) +real_t Entity::getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Entity* attacker, Stat* attackerStats, int& outNumBlessings) { if ( !myStats || !my ) { @@ -5258,7 +5278,7 @@ real_t Entity::getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Enti { blessings += cursedItemIsBuff ? abs(myStats->amulet->beatitude) : myStats->amulet->beatitude; } - + outNumBlessings = blessings; return std::max(0.0, std::min(1.0, .75 + 0.025 * blessings)); } @@ -8452,7 +8472,8 @@ void Entity::attack(int pose, int charge, Entity* target) gugnirProc = true; } } - real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, this, myStats); + int numBlessings = 0; + real_t targetACEffectiveness = Entity::getACEffectiveness(hit.entity, hitstats, hit.entity->behavior == &actPlayer, this, myStats, numBlessings); int attackAfterReductions = static_cast(std::max(0.0, ((myAttack * targetACEffectiveness - enemyAC))) + (1.0 - targetACEffectiveness) * myAttack); if ( weaponskill == PRO_UNARMED ) { @@ -8630,21 +8651,36 @@ void Entity::attack(int pose, int charge, Entity* target) } } - if ( hitstats->defending && damage == 0 && hitstats->shield ) + if ( playerhit >= 0 ) { - if ( playerhit >= 0 ) + if ( hitstats->defending && damage == 0 && hitstats->shield ) { Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BLOCKED_ATTACKS, hitstats->shield->type, 1); Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BLOCKED_HIGHEST_DMG, hitstats->shield->type, myAttack); } + + if ( hitstats->shield && hitstats->defending ) + { + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_CLASS_BLOCK_DEFENDED, "blocking", 1); + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_CLASS_BLOCK_DEFENDED_RUN, "blocking", 1); + } + else + { + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_CLASS_BLOCK_UNDEFENDED, "blocking", 1); + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_CLASS_BLOCK_UNDEFENDED_RUN, "blocking", 1); + } } Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); // do the damage - if ( hitstats->HP < oldHP ) + if ( playerhit >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", oldHP - hitstats->HP); + } + if ( player >= 0 ) { - if ( player >= 0 ) + if ( hitstats->HP < oldHP ) { Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_DMG_TOTAL, "melee", oldHP - hitstats->HP); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_HITS, "melee", 1); @@ -12567,11 +12603,11 @@ void Entity::awardXP(Entity* src, bool share, bool root) { if ( splitscreen ) { - Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_PARTY, src, 1); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); } else { - Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_SOLO, src, 1); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_KILLED_SOLO, src, 1); } } else @@ -12673,7 +12709,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) { if ( splitscreen ) { - Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_PARTY, src, 1); + Compendium_t::Events_t::eventUpdateMonster(leader->skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); } else { @@ -17894,6 +17930,7 @@ void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum) { if ( hitstats.defending ) { + Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_DEGRADED, armor.type, 1); Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN_BY_BLOCKING, armor.type, 1); } Compendium_t::Events_t::eventUpdate(playerhit, Compendium_t::CPDM_BROKEN, armor.type, 1); diff --git a/src/entity.hpp b/src/entity.hpp index 008252180..9dcf98ca3 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -626,7 +626,7 @@ class Entity bool safeConsumeMP(int amount); //A function for the magic code. Attempts to remove mana without overdrawing the player. Returns true if success, returns false if didn't have enough mana. static Sint32 getAttack(Entity* my, Stat* myStats, bool isPlayer = false); - static real_t getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Entity* attacker, Stat* attackerStats); + static real_t getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Entity* attacker, Stat* attackerStats, int& outNumBlessings); static void setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, real_t& baseSkillModifier, real_t& variance); Sint32 getBonusAttackOnTarget(Stat& hitstats); Sint32 getRangedAttack(); diff --git a/src/game.cpp b/src/game.cpp index df12e4ae1..4370aa513 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1636,10 +1636,7 @@ void gameLogic(void) gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); - if ( ticks % TICKS_PER_SECOND == 25 ) - { - Compendium_t::Events_t::updateEventsInMainLoop(clientnum); - } + Compendium_t::Events_t::updateEventsInMainLoop(clientnum); achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); //if( TICKS_PER_SECOND ) @@ -3041,10 +3038,7 @@ void gameLogic(void) gameplayPreferences[i].process(); } updatePlayerConductsInMainLoop(); - if ( ticks % TICKS_PER_SECOND == 25 ) - { - Compendium_t::Events_t::updateEventsInMainLoop(clientnum); - } + Compendium_t::Events_t::updateEventsInMainLoop(clientnum); achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DAPPER, AchievementObserver::DAPPER_EQUIPMENT_CHECK); // ask for entity delete update diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 9461a7a12..e760700f5 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -4126,7 +4126,14 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int players[player]->inventoryUI.miscTooltipOpacitySetpoint = 0; players[player]->inventoryUI.miscTooltipOpacityAnimate = 0.0; - auto& tooltipDisplayedSettings = this->player.inventoryUI.itemTooltipDisplay; + bool compendiumTooltip = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + } + + auto& tooltipDisplayedSettings = compendiumTooltip ? this->player.inventoryUI.compendiumItemTooltipDisplay + : this->player.inventoryUI.itemTooltipDisplay; bool bUpdateDisplayedTooltip = (!tooltipDisplayedSettings.isItemSameAsCurrent(player, item) || ItemTooltips.itemDebug); @@ -4173,9 +4180,9 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int const int imgTopBackgroundDefaultHeight = 28; const int imgTopBackground2XHeight = 42; - const bool doTitleOnlyTooltip = players[player]->shootmode && !(enableDebugKeys && keystatus[SDLK_g]); + const bool doTitleOnlyTooltip = players[player]->shootmode && !compendiumTooltip && !(enableDebugKeys && keystatus[SDLK_g]); bool doShortTooltip = false; - if ( players[player]->shootmode ) + if ( players[player]->shootmode && !compendiumTooltip ) { doShortTooltip = true; if ( doTitleOnlyTooltip ) @@ -4278,11 +4285,15 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int bool expandBindingPressed = false; if ( !players[player]->usingCommand() && players[player]->bControlEnabled - && !gamePaused + && (!gamePaused || compendiumTooltip) && Input::inputs[player].consumeBinaryToggle("Expand Inventory Tooltip") ) { expandBindingPressed = true; - if ( !players[player]->shootmode && item->identified && !doTitleOnlyTooltip + if ( compendiumTooltip ) + { + tooltipDisplayedSettings.expanded = !tooltipDisplayedSettings.expanded; + } + else if ( !players[player]->shootmode && item->identified && !doTitleOnlyTooltip && !doShortTooltip ) { tooltipDisplayedSettings.expanded = !tooltipDisplayedSettings.expanded; @@ -4787,12 +4798,6 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int txtThirdValueNegative->setPaddingPerLine(*cvar_item_tooltip_attr_padding); } - bool compendiumTooltip = false; - if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) - { - compendiumTooltip = true; - } - if ( itemTooltip.icons.size() > 0 ) { int index = 0; @@ -4887,7 +4892,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( icon.conditionalAttribute == "SPELLBOOK_UNLEARNED" ) { - if ( compendiumTooltip ) { continue; } + if ( compendiumTooltip && intro ) { continue; } if ( isGoblin || playerLearnedSpellbook(player, item) || (spell && skillLVL >= spell->difficulty) ) { continue; @@ -4896,7 +4901,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( icon.conditionalAttribute == "SPELLBOOK_UNLEARNABLE" ) { if ( !isGoblin ) { continue; } - if ( compendiumTooltip ) { continue; } + if ( compendiumTooltip && intro ) { continue; } if ( playerLearnedSpellbook(player, item) ) { continue; @@ -4905,7 +4910,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( icon.conditionalAttribute == "SPELLBOOK_LEARNABLE" ) { if ( isGoblin ) { continue; } - if ( compendiumTooltip ) { continue; } + if ( compendiumTooltip && intro ) { continue; } if ( playerLearnedSpellbook(player, item) || (spell && skillLVL < spell->difficulty) ) { continue; @@ -4916,14 +4921,14 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { if ( icon.conditionalAttribute == "SPELLBOOK_SPELLINFO_LEARNED" ) { - if ( !compendiumTooltip && !playerLearnedSpellbook(player, item) ) + if ( !(compendiumTooltip && intro) && !playerLearnedSpellbook(player, item) ) { continue; } } else if ( icon.conditionalAttribute == "SPELLBOOK_SPELLINFO_UNLEARNED" ) { - if ( compendiumTooltip ) { continue; } + if ( compendiumTooltip && intro ) { continue; } if ( playerLearnedSpellbook(player, item) ) { continue; @@ -5332,7 +5337,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( tag.compare("spellbook_cast_success") == 0 || tag.compare("spellbook_extramana_chance") == 0 ) { - if ( compendiumTooltip ) + if ( compendiumTooltip && intro ) { continue; } @@ -5347,7 +5352,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int || tag.compare("spellbook_magic_current") == 0 || tag.compare("spellbook_unlearned_blank_space") == 0 ) { - if ( compendiumTooltip ) + if ( compendiumTooltip && intro ) { continue; } @@ -5370,7 +5375,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( tag.compare("spell_cast_success") == 0 || tag.compare("spell_extramana_chance") == 0 ) { - if ( compendiumTooltip ) + if ( compendiumTooltip && intro ) { continue; } @@ -5382,7 +5387,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } else if ( tag.compare("spell_newbie_newline") == 0 ) { - if ( compendiumTooltip ) + if ( compendiumTooltip && intro ) { continue; } @@ -6165,8 +6170,8 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int framePromptPos.y = frameValuesPos.y + frameValuesPos.h; framePrompt->setSize(framePromptPos); - bool doAppraisalPrompt = !item->identified && isItemFromInventory && appraisal.current_item != item->uid; - bool doAppraisalProgressPrompt = !item->identified && isItemFromInventory && appraisal.current_item == item->uid; + bool doAppraisalPrompt = !item->identified && isItemFromInventory && appraisal.current_item != item->uid && !compendiumTooltip; + bool doAppraisalProgressPrompt = !item->identified && isItemFromInventory && appraisal.current_item == item->uid && !compendiumTooltip; if ( doAppraisalProgressPrompt ) { real_t percent = (((double)(appraisal.timermax - appraisal.timer)) / ((double)appraisal.timermax)) * 100; @@ -6214,7 +6219,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int // prompt for expanding tooltip position auto promptImg = frameMain->findImage("inventory mouse tooltip prompt img"); promptImg->disabled = true; - if ( !players[player]->shootmode + if ( (!players[player]->shootmode || compendiumTooltip) && !txtPrompt->isDisabled() && !doAppraisalProgressPrompt && (!doAppraisalPrompt || (doAppraisalPrompt && stats[player]->HP > 0)) ) @@ -6442,8 +6447,6 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, int justify, Frame* parentFrame) { const int player = this->player.playernum; - const bool doTitleOnlyTooltip = players[player]->shootmode && !(enableDebugKeys && keystatus[SDLK_g]); - auto& tooltipDisplayedSettings = this->player.inventoryUI.itemTooltipDisplay; Frame* tooltipContainerFrame = nullptr; Frame* frameMain = nullptr; @@ -6451,6 +6454,7 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i Frame* frameInteract = nullptr; Frame* frameTooltipPrompt = nullptr; Frame* titleOnlyFrame = nullptr; + bool compendiumTooltip = false; if ( parentFrame != nullptr ) { // hacks for compendium tooltips @@ -6465,6 +6469,11 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i frameInteract = parentFrame->findFrame(name); snprintf(name, sizeof(name), "player item prompt %d", 0); frameTooltipPrompt = tooltipContainerFrame->findFrame(name); + + if ( !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + } } else { @@ -6480,6 +6489,10 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i } } + auto& tooltipDisplayedSettings = compendiumTooltip ? this->player.inventoryUI.compendiumItemTooltipDisplay + : this->player.inventoryUI.itemTooltipDisplay; + + const bool doTitleOnlyTooltip = players[player]->shootmode && !compendiumTooltip && !(enableDebugKeys && keystatus[SDLK_g]); if ( !doTitleOnlyTooltip ) { tooltipDisplayedSettings.opacitySetpoint = 0; @@ -6504,7 +6517,8 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i && !doTitleOnlyTooltip && !players[player]->GUI.isDropdownActive() && !selectedItem - && !inputs.getVirtualMouse(player)->draw_cursor + && !inputs.getVirtualMouse(player)->draw_cursor + && !compendiumTooltip && !players[player]->shootmode && !(itemCategory(item) == SPELL_CAT && (players[player]->shopGUI.bOpen || players[player]->inventoryUI.chestGUI.bOpen)) ) { diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 7ba8e39f7..f5d658bde 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -583,6 +583,33 @@ void spawnBloodVialOnMonsterDeath(Entity* entity, Stat* hitstats, Entity* killer } static ConsoleVariable cvar_magic_fx_use_vismap("/magic_fx_use_vismap", true); +void magicOnPlayerHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID) +{ + if ( !hitentity || !hitstats ) { return; } + + if ( hitentity->behavior != &actPlayer ) + { + return; + } + + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_SPELLS_HIT, "res", 1); + + Sint32 damageTaken = oldHP - hitstats->HP; + if ( damageTaken > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_TAKEN, "res", damageTaken); + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", damageTaken); + if ( preResistanceDamage > damage ) + { + Sint32 noResistDmgTaken = oldHP - std::max(0, oldHP - preResistanceDamage); + if ( noResistDmgTaken > damageTaken ) + { + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED, "res", noResistDmgTaken - damageTaken); + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED_RUN, "res", noResistDmgTaken - damageTaken); + } + } + } +} void magicTrapOnHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 oldHP, int spellID) { @@ -1418,6 +1445,11 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicCastByMagicstaff == 0 && my->actmagicCastByTinkerTrap == 0 ) { spellbookDamageBonus += getBonusFromCasterOfSpellElement(parent, nullptr, element, spell ? spell->ID : SPELL_NONE); + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_CLASS_PWR_MAX_CASTED, "pwr", + 100 + (Sint32)(spellbookDamageBonus * 100.0)); + } } if ( parent && (parent->behavior == &actMagicTrap || parent->behavior == &actMagicTrapCeiling) ) { @@ -1449,10 +1481,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int damage = element->damage; damage += (spellbookDamageBonus * damage); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; damage /= (1 + (int)resistance); Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { @@ -1625,12 +1659,13 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage = damage - local_rng.rand() % ((damage / 8) + 1); } - + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; damage /= (1 + (int)resistance); - Sint32 oldHP; + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { Entity* gib = spawnGib(hit.entity); @@ -1902,6 +1937,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } damage = damage - local_rng.rand() % ((damage / 8) + 1); } + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; if ( parent ) { @@ -1915,6 +1951,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage *= fireMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); //for (i = 0; i < damage; i += 2) { //Spawn a gib for every two points of damage. Entity* gib = spawnGib(hit.entity); @@ -2043,7 +2080,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { int damage = element->damage; damage += (spellbookDamageBonus * damage); - damage /= (1+(int)resistance); + damage /= (1 + (int)resistance); hit.entity->chestHandleDamageMagic(damage, *my, parent); if ( my->actmagicProjectileArc > 0 ) { @@ -2278,6 +2315,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; int oldHP = hitstats->HP; + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; damage *= coldMultiplier; damage /= (1 + (int)resistance); @@ -2285,6 +2323,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( damage > 0 ) { hit.entity->modHP(-damage); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); Entity* gib = spawnGib(hit.entity); serverSpawnGibForClient(gib); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); @@ -2540,10 +2579,11 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; int oldHP = hitstats->HP; + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); - + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); // write the obituary @@ -3212,6 +3252,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int damage = element->damage; damage += (spellbookDamageBonus * damage); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; Stat* casterStats = nullptr; if ( parent ) @@ -3226,7 +3267,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); - + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); // write the obituary diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 486be5524..8d7560765 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -397,12 +397,14 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool bool playerCastingFromKnownSpellbook = false; int spellBookBonusPercent = 0; int spellBookBeatitude = 0; + ItemType spellbookType = WOODEN_SHIELD; if ( !using_magicstaff && !trap && stat ) { newbie = isSpellcasterBeginner(player, caster); if ( usingSpellbook && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) { + spellbookType = stat->shield->type; spellBookBeatitude = stat->shield->beatitude; spellBookBonusPercent += getSpellbookBonusPercent(caster, stat, stat->shield); if ( spellcasting >= spell->difficulty || playerLearnedSpellbook(player, stat->shield) ) @@ -544,7 +546,15 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool if ( usingSpellbook && stat->shield && itemCategory(stat->shield) == SPELLBOOK && (stat->shield->beatitude < 0 && !shouldInvertEquipmentBeatitude(stat)) ) { + Status oldStatus = stat->shield->status; caster->degradeArmor(*stat, *(stat->shield), 4); + if ( stat->shield->status < oldStatus ) + { + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CAST_DEGRADES, stat->shield->type, 1); + } + } if ( stat->shield->status == BROKEN ) { Item* toBreak = stat->shield; @@ -553,10 +563,21 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } if ( player >= 0 ) { - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_FAILURES, SPELL_ITEM, 1, false, spell->ID); - if ( !usingSpellbook && !using_magicstaff && !trap ) + if ( !using_magicstaff && !trap ) { - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_FIZZLES_RUN, "memorized", 1); + if ( !usingSpellbook ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_FAILURES, SPELL_ITEM, 1, false, spell->ID); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_FIZZLES_RUN, "memorized", 1); + } + else if ( usingSpellbook ) + { + if ( items[spellbookType].category == SPELLBOOK ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_FAILURES, spellbookType, 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELLBOOK_FIZZLES_RUN, "spellbook casting", 1); + } + } } } return NULL; @@ -1476,7 +1497,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } } // spellbook 100-150%, 50 INT = 200%. - amount += amount * ((spellBookBonusPercent * 1 / 100.f) + getBonusFromCasterOfSpellElement(caster, nullptr, element, spell ? spell->ID : SPELL_NONE)); + real_t bonus = ((spellBookBonusPercent * 1 / 100.f) + getBonusFromCasterOfSpellElement(caster, nullptr, element, spell ? spell->ID : SPELL_NONE)); + amount += amount * bonus; + if ( caster && caster->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateCodex(caster->skill[2], Compendium_t::CPDM_CLASS_PWR_MAX_CASTED, "pwr", + (Sint32)(bonus * 100.0)); + } int totalHeal = 0; int oldHP = players[i]->entity->getHP(); @@ -2456,17 +2483,26 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { if ( player >= 0 ) { - if ( usingSpellbook && !using_magicstaff ) + if ( !using_magicstaff ) { - if ( stat && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) + if ( !usingSpellbook ) { - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CASTS, stat->shield->type, 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_CASTS_RUN, "memorized", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_CASTS, SPELL_ITEM, 1, false, spell->ID); + } + else if ( usingSpellbook ) + { + if ( items[spellbookType].category == SPELLBOOK ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELLBOOK_CASTS_RUN, "spellbook casting", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CASTS, spellbookType, 1); + + if ( items[spellbookType].hasAttribute("SPELLBOOK_CAST_BONUS") ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_PWR_MAX_SPELLBOOK, "pwr", spellBookBonusPercent); + } + } } - } - else if ( !usingSpellbook && !using_magicstaff ) - { - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELL_CASTS, SPELL_ITEM, 1, false, spell->ID); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SPELL_CASTS_RUN, "memorized", 1); } } if ( using_magicstaff ) @@ -2632,7 +2668,16 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } if ( local_rng.rand() % chance == 0 && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) { + Status oldStatus = stat->shield->status; caster->degradeArmor(*stat, *(stat->shield), 4); + if ( stat->shield->status < oldStatus ) + { + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CAST_DEGRADES, stat->shield->type, 1); + } + } + if ( stat->shield->status == BROKEN && player >= 0 ) { if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->appearance == 0 ) diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index c592359ef..01119211e 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -300,11 +300,17 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re } int oldHP = hitstats->HP; - damage /= (1 + (int)resistance); + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; + damage /= (1 + (int)resistance); if ( !hasgoggles ) { hit.entity->modHP(-damage); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_ACID_SPRAY); + } + else + { + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, 0, oldHP, SPELL_ACID_SPRAY); } // write the obituary @@ -487,10 +493,14 @@ void spellEffectPoison(Entity& my, spellElement_t& element, Entity* parent, int dmgGib = DMG_WEAKEST; } - damage /= (1 + (int)resistance); + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; + damage /= (1 + (int)resistance); + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_POISON); + // write the obituary if ( parent ) { @@ -974,8 +984,10 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i int damage = element.damage; damage += damage * ((my.actmagicSpellbookBonus / 100.f) + getBonusFromCasterOfSpellElement(parent, nullptr, &element, SPELL_DRAIN_SOUL)); //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; - damage /= (1 + (int)resistance); + + Sint32 preResistanceDamage = damage; damage *= damageMultiplier; + damage /= (1 + (int)resistance); if ( parent ) { @@ -988,7 +1000,11 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i int damageHP = hitstats->HP; int damageMP = hitstats->MP; + Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); + + magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_DRAIN_SOUL); + if ( damage > hitstats->MP ) { damage = hitstats->MP; diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index c32b2c846..afd99c342 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -507,6 +507,8 @@ int getSpellcastingAbilityFromUsingSpellbook(spell_t* spell, Entity* caster, Sta bool isSpellcasterBeginnerFromSpellbook(int player, Entity* caster, Stat* stat, spell_t* spell, Item* spellbookItem); int getSpellbookBonusPercent(Entity* caster, Stat* stat, Item* spellbookItem); real_t getBonusFromCasterOfSpellElement(Entity* caster, Stat* casterStats, spellElement_t* spellElement, int spellID); +real_t getSpellBonusFromCasterINT(Entity* caster, Stat* casterStats); +void magicOnPlayerHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID); #endif bool isSpellcasterBeginner(int player, Entity* caster); void actMagicTrap(Entity* my); diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index d53eb463d..dfff78767 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -630,25 +630,44 @@ bool spell_isChanneled(spell_t* spell) return false; } -real_t getBonusFromCasterOfSpellElement(Entity* caster, Stat* casterStats, spellElement_t* spellElement, int spellID) +real_t getSpellBonusFromCasterINT(Entity* caster, Stat* casterStats) { if ( caster && caster->behavior != &actPlayer ) { return 0.0; } + real_t bonus = 0.0; if ( !casterStats && caster ) { casterStats = caster->getStats(); } + if ( casterStats ) + { + int INT = statGetINT(casterStats, caster); + if ( INT > 0 ) + { + bonus += INT / 100.0; + } + } + return bonus; +} - real_t bonus = 0.0; - int INT = statGetINT(casterStats, caster); - if ( INT > 0 ) +real_t getBonusFromCasterOfSpellElement(Entity* caster, Stat* casterStats, spellElement_t* spellElement, int spellID) +{ + if ( caster && caster->behavior != &actPlayer ) + { + return 0.0; + } + + if ( !casterStats && caster ) { - bonus += INT / 100.0; + casterStats = caster->getStats(); } + real_t bonus = 0.0; + bonus += getSpellBonusFromCasterINT(caster, casterStats); + if ( casterStats ) { if ( casterStats->helmet ) diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index e0d404858..06ba7188c 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3840,6 +3840,23 @@ struct Compendium_t CPDM_WANTED_TIMES_RUN, CPDM_WANTED_INFLUENCE, CPDM_WANTED_CRIMES_RUN, + CPDM_CUSTOM_TAG, + CPDM_SPELLBOOK_CAST_DEGRADES, + CPDM_CLASS_SPELLBOOK_CASTS_RUN, + CPDM_CLASS_SPELLBOOK_FIZZLES_RUN, + CPDM_CLASS_MOVING_TIME, + CPDM_CLASS_IDLING_TIME, + CPDM_CLASS_SKILL_UPS_ALL_RUN, + CPDM_CLASS_WGT_EQUIPPED_MAX, + CPDM_CLASS_PWR_MAX_CASTED, + CPDM_PWR_MAX_EQUIP, + CPDM_PWR_MAX_SPELLBOOK, + CPDM_RES_DMG_TAKEN, + CPDM_RES_SPELLS_HIT, + CPDM_HP_MOST_DMG_LOST_ONE_HIT, + CPDM_HP_LOST_TOTAL, + CPDM_AC_EFFECTIVENESS_MAX, + CPDM_AC_EQUIPMENT_MAX, CPDM_EVENT_TAGS_MAX }; @@ -3869,6 +3886,22 @@ struct Compendium_t return ""; } + struct CompendiumEntityCurrent + { + std::string contentsName = ""; + std::string modelName = ""; + int modelIndex = -1; + Uint32 modelRNG = 0; + void set(std::string _contentsName, std::string _modelName, int _modelIndex = -1) + { + contentsName = _contentsName; + modelName = _modelName; + modelIndex = _modelIndex; + ++modelRNG; + } + }; + static CompendiumEntityCurrent compendiumEntityCurrent; + struct Events_t { enum Type @@ -3922,6 +3955,8 @@ struct Compendium_t static std::map> itemEventLookup; static std::map monsterUniqueIDLookup; static std::map> itemDisplayedEventsList; + static std::map> itemDisplayedCustomEventsList; + static std::map customEventsValues; static std::map> eventItemLookup; static std::map> eventMonsterLookup; static std::map> eventWorldLookup; @@ -3931,6 +3966,9 @@ struct Compendium_t static std::map> eventClassIds; static const int kEventClassesMax = 40; static std::map> eventLangEntries; + static std::map> eventCustomLangEntries; + static std::vector> getCustomEventValue(std::string key, int specificClass = -1); + static std::string formatEventRecordText(Sint32 value, const char* formatType, int formatVal, std::map& langMap); static void readEventsFromFile(); static void writeItemsSaveData(); static void loadItemsSaveData(); diff --git a/src/player.hpp b/src/player.hpp index a6c612fdf..1a109fa5e 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -997,6 +997,7 @@ class Player ItemTooltipDisplay_t(); }; ItemTooltipDisplay_t itemTooltipDisplay; + ItemTooltipDisplay_t compendiumItemTooltipDisplay; int DEFAULT_INVENTORY_SIZEX = 12; int DEFAULT_INVENTORY_SIZEY = 3; @@ -2272,6 +2273,8 @@ class Player std::map>> floorEvents; real_t playerDistAccum = 0.0; Uint32 playerSneakTime = 0; + Uint32 playerAliveTimeMoving = 0; + Uint32 playerAliveTimeStopped = 0; CompendiumProgress_t(Player& p) : player(p) {}; ~CompendiumProgress_t() {}; diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 8e426b7d7..f3f9a1815 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -16049,12 +16049,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element case SHEET_INT: { //real_t val = getBonusFromCasterOfSpellElement(players[player.playernum]->entity, stats[player.playernum], nullptr, SPELL_NONE) * 100.0; - int INT = statGetINT(stats[player.playernum], players[player.playernum]->entity); - real_t bonus = 0.0; - if ( INT > 0 ) - { - bonus += INT / 100.0; - } + real_t bonus = getSpellBonusFromCasterINT(players[player.playernum]->entity, stats[player.playernum]); real_t val = bonus * 100.0; snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_pwr_value_format").c_str(), val); } @@ -21643,8 +21638,11 @@ void drawItemPreview(Entity* item, SDL_Rect pos, real_t offsetyaw, bool dark) { if ( item->flags[SPRITE] && item->skill[10] == SPELL_ITEM ) { + ItemType tmpItem = Compendium_t::compendiumItem.type; + Compendium_t::compendiumItem.type = SPELL_ITEM; glDrawSpriteFromImage(&view, item, ItemTooltips.getSpellIconPath(clientnum, Compendium_t::compendiumItem, item->skill[14]), REALCOLORS, true, true); + Compendium_t::compendiumItem.type = tmpItem; } else { @@ -21819,6 +21817,15 @@ void drawSpritesPreview(std::string name, std::string modelsPath, SDL_Rect pos, { if ( codexEntry->renderedImagePaths[index] != "" ) { + if ( Compendium_t::compendiumEntityCurrent.modelIndex != -1 ) + { + if ( index != Compendium_t::compendiumEntityCurrent.modelIndex ) + { + e.flags[BRIGHT] = b; + ++index; + continue; + } + } glDrawSpriteFromImage(&view, &e, codexEntry->renderedImagePaths[index], REALCOLORS, true, true); } @@ -22311,7 +22318,12 @@ void drawObjectPreview(std::string modelsPath, Entity* object, SDL_Rect pos, rea { for ( auto& limb : *limbsArray ) { - if ( limb.sprite == 3 ) // torch + if ( limb.sprite >= 1238 && limb.sprite <= 1242 ) + { + // ghost limbs + limb.yaw += 0.05; + } + else if ( limb.sprite == 3 ) // torch { Entity* flame = newEntity(SPRITE_FLAME, 0, &limb.children, nullptr); flame->x = limb.x; @@ -22341,7 +22353,15 @@ void drawObjectPreview(std::string modelsPath, Entity* object, SDL_Rect pos, rea { if ( list_Size(&limb.children) == 0 ) { - Entity* entity = newEntity(245, 0, &limb.children, nullptr); + Entity* entity = nullptr; + if ( modelsPath == "brimstone" ) + { + entity = newEntity(989, 0, &limb.children, nullptr); + } + else + { + entity = newEntity(245, 0, &limb.children, nullptr); + } entity->x = limb.x; entity->y = limb.y; entity->z = -64; @@ -26917,7 +26937,13 @@ void Player::Inventory_t::updateInventoryItemTooltip(Frame* parentFrame) return; } - auto& tooltipDisplay = this->itemTooltipDisplay; + bool compendiumTooltip = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + } + auto& tooltipDisplay = compendiumTooltip ? this->compendiumItemTooltipDisplay + : this->itemTooltipDisplay; if ( static_cast(frameMain->getOpacity()) != tooltipDisplay.opacitySetpoint ) { @@ -33255,24 +33281,14 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& else if ( tag == "MAGIC_SPELLPOWER_INT" ) { //val = (getBonusFromCasterOfSpellElement(player, stats[playernum], nullptr, SPELL_NONE) * 100.0); - int INT = statGetINT(stats[playernum], players[playernum]->entity); - real_t bonus = 0.0; - if ( INT > 0 ) - { - bonus += INT / 100.0; - } + real_t bonus = getSpellBonusFromCasterINT(players[playernum]->entity, stats[playernum]); val = bonus * 100.0; snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } else if ( tag == "MAGIC_SPELLPOWER_EQUIPMENT" ) { val = (getBonusFromCasterOfSpellElement(player, stats[playernum], nullptr, SPELL_NONE) * 100.0); - int INT = statGetINT(stats[playernum], players[playernum]->entity); - real_t bonus = 0.0; - if ( INT > 0 ) - { - bonus += INT / 100.0; - } + real_t bonus = getSpellBonusFromCasterINT(players[playernum]->entity, stats[playernum]); val -= bonus * 100.0; snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 79005d9fe..7344dc95e 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -25661,7 +25661,7 @@ namespace MainMenu { options.insert(options.begin(), { {"Back to Game", Language::get(5761), mainClose}, {"Assign Controllers", Language::get(5762), mainAssignControllers}, - //{"Dungeon Compendium", Language::get(5763), archivesDungeonCompendium}, // TODO + {"Dungeon Compendium", Language::get(5763), archivesDungeonCompendium}, #ifndef STEAMWORKS #if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) {"Achievements", Language::get(5764), archivesAchievements}, @@ -32550,10 +32550,9 @@ namespace MainMenu { static Entity* compendiumMonster = nullptr; static bool compendiumMonsterOverride = false; - static Uint32 compendiumRNG = 0; - static std::pair compendiumEntityCurrent = { "", ""}; // contents name, then model filename static std::vector compendiumMonsterLimbs; - static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName = ""); + static ConsoleVariable cvar_compendium_monster_entity("/compendium_monster_entity", false); + static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName = "", int specificClass = -1); static void refreshCompendiumCamera(std::string& modelsPath) { auto find = CompendiumEntries.compendiumObjectLimbs.find(modelsPath); @@ -32573,6 +32572,7 @@ namespace MainMenu { list_RemoveNode(compendiumMonster->mynode); compendiumMonster = nullptr; } + if ( !(*cvar_compendium_monster_entity) ) { return nullptr; } Entity* entity = newEntity(-1, 1, map.entities, map.creatures); //Monster entity. compendiumMonster = entity; @@ -32629,10 +32629,12 @@ namespace MainMenu { } auto& entry = CompendiumEntries.worldObjects[name]; - compendiumEntityCurrent.first = name; - compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + Compendium_t::compendiumEntityCurrent.set( + name, + entry.models.empty() ? "" : entry.models[Compendium_t::compendiumEntityCurrent.modelRNG % entry.models.size()] + ); - refreshCompendiumCamera(compendiumEntityCurrent.second); + refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); if ( Frame* page_left = parent->findFrame("page_left") ) { @@ -32800,9 +32802,14 @@ namespace MainMenu { } } - static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName) + static std::map> compendiumRecordsSectionLoadedValues; + static Uint32 compendiumRecordsSectionRandSequence = 0; + static Uint32 compendiumRecordsProcessedOnTick = 0; + static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName, int specificClass) { if ( !page_right ) { return; } + compendiumRecordsSectionLoadedValues.clear(); + compendiumRecordsSectionRandSequence = 0; if ( auto page_right_overlay = page_right->findFrame("page_right_overlay") ) { @@ -32856,6 +32863,21 @@ namespace MainMenu { { displayedEvents = Compendium_t::Events_t::itemDisplayedEventsList[entryType]; } + + if ( entryType >= 0 && entryType < NUMITEMS ) + { + if ( items[entryType].category == SPELLBOOK ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELLBOOK_LEARNT); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELLBOOK_CASTS); + } + } + else if ( entryType >= Compendium_t::Events_t::kEventSpellOffset + && entryType < Compendium_t::Events_t::kEventSpellOffset + 1000 ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_CASTS); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_FAILURES); + } } if ( displayedEvents.size() > 0 ) @@ -32888,6 +32910,8 @@ namespace MainMenu { break; } + std::string customTagKey = ""; + if ( txt ) { txt->setDisabled(false); @@ -32940,13 +32964,55 @@ namespace MainMenu { } } } - txt->setText(Compendium_t::Events_t::eventLangEntries[tag][itemname].c_str()); + + std::string str = Compendium_t::Events_t::eventLangEntries[tag][itemname]; + if ( tag == Compendium_t::EventTags::CPDM_CUSTOM_TAG ) + { + auto find = Compendium_t::Events_t::itemDisplayedCustomEventsList.find(entryType); + if ( find != Compendium_t::Events_t::itemDisplayedCustomEventsList.end() ) + { + str = "CUSTOM_TAG"; + if ( index < find->second.size() ) + { + customTagKey = find->second[index]; + auto find2 = Compendium_t::Events_t::eventCustomLangEntries.find(customTagKey); + if ( find2 != Compendium_t::Events_t::eventCustomLangEntries.end() ) + { + if ( find2->second.find(entryName) != find2->second.end() ) + { + itemname = entryName; + } + str = find2->second[itemname]; + } + } + } + } + txt->setText(str.c_str()); } if ( val ) { val->setDisabled(false); int value = 0; - if ( entryType >= Compendium_t::Events_t::kEventCodexOffset + + if ( customTagKey != "" ) + { + auto results = Compendium_t::Events_t::getCustomEventValue(customTagKey, specificClass); + if ( results.size() == 0 ) + { + val->setText("-"); + } + else + { + compendiumRecordsSectionLoadedValues[index].clear(); + for ( auto& pair : results ) + { + compendiumRecordsSectionLoadedValues[index].push_back(pair.first); + } + val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence + % compendiumRecordsSectionLoadedValues[index].size()].c_str()); + } + } + else if ( entryType >= Compendium_t::Events_t::kEventCodexOffset && entryType <= Compendium_t::Events_t::kEventCodexOffsetMax && Compendium_t::Events_t::eventClassIds.find(tag) != Compendium_t::Events_t::eventClassIds.end() ) { @@ -32959,50 +33025,147 @@ namespace MainMenu { { bool firstValue = true; int entryNum = -1; + + int startOffsetId = -1; + if ( findEvent->second.attributes.find("skills") != findEvent->second.attributes.end() ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( !strcmp(entryName, Compendium_t::getSkillStringForCompendium(i)) ) + { + startOffsetId = Compendium_t::Events_t::eventClassIds[tag][0] + i * Compendium_t::Events_t::kEventClassesMax; + break; + } + } + } + + std::vector> results; + int minValue = 0; + int maxValue = 0; + int sum = 0; + bool useSum = findEvent->second.attributes.find("display_sum_classes") != findEvent->second.attributes.end(); + auto type = Compendium_t::Events_t::Type::MAX; for ( auto& pair : Compendium_t::Events_t::eventClassIds[tag] ) { + if ( startOffsetId >= 0 ) + { + if ( pair.second < startOffsetId || pair.second >= startOffsetId + Compendium_t::Events_t::kEventClassesMax ) + { + continue; + } + } + auto find = findTag->second.find(pair.second); if ( find != findTag->second.end() ) { - if ( find->second.type == Compendium_t::Events_t::MAX - || find->second.type == Compendium_t::Events_t::SUM ) + if ( useSum ) { - if ( find->second.value > value ) - { - entryNum = pair.first; - value = find->second.value; - } + sum += find->second.value; + type = Compendium_t::Events_t::Type::SUM; + continue; } - else if ( find->second.type == Compendium_t::Events_t::MIN ) + else { - if ( firstValue || find->second.value < value ) + type = find->second.type; + entryNum = pair.first; + value = find->second.value; + } + + if ( firstValue ) + { + minValue = find->second.value; + } + else + { + minValue = std::min(minValue, find->second.value); + } + maxValue = std::max(maxValue, find->second.value); + + firstValue = false; + + std::string output = ""; + if ( findEvent->second.attributes.find("stats") != findEvent->second.attributes.end() + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_STR_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_DEX_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CON_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_INT_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_PER_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CHR_MAX ) + { + if ( !strcmp(entryName, "str") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_STR, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "dex") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_DEX, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "con") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CON, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "int") ) { - entryNum = pair.first; - value = find->second.value; + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_INT, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "per") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_PER, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "chr") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CHR, Compendium_t::Events_t::eventLangEntries[tag]); } } - firstValue = false; + else if ( findEvent->second.attributes.find("class") != findEvent->second.attributes.end() ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "class", entryNum % Compendium_t::Events_t::kEventClassesMax, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( findEvent->second.attributes.find("race") != findEvent->second.attributes.end() ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "race", entryNum, Compendium_t::Events_t::eventLangEntries[tag]); + } + else + { + output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + } + results.push_back(std::make_pair(value, output)); } } - if ( !firstValue ) + if ( useSum ) { - std::string output = std::to_string(value); - if ( findEvent->second.attributes.find("class") != findEvent->second.attributes.end() ) + std::string output = Compendium_t::Events_t::formatEventRecordText(sum, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + results.push_back(std::make_pair(sum, output)); + } + else + { + for ( auto itr = results.begin(); itr != results.end(); ) { - if ( entryNum >= 0 && entryNum < NUMCLASSES ) + if ( (type == Compendium_t::Events_t::MAX || type == Compendium_t::Events_t::SUM) + && itr->first != maxValue ) + { + itr = results.erase(itr); + } + else if ( type == Compendium_t::Events_t::MIN && itr->first != minValue ) { - output += ' '; - output += playerClassLangEntry(entryNum, 0); - val->setText(output.c_str()); + itr = results.erase(itr); + } + else + { + ++itr; } } - else if ( findEvent->second.attributes.find("race") != findEvent->second.attributes.end() ) + } + + if ( results.size() > 0 ) + { + compendiumRecordsSectionLoadedValues[index].clear(); + for ( auto& pair : results ) { - int type = getMonsterFromPlayerRace(entryNum); - output += ' '; - output += getMonsterLocalizedName((Monster)type); - val->setText(output.c_str()); + compendiumRecordsSectionLoadedValues[index].push_back(pair.second); } + val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence + % compendiumRecordsSectionLoadedValues[index].size()].c_str()); } } } @@ -33032,7 +33195,47 @@ namespace MainMenu { value = find->second.value; } } - val->setText(std::to_string(value).c_str()); + std::string output = ""; + auto findEvent = Compendium_t::Events_t::events.find(tag); + if ( find == findTag->second.end() ) + { + // no matching record + val->setText("-"); + } + else if ( findEvent != Compendium_t::Events_t::events.end() + && findEvent->second.attributes.find("stats") != findEvent->second.attributes.end() ) + { + if ( !strcmp(entryName, "str") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_STR, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "dex") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_DEX, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "con") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CON, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "int") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_INT, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "per") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_PER, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "chr") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CHR, Compendium_t::Events_t::eventLangEntries[tag]); + } + val->setText(output.c_str()); + } + else + { + output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + val->setText(output.c_str()); + } } else { @@ -33047,6 +33250,48 @@ namespace MainMenu { static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner) { + if ( compendium_current == "codex" ) + { + for ( auto f : page_right_inner.getFrames() ) + { + if ( auto txt = f->findField("item name") ) + { + auto txtRight = f->findField("item txt right"); + if ( f == &toSelect ) + { + txt->setColor(compendiumContentsSelectedColor); + if ( txtRight ) + { + txtRight->setColor(compendiumContentsSelectedColor); + } + + refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); + + // find records for this item + auto findCat = Compendium_t::Events_t::eventCodexIDLookup.find(Compendium_t::compendiumEntityCurrent.contentsName); + if ( findCat != Compendium_t::Events_t::eventCodexIDLookup.end() ) + { + int index = -1; + if ( f->getUserData() ) + { + index = (reinterpret_cast(f->getUserData()) & 0x7F); + } + Compendium_t::compendiumEntityCurrent.modelIndex = index + 1; + populateRecordsSectionItems(page_right_inner.getParent(), Compendium_t::Events_t::kEventCodexOffset + findCat->second, findCat->first.c_str(), index); + } + } + else + { + txt->setColor(compendiumContentsDefaultColor); + if ( txtRight ) + { + txtRight->setColor(compendiumContentsDefaultColor); + } + } + } + } + return; + } Compendium_t::compendiumItemModel.sprite = -1; Compendium_t::compendiumItemModel.focalz = 0.5; Compendium_t::compendiumItemModel.roll = 0.0; @@ -33360,6 +33605,264 @@ namespace MainMenu { } } + static void refreshCompendiumEntryCodexList(std::string name, Frame* parent) + { + if ( CompendiumEntries.codex.find(name) == CompendiumEntries.codex.end() ) + { + return; + } + auto& entry = CompendiumEntries.codex[name]; + std::vector entries; + if ( name == "classes list" ) + { + for ( int i = 0; i < NUMCLASSES; ++i ) + { + std::string classname = playerClassLangEntry(i, 0); + camelCaseString(classname); + entries.push_back(classname); + } + } + if ( Frame* page_right = parent->findFrame("page_right") ) + { + if ( page_right = page_right->findFrame("page_right_inner") ) + { + for ( auto f : page_right->getFrames() ) + { + f->removeSelf(); + } + + const int entrySize = 64; + + std::vector> rankedScores; + for ( int j = 0; j < NUMCLASSES; ++j ) + { + auto r = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", j); + if ( r.size() > 0 ) + { + if ( r[0].second > 0 ) + { + rankedScores.push_back(std::make_pair(r[0].second, j)); + } + } + } + std::sort(rankedScores.begin(), rankedScores.end(), [](const std::pair& lhs, const std::pair& rhs) { + return lhs > rhs; + }); + int lastRank = rankedScores[0].first; + int rankNum = 1; + for ( auto& pair : rankedScores ) + { + if ( lastRank != pair.first ) + { + ++rankNum; + } + pair.first = rankNum; + } + + for ( int i = 0; i < entries.size(); ++i ) + { + auto& data = entries[i]; + auto entry = page_right->addFrame(data.c_str()); + entry->setHollow(true); + entry->setClickable(false); + entry->setSize(SDL_Rect{ 8, 0 + i * entrySize, page_right->getSize().w - 32, entrySize }); + /*Button* btn = entry->addButton("item btn"); + btn->setSize(entry->getSize()); + btn->setText("click");*/ + + if ( i == 0 ) + { + auto selector_bg = page_right->addImage(SDL_Rect{ entry->getSize().x, 0, entry->getSize().w, entry->getSize().h }, + makeColorRGB(71, 41, 27), "images/system/white.png", "selector_bg"); + selector_bg->disabled = true; + } + + entry->setTickCallback([](Widget& widget) { + auto frame = static_cast(&widget); + if ( Frame* page_right_inner = frame->getParent() ) + { + auto selector_bg = page_right_inner->findImage("selector_bg"); + if ( frame->getUserData() ) + { + bool first = (reinterpret_cast(frame->getUserData()) >> 7) & 1; + if ( first ) + { + if ( selector_bg ) + { + selector_bg->disabled = true; + } + } + } + auto txt = frame->findField("item name"); + /*if ( parent = parent->getParent() )*/ + { + if ( Frame* parent = page_right_inner->getParent() ) + { + if ( parent = parent->getParent() ) + { + SDL_Rect absolutePos = frame->getAbsoluteSize(); + if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) + { + selectCompendiumItemInList(*frame, *page_right_inner); + } + + SDL_Rect pos = frame->getSize(); + bool hovered = false; + if ( selector_bg ) + { + selector_bg->disabled = false; + selector_bg->pos.y = frame->getSize().y; + } + auto itemBg = frame->findImage("item bg"); + if ( itemBg ) + { + SDL_Rect tmp = frame->getSize(); + tmp.x += itemBg->pos.x; + tmp.y += itemBg->pos.y; + tmp.w = itemBg->pos.w; + tmp.h = itemBg->pos.h; + frame->setSize(tmp); + hovered = frame->capturesMouseInRealtimeCoords(); + } + frame->setSize(pos); + + if ( hovered ) + { + //frame->select(); + /*const int itemType = strstr(widget.getName(), "spell_") ? SPELL_ITEM + : ItemTooltips.itemNameStringToItemID[widget.getName()]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::compendiumItem.type = (ItemType)itemType; + if ( frame->getUserData() ) + { + Compendium_t::compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); + } + + Compendium_t::tooltipPos.x = absolutePos.x - 16; + Compendium_t::tooltipPos.y = absolutePos.y; + Compendium_t::tooltipNeedUpdate = true; + + }*/ + page_right_inner->setAllowScrollBinds(false); + } + } + } + } + } + } + }); + + /*auto itemBg = entry->addImage(SDL_Rect{ 8, 8, 48, 48 }, + 0xFFFFFFFF, + "*images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_02.png", "item bg");*/ + auto itemImg = entry->addImage(SDL_Rect{ 1, 5, 54, 54 }, + 0xFFFFFFFF, "", "item img"); + + auto itemName = entry->addField("item name", 128); + itemName->setFont(smallfont_outline); + itemName->setSize(SDL_Rect{ 8 + 48 + 4, 8, entry->getSize().w - 8, 48 }); + + auto itemNameRight = entry->addField("item txt right", 128); + itemNameRight->setFont(itemName->getFont()); + SDL_Rect pos = itemName->getSize(); + pos.w = entry->getSize().w - pos.x - 8; + itemNameRight->setSize(pos); + itemNameRight->setHJustify(Field::justify_t::RIGHT); + itemNameRight->setText(""); + itemNameRight->setColor(compendiumContentsDefaultColor); + for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line + { + itemNameRight->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + } + + if ( name == "classes list" ) + { + std::string name = data; + name += '\n'; + char buf[64] = "00:00:00:00"; + + auto findLang = Compendium_t::Events_t::eventCustomLangEntries.find("CUSTOM_CLASS_PLAYTIME"); + if ( findLang != Compendium_t::Events_t::eventCustomLangEntries.end() ) + { + name += findLang->second["default"]; + name += ' '; + } + auto results = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", i); + if ( results.size() > 0 ) + { + Uint32 playtimeTicks = results[0].second; + if ( playtimeTicks == 0 ) + { + name += '-'; + } + else + { + Uint32 sec = (playtimeTicks / TICKS_PER_SECOND) % 60; + Uint32 min = ((playtimeTicks / TICKS_PER_SECOND) / 60) % 60; + Uint32 hour = (((playtimeTicks / TICKS_PER_SECOND) / 60) / 60) % 24; + Uint32 day = ((playtimeTicks / TICKS_PER_SECOND) / 60) / 60 / 24; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d:%02d", day, hour, min, sec); + name += buf; + + for ( auto& pair : rankedScores ) + { + if ( pair.second == i ) + { + std::string txt = "\n"; + if ( findLang != Compendium_t::Events_t::eventCustomLangEntries.end() ) + { + txt += findLang->second["format"]; + } + txt += std::to_string(pair.first); + itemNameRight->setText(txt.c_str()); + break; + } + } + } + } + else + { + name += '-'; + } + itemName->setText(name.c_str()); + for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line + { + itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + } + + const auto class_name = classes_in_order[i]; + const auto class_find = classes.find(class_name); + if ( class_find != classes.end() ) + { + itemImg->path = "*images/ui/Main Menus/Play/PlayerCreation/ClassSelection/"; + itemImg->path += class_find->second.image_highlighted; + } + } + + Uint8 userData = i; + if ( i == 0 ) + { + userData |= 1 << 7; + entry->setUserData((void*)(intptr_t)userData); + selectCompendiumItemInList(*entry, *page_right); + } + else + { + entry->setUserData((void*)(intptr_t)userData); + itemName->setColor(compendiumContentsDefaultColor); + } + + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); + page_right->setActualSize(actualPos); + } + } + } + } + static void refreshCompendiumEntryCodex(std::string name, Frame* parent) { if ( CompendiumEntries.codex.find(name) == CompendiumEntries.codex.end() ) @@ -33368,10 +33871,12 @@ namespace MainMenu { } auto& entry = CompendiumEntries.codex[name]; - compendiumEntityCurrent.first = name; - compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + Compendium_t::compendiumEntityCurrent.set( + name, + entry.models.empty() ? "" : entry.models[Compendium_t::compendiumEntityCurrent.modelRNG % entry.models.size()] + ); - refreshCompendiumCamera(compendiumEntityCurrent.second); + refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); if ( Frame* page_left = parent->findFrame("page_left") ) { @@ -33416,21 +33921,67 @@ namespace MainMenu { if ( Frame* page_right = parent->findFrame("page_right") ) { // find records for this item - populateRecordsSectionItems(page_right, entry.id + Compendium_t::Events_t::kEventCodexOffset, name.c_str()); + if ( name == "classes list" ) + { + // do nothing + } + else + { + populateRecordsSectionItems(page_right, entry.id + Compendium_t::Events_t::kEventCodexOffset, name.c_str()); + } if ( page_right = page_right->findFrame("page_right_inner") ) { + for ( auto f : page_right->getFrames() ) + { + f->removeSelf(); + } if ( auto details = page_right->findField("details") ) { - std::string txt = ""; - for ( auto& str : entry.details ) + Field* tipsTxt = page_right->findField("tips txt"); + if ( tipsTxt ) { - if ( txt != "" ) { txt += '\n'; } - txt += str; + tipsTxt->setDisabled(true); } - details->setText(txt.c_str()); + if ( name == "classes list" ) + { + details->setText(""); + refreshCompendiumEntryCodexList(name, parent); + return; + } + else + { + if ( tipsTxt ) + { + tipsTxt->setDisabled(false); + } + std::string txt = ""; + for ( auto& str : entry.details ) + { + if ( txt != "" ) { txt += '\n'; } + txt += str; + } + details->setText(txt.c_str()); + } + + const int numLines = details->getNumTextLines(); + auto actualFont = Font::get(details->getFont()); + int height = 0; + if ( actualFont ) + { + height = std::max(24, 24 + (numLines - 1) * actualFont->height(true)); + } + + SDL_Rect pos = details->getSize(); + pos.h = height; + details->setSize(pos); + + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(compendiumPageRightInnerHeight, pos.y + pos.h); + page_right->setActualSize(actualPos); } } + } } @@ -33441,17 +33992,22 @@ namespace MainMenu { return; } auto& entry = CompendiumEntries.monsters[name]; - compendiumEntityCurrent.first = name; if ( compendiumMonsterOverride ) { - compendiumEntityCurrent.second = ""; + Compendium_t::compendiumEntityCurrent.set( + name, + "" + ); } else { - compendiumEntityCurrent.second = entry.models.empty() ? "" : entry.models[compendiumRNG % entry.models.size()]; + Compendium_t::compendiumEntityCurrent.set( + name, + entry.models.empty() ? "" : entry.models[Compendium_t::compendiumEntityCurrent.modelRNG % entry.models.size()] + ); } - refreshCompendiumCamera(compendiumEntityCurrent.second); + refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); if ( true /*compendiumMonster */ ) { @@ -33832,7 +34388,10 @@ namespace MainMenu { } content = entry.second; - compendiumRNG = local_rng.rand(); + if ( Compendium_t::compendiumEntityCurrent.modelRNG == 0 ) + { + Compendium_t::compendiumEntityCurrent.modelRNG = local_rng.rand(); + } } if ( compendium_current == "codex" ) @@ -33889,6 +34448,7 @@ namespace MainMenu { if ( entries ) { + int indexToSelect = 0; for ( int i = 0; i < entries->size(); ++i ) { auto& data = (*entries)[i]; @@ -33901,8 +34461,16 @@ namespace MainMenu { entry->text = data.first; entry->clickable = true; memcpy(&entry->data, &i, sizeof(i)); + + if ( compendium_contents_current != "" ) + { + if ( compendium_contents_current == data.first ) + { + indexToSelect = i; + } + } } - contents->setSelection(0); + contents->setSelection(indexToSelect); contents->scrollToSelection(true); contents->activateSelection(); } @@ -33941,6 +34509,13 @@ namespace MainMenu { heading->setVJustify(Field::justify_t::TOP); heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); heading->setColor(makeColor(198, 190, 179, 255)); + heading->setTickCallback([](Widget& widget) { + if ( ticks % TICKS_PER_SECOND == 0 && compendiumRecordsProcessedOnTick != ticks ) + { + compendiumRecordsSectionRandSequence++; + compendiumRecordsProcessedOnTick = ticks; + } + }); const int recordSpacing = 20; const int recordTextOffsetW = 36; @@ -33959,6 +34534,15 @@ namespace MainMenu { record1val->setVJustify(Field::justify_t::TOP); record1val->setSize(record1->getSize()); record1val->setColor(makeColor(159, 145, 127, 255)); + record1val->setTickCallback([](Widget& widget) { + size_t line = 0; + if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + { + Field* txt = static_cast(&widget); + size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); + txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + } + }); auto record2 = page_right_overlay->addField("record 2 txt", 128); record2->setFont(smallfont_outline); @@ -33975,6 +34559,15 @@ namespace MainMenu { record2val->setVJustify(Field::justify_t::TOP); record2val->setSize(record2->getSize()); record2val->setColor(makeColor(159, 145, 127, 255)); + record2val->setTickCallback([](Widget& widget) { + size_t line = 1; + if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + { + Field* txt = static_cast(&widget); + size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); + txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + } + }); auto record3 = page_right_overlay->addField("record 3 txt", 128); record3->setFont(smallfont_outline); @@ -33991,6 +34584,15 @@ namespace MainMenu { record3val->setVJustify(Field::justify_t::TOP); record3val->setSize(record3->getSize()); record3val->setColor(makeColor(159, 145, 127, 255)); + record3val->setTickCallback([](Widget& widget) { + size_t line = 2; + if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + { + Field* txt = static_cast(&widget); + size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); + txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + } + }); auto record4 = page_right_overlay->addField("record 4 txt", 128); record4->setFont(smallfont_outline); @@ -34007,6 +34609,15 @@ namespace MainMenu { record4val->setVJustify(Field::justify_t::TOP); record4val->setSize(record4->getSize()); record4val->setColor(makeColor(159, 145, 127, 255)); + record4val->setTickCallback([](Widget& widget) { + size_t line = 3; + if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + { + Field* txt = static_cast(&widget); + size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); + txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + } + }); } } @@ -34426,6 +35037,19 @@ namespace MainMenu { window->setColor(0); window->setBorder(0); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + auto& itemTooltipDisplay = players[i]->inventoryUI.compendiumItemTooltipDisplay; + itemTooltipDisplay.expanded = false; + itemTooltipDisplay.expandSetpoint = 0; + itemTooltipDisplay.expandCurrent = 0; + itemTooltipDisplay.expandAnimate = 0; + itemTooltipDisplay.frameTooltipScrollAnim = 0.0; + itemTooltipDisplay.frameTooltipScrollSetpoint = 0.0; + itemTooltipDisplay.frameTooltipScrollPrevSetpoint = 0.0; + itemTooltipDisplay.scrolledToMax = 0; + } + auto background = window->addImage( SDL_Rect{ *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, @@ -34819,15 +35443,15 @@ namespace MainMenu { model_viewer->setDrawCallback([](const Widget& widget, SDL_Rect pos) { if ( compendium_current == "monsters" ) { - drawObjectPreview(compendiumEntityCurrent.second, compendiumMonster, pos, 0.0); + drawObjectPreview(Compendium_t::compendiumEntityCurrent.modelName, compendiumMonster, pos, 0.0); } else if ( compendium_current == "world" ) { - drawObjectPreview(compendiumEntityCurrent.second, nullptr, pos, 0.0); + drawObjectPreview(Compendium_t::compendiumEntityCurrent.modelName, nullptr, pos, 0.0); } else if ( compendium_current == "codex" ) { - drawSpritesPreview(compendiumEntityCurrent.first, compendiumEntityCurrent.second, pos, 0.0); + drawSpritesPreview(Compendium_t::compendiumEntityCurrent.contentsName, Compendium_t::compendiumEntityCurrent.modelName, pos, 0.0); } else if ( compendium_current == "items" ) { From a787f9980a925814d4c93b8cf40462d0ddac8bb8 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 19 Jul 2024 18:50:59 +1000 Subject: [PATCH 030/244] * more compendium codex tag handling --- src/mod_tools.cpp | 1151 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 991 insertions(+), 160 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 9213e5203..012c0f3d1 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -21,6 +21,7 @@ See LICENSE for details. #include "ui/MainMenu.hpp" #include "shops.hpp" #include "interface/ui.hpp" +#include "ui/GameUI.hpp" #endif #include "init.hpp" #include "ui/LoadingScreen.hpp" @@ -10645,6 +10646,7 @@ void jsonVecToVec(rapidjson::Value& val, std::vector& vec) #ifndef EDITOR Compendium_t CompendiumEntries; Item Compendium_t::compendiumItem; +Compendium_t::CompendiumEntityCurrent Compendium_t::compendiumEntityCurrent; Entity Compendium_t::compendiumItemModel(-1, 0, nullptr, nullptr); bool Compendium_t::tooltipNeedUpdate = false; SDL_Rect Compendium_t::tooltipPos; @@ -10895,13 +10897,19 @@ void Compendium_t::readMagicFromFile() std::unordered_set ignoredSpells; std::unordered_set ignoredSpellbooks; - for ( auto itr = d["exclude_spells"].Begin(); itr != d["exclude_spells"].End(); ++itr ) + if ( d.HasMember("exclude_spells") ) { - ignoredSpells.insert(itr->GetString()); + for ( auto itr = d["exclude_spells"].Begin(); itr != d["exclude_spells"].End(); ++itr ) + { + ignoredSpells.insert(itr->GetString()); + } } - for ( auto itr = d["exclude_spellbooks"].Begin(); itr != d["exclude_spellbooks"].End(); ++itr ) + if ( d.HasMember("exclude_spellbooks") ) { - ignoredSpellbooks.insert(itr->GetString()); + for ( auto itr = d["exclude_spellbooks"].Begin(); itr != d["exclude_spellbooks"].End(); ++itr ) + { + ignoredSpellbooks.insert(itr->GetString()); + } } for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) @@ -10911,6 +10919,8 @@ void Compendium_t::readMagicFromFile() auto& obj = magic[name]; jsonVecToVec(w["blurb"], obj.blurb); + + std::set objSpellsLookup; for ( auto itr = w["items"].Begin(); itr != w["items"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -10922,115 +10932,142 @@ void Compendium_t::readMagicFromFile() { item.rotation = itr2->value["rotation"].GetInt(); } - obj.items_in_category.push_back(item); - } - } - - std::list spellsToSort; - bool spells = name.find("spells") != std::string::npos; - bool spellbooks = name.find("spellbooks") != std::string::npos; - if ( spells || spellbooks ) - { - for ( auto spell : allGameSpells ) - { - if ( spells && ignoredSpells.find(spell->spell_internal_name) != ignoredSpells.end() ) - { - continue; - } - if ( spellbooks ) + if ( item.name.find("spell_") != std::string::npos ) { - int book = getSpellbookFromSpellID(spell->ID); - if ( book >= WOODEN_SHIELD && book < NUMITEMS && ::items[book].category == SPELLBOOK ) + for ( auto spell : allGameSpells ) { - if ( ignoredSpellbooks.find(itemNameStrings[book + 2]) != ignoredSpellbooks.end() ) + if ( item.name == spell->spell_internal_name ) { - continue; + item.spellID = spell->ID; + objSpellsLookup.insert(item.name); + break; } } - else - { - continue; - } - } - - if ( name.find("damage") != std::string::npos ) - { - if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) - == ItemTooltips.spellItems[spell->ID].spellTags.end() ) - { - continue; - } } - else if ( name.find("status") != std::string::npos ) + else if ( item.name.find("spellbook_") != std::string::npos ) { - if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) - == ItemTooltips.spellItems[spell->ID].spellTags.end() ) + for ( auto spell : allGameSpells ) { - continue; - } - } - else if ( name.find("utility") != std::string::npos ) - { - if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) - != ItemTooltips.spellItems[spell->ID].spellTags.end() - || ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) - != ItemTooltips.spellItems[spell->ID].spellTags.end() ) - { - continue; + int book = getSpellbookFromSpellID(spell->ID); + if ( book >= WOODEN_SHIELD && book < NUMITEMS && ::items[book].category == SPELLBOOK ) + { + if ( item.name == itemNameStrings[book + 2] ) + { + item.spellID = spell->ID; + break; + } + } } } - else - { - continue; - } - spellsToSort.push_back(spell); + obj.items_in_category.push_back(item); } + } - if ( spellbooks ) - { - spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { - const int bookLeft = getSpellbookFromSpellID(lhs->ID); - const int bookRight = getSpellbookFromSpellID(rhs->ID); + //std::list spellsToSort; + //bool spells = name.find("spells") != std::string::npos; + //bool spellbooks = name.find("spellbooks") != std::string::npos; + //if ( spells || spellbooks ) + //{ + // for ( auto spell : allGameSpells ) + // { + // if ( spells && ignoredSpells.find(spell->spell_internal_name) != ignoredSpells.end() ) + // { + // continue; + // } + // if ( spellbooks ) + // { + // int book = getSpellbookFromSpellID(spell->ID); + // if ( book >= WOODEN_SHIELD && book < NUMITEMS && ::items[book].category == SPELLBOOK ) + // { + // if ( ignoredSpellbooks.find(itemNameStrings[book + 2]) != ignoredSpellbooks.end() ) + // { + // continue; + // } + // } + // else + // { + // continue; + // } + // } - const int bookLevelLeft = ::items[bookLeft].level >= 0 ? ::items[bookLeft].level : 10000; // -1 level sorted to the end - const int bookLevelRight = ::items[bookRight].level >= 0 ? ::items[bookRight].level : 10000; + // if ( name.find("damage") != std::string::npos ) + // { + // if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + // == ItemTooltips.spellItems[spell->ID].spellTags.end() ) + // { + // continue; + // } + // } + // else if ( name.find("status") != std::string::npos ) + // { + // if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) + // == ItemTooltips.spellItems[spell->ID].spellTags.end() ) + // { + // continue; + // } + // } + // else if ( name.find("utility") != std::string::npos ) + // { + // if ( ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + // != ItemTooltips.spellItems[spell->ID].spellTags.end() + // || ItemTooltips.spellItems[spell->ID].spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_STATUS_EFFECT) + // != ItemTooltips.spellItems[spell->ID].spellTags.end() ) + // { + // continue; + // } + // } + // else + // { + // continue; + // } + // spellsToSort.push_back(spell); + // } - if ( bookLevelLeft < bookLevelRight ) { return true; } - if ( bookLevelLeft > bookLevelRight ) { return false; } - if ( rhs->difficulty > lhs->difficulty ) { return true; } - if ( rhs->difficulty < lhs->difficulty ) { return false; } + // if ( spellbooks ) + // { + // spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { + // const int bookLeft = getSpellbookFromSpellID(lhs->ID); + // const int bookRight = getSpellbookFromSpellID(rhs->ID); - return rhs->ID < lhs->ID; - }); - } - else - { - spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { - if ( rhs->difficulty > lhs->difficulty ) { return true; } - if ( rhs->difficulty < lhs->difficulty ) { return false; } - - return rhs->ID < lhs->ID; - }); - } + // const int bookLevelLeft = ::items[bookLeft].level >= 0 ? ::items[bookLeft].level : 10000; // -1 level sorted to the end + // const int bookLevelRight = ::items[bookRight].level >= 0 ? ::items[bookRight].level : 10000; - for ( auto spell : spellsToSort ) - { - CompendiumItems_t::Codex_t::CodexItem_t item; - item.rotation = 0; - if ( spellbooks ) - { - int book = getSpellbookFromSpellID(spell->ID); - item.name = itemNameStrings[book + 2]; - item.rotation = 180; - } - else - { - item.name = spell->spell_internal_name; - } - item.spellID = spell->ID; - obj.items_in_category.push_back(item); - } - } + // if ( bookLevelLeft < bookLevelRight ) { return true; } + // if ( bookLevelLeft > bookLevelRight ) { return false; } + // if ( rhs->difficulty > lhs->difficulty ) { return true; } + // if ( rhs->difficulty < lhs->difficulty ) { return false; } + + // return rhs->ID < lhs->ID; + // }); + // } + // else + // { + // spellsToSort.sort([](const spell_t* lhs, const spell_t* rhs) { + // if ( rhs->difficulty > lhs->difficulty ) { return true; } + // if ( rhs->difficulty < lhs->difficulty ) { return false; } + // + // return rhs->ID < lhs->ID; + // }); + // } + + // for ( auto spell : spellsToSort ) + // { + // CompendiumItems_t::Codex_t::CodexItem_t item; + // item.rotation = 0; + // if ( spellbooks ) + // { + // int book = getSpellbookFromSpellID(spell->ID); + // item.name = itemNameStrings[book + 2]; + // item.rotation = 180; + // } + // else + // { + // item.name = spell->spell_internal_name; + // } + // item.spellID = spell->ID; + // obj.items_in_category.push_back(item); + // } + //} if ( w.HasMember("events") ) { @@ -11041,7 +11078,8 @@ void Compendium_t::readMagicFromFile() for ( auto& item : obj.items_in_category ) { - const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { if ( itemType != SPELL_ITEM && ::items[itemType].item_slot != NO_EQUIP ) @@ -11063,7 +11101,8 @@ void Compendium_t::readMagicFromFile() { for ( auto& item : obj.items_in_category ) { - const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType == SPELL_ITEM ) { Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); @@ -11089,7 +11128,8 @@ void Compendium_t::readMagicFromFile() { for ( auto& item : obj.items_in_category ) { - const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType == SPELL_ITEM ) { Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); @@ -11118,7 +11158,8 @@ void Compendium_t::readMagicFromFile() { for ( auto& item : obj.items_in_category ) { - const int itemType = spells ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; if ( itemType == SPELL_ITEM ) { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventSpellOffset + item.spellID]; @@ -11165,7 +11206,7 @@ void Compendium_t::readCodexFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11230,6 +11271,9 @@ void Compendium_t::readCodexFromFile() } } } + + Compendium_t::Events_t::itemDisplayedEventsList.erase(Compendium_t::Events_t::kEventCodexOffset + obj.id); + Compendium_t::Events_t::itemDisplayedCustomEventsList.erase(Compendium_t::Events_t::kEventCodexOffset + obj.id); if ( w.HasMember("events_display") ) { for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) @@ -11243,7 +11287,7 @@ void Compendium_t::readCodexFromFile() { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventCodexOffset + obj.id]; if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) - == vec.end() ) + == vec.end() || find2->second.id == EventTags::CPDM_CUSTOM_TAG ) { vec.push_back((Compendium_t::EventTags)find2->second.id); } @@ -11251,6 +11295,37 @@ void Compendium_t::readCodexFromFile() } } } + if ( w.HasMember("custom_events_display") ) + { + std::vector customEvents; + for ( auto itr = w["custom_events_display"].Begin(); itr != w["custom_events_display"].End(); ++itr ) + { + customEvents.push_back(itr->GetString()); + } + + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventCodexOffset + obj.id]; + int index = -1; + for ( auto& v : vec ) + { + ++index; + auto& vec2 = Compendium_t::Events_t::itemDisplayedCustomEventsList[Compendium_t::Events_t::kEventCodexOffset + obj.id]; + if ( v == EventTags::CPDM_CUSTOM_TAG ) + { + if ( index < customEvents.size() ) + { + vec2.push_back(customEvents[index]); + } + else + { + vec2.push_back(""); + } + } + else + { + vec2.push_back(""); + } + } + } } } @@ -11465,7 +11540,7 @@ void Compendium_t::readMonstersFromFile() } } - if ( monsterType == NOTHING ) { continue; } + if ( monsterType == NOTHING && name != "ghost" ) { continue; } auto& m = itr->value; auto& monster = monsters[itr->name.GetString()]; @@ -11512,9 +11587,12 @@ std::map Compendium_t::Events_t::eventWorldIDLookup; std::map Compendium_t::Events_t::eventCodexIDLookup; std::map> Compendium_t::Events_t::eventClassIds; std::map> Compendium_t::Events_t::itemDisplayedEventsList; +std::map> Compendium_t::Events_t::itemDisplayedCustomEventsList; +std::map Compendium_t::Events_t::customEventsValues; std::map> Compendium_t::Events_t::playerEvents; std::map> Compendium_t::Events_t::serverPlayerEvents[MAXPLAYERS]; std::map> Compendium_t::Events_t::eventLangEntries; +std::map> Compendium_t::Events_t::eventCustomLangEntries; void Compendium_t::Events_t::readEventsTranslations() { @@ -11536,7 +11614,7 @@ void Compendium_t::Events_t::readEventsTranslations() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11551,6 +11629,7 @@ void Compendium_t::Events_t::readEventsTranslations() } eventLangEntries.clear(); + eventCustomLangEntries.clear(); for ( auto itr = d["tags"].Begin(); itr != d["tags"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -11567,6 +11646,597 @@ void Compendium_t::Events_t::readEventsTranslations() } } } + if ( d.HasMember("custom_tags") ) + { + for ( auto itr = d["custom_tags"].MemberBegin(); itr != d["custom_tags"].MemberEnd(); ++itr ) + { + for ( auto itr2 = itr->value.MemberBegin(); itr2 != itr->value.MemberEnd(); ++itr2 ) + { + eventCustomLangEntries[itr->name.GetString()][itr2->name.GetString()] = itr2->value.GetString(); + } + } + } +} + +std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const char* formatType, int formatVal, std::map& langMap) +{ + std::string resultsFormatting = "%d"; + if ( langMap.find("format") != langMap.end() ) + { + resultsFormatting = langMap["format"]; + } + else + { + return std::to_string(value); + } + + std::string output = ""; + for ( size_t c = 0; c < resultsFormatting.size(); ++c ) + { + if ( resultsFormatting[c] == '%' && ((c + 1) < resultsFormatting.size()) ) + { + if ( resultsFormatting[c + 1] == 'm' ) + { + // dist to meters + float meters = value / (8.f); + char buf[32]; + snprintf(buf, sizeof(buf), "%.1f", meters); + output += buf; + } + else if ( resultsFormatting[c + 1] == 't' ) + { + // ticks to seconds + float seconds = value / (50.f); + char buf[32]; + snprintf(buf, sizeof(buf), "%.1f", seconds); + output += buf; + } + else if ( resultsFormatting[c + 1] == 'd' ) + { + output += std::to_string(value); + } + else if ( resultsFormatting[c + 1] == 'h' ) + { + real_t regen = value; + if ( regen > 0.01 ) + { + real_t nominalRegen = HEAL_TIME; + regen = nominalRegen / regen; + char buf[32]; + snprintf(buf, sizeof(buf), "%.f", regen * 100.0); + output += buf; + } + } + else if ( resultsFormatting[c + 1] == 'e' ) + { + real_t regen = value; + if ( regen > 0.01 ) + { + real_t nominalRegen = MAGIC_REGEN_TIME; + regen = nominalRegen / regen; + char buf[32]; + snprintf(buf, sizeof(buf), "%.f", regen * 100.0); + output += buf; + } + } + else if ( resultsFormatting[c + 1] == '%' ) + { + output += '%'; + } + else if ( resultsFormatting[c + 1] == 's' ) + { + if ( formatType ) + { + if ( !strcmp(formatType, "stats") ) + { + switch ( formatVal ) + { + case STAT_STR: + output += ItemTooltips.getItemStatShortName("STR"); + break; + case STAT_DEX: + output += ItemTooltips.getItemStatShortName("DEX"); + break; + case STAT_CON: + output += ItemTooltips.getItemStatShortName("CON"); + break; + case STAT_INT: + output += ItemTooltips.getItemStatShortName("INT"); + break; + case STAT_PER: + output += ItemTooltips.getItemStatShortName("PER"); + break; + case STAT_CHR: + output += ItemTooltips.getItemStatShortName("CHR"); + break; + } + } + else if ( !strcmp(formatType, "class") ) + { + std::string tmp = playerClassLangEntry(formatVal, 0); + camelCaseString(tmp); + output += tmp; + } + else if ( !strcmp(formatType, "race") ) + { + std::string tmp = getMonsterLocalizedName(getMonsterFromPlayerRace(formatVal)); + camelCaseString(tmp); + output += tmp; + } + else if ( !strcmp(formatType, "skills") ) + { + std::string tmp = getSkillLangEntry(formatVal); + camelCaseString(tmp); + output += tmp; + } + } + } + ++c; + } + else + { + output += resultsFormatting[c]; + } + } + return output; +} + +std::vector> Compendium_t::Events_t::getCustomEventValue(std::string key, int specificClass) +{ + std::vector> results; + if ( customEventsValues.find(key) == customEventsValues.end() ) + { + return results; + } + + rapidjson::Document d; + d.Parse(customEventsValues[key].c_str()); + if ( !d.IsObject() ) + { + return results; + } + + if ( d.HasMember("value") ) + { + Events_t::Type type = MAX; + int minValue = 0; + int maxValue = 0; + bool firstResult = true; + std::string valueType = ""; + if ( d["value"].HasMember("type") ) + { + valueType = d["value"]["type"].GetString(); + if ( valueType == "max" ) + { + type = MAX; + } + else if ( valueType == "min" ) + { + type = MIN; + } + else if ( valueType == "sum" ) + { + type = SUM; + } + else if ( valueType == "class_sum" ) + { + type = SUM; + } + } + + if ( valueType != "class_sum" ) + { + specificClass = -1; + } + + std::map mapValueTotals; + std::string formatType = ""; + if ( d["value"].HasMember("format") ) + { + formatType = d["value"]["format"].GetString(); + } + if ( d["value"].HasMember("tags") ) + { + for ( auto itr = d["value"]["tags"].Begin(); itr != d["value"]["tags"].End(); ++itr ) + { + std::string name = (*itr)["name"].GetString(); + std::string cat = (*itr)["category"].GetString(); + + if ( valueType == "sum_items" ) + { + auto findTag = eventIdLookup.find(name); + if ( findTag != eventIdLookup.end() ) + { + auto& playerTags = playerEvents[findTag->second]; + for ( auto itemId : eventItemLookup[findTag->second] ) + { + if ( cat == "spells" ) + { + if ( itemId >= kEventSpellOffset ) + { + int spellID = itemId - kEventSpellOffset; + auto findVal = playerTags.find(itemId); + if ( findVal != playerTags.end() ) + { + mapValueTotals[itemId] += findVal->second.value; + } + } + } + else if ( cat == "spellbooks" ) + { + if ( itemId >= WOODEN_SHIELD && itemId < NUMITEMS && ::items[itemId].category == SPELLBOOK ) + { + auto findVal = playerTags.find(itemId); + if ( findVal != playerTags.end() ) + { + mapValueTotals[itemId] += findVal->second.value; + } + } + } + else if ( cat == "equipment" ) + { + if ( itemId >= WOODEN_SHIELD && itemId < NUMITEMS && ::items[itemId].item_slot != NO_EQUIP ) + { + auto findVal = playerTags.find(itemId); + if ( findVal != playerTags.end() ) + { + mapValueTotals[itemId] += findVal->second.value; + } + } + } + else if ( cat == "armor" ) + { + if ( itemId >= WOODEN_SHIELD && itemId < NUMITEMS && + ::items[itemId].item_slot != NO_EQUIP + && ::items[itemId].item_slot != EQUIPPABLE_IN_SLOT_WEAPON ) + { + auto findVal = playerTags.find(itemId); + if ( findVal != playerTags.end() ) + { + mapValueTotals[itemId] += findVal->second.value; + } + } + } + else if ( cat == "weapons" ) + { + if ( itemId >= WOODEN_SHIELD && itemId < NUMITEMS + && ::items[itemId].item_slot == EQUIPPABLE_IN_SLOT_WEAPON ) + { + auto findVal = playerTags.find(itemId); + if ( findVal != playerTags.end() ) + { + mapValueTotals[itemId] += findVal->second.value; + } + } + } + } + } + continue; + } + + auto findCat = eventCodexIDLookup.find(cat); + if ( findCat != eventCodexIDLookup.end() ) + { + auto findTag = eventIdLookup.find(name); + if ( findTag != eventIdLookup.end() ) + { + auto tag = findTag->second; + if ( playerEvents.find(tag) == playerEvents.end() ) + { + if ( valueType == "list" ) + { + results.push_back(std::make_pair("-", 0)); + } + continue; + } + auto& playerTags = playerEvents[tag]; + std::vector> codexIDs; + codexIDs.push_back(std::make_pair(-1, findCat->second)); + + if ( eventCodexLookup[tag].find(cat) != eventCodexLookup[tag].end() ) + { + auto& def = events[tag]; + if ( def.attributes.find("stats") != def.attributes.end() && valueType != "max_class" ) + { + if ( cat == "str" ) { codexIDs.back().first = STAT_STR; } + if ( cat == "dex" ) { codexIDs.back().first = STAT_DEX; } + if ( cat == "con" ) { codexIDs.back().first = STAT_CON; } + if ( cat == "int" ) { codexIDs.back().first = STAT_INT; } + if ( cat == "per" ) { codexIDs.back().first = STAT_PER; } + if ( cat == "chr" ) { codexIDs.back().first = STAT_CHR; } + } + else if ( def.attributes.find("class") != def.attributes.end() ) + { + codexIDs.clear(); + auto findClassTag = eventClassIds.find(tag); + if ( findClassTag != eventClassIds.end() ) + { + // iterate through classes + int startOffsetId = -1; + if ( def.attributes.find("skills") != def.attributes.end() ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( cat == getSkillStringForCompendium(i) ) + { + startOffsetId = findClassTag->second[0] + i * kEventClassesMax; + break; + } + } + } + + for ( auto classId : findClassTag->second ) + { + if ( startOffsetId >= 0 ) + { + if ( classId.second >= startOffsetId && classId.second < startOffsetId + kEventClassesMax ) + { + if ( specificClass >= 0 ) + { + if ( classId.first != specificClass ) + { + continue; + } + } + codexIDs.push_back(classId); + codexIDs.back().first = codexIDs.back().first % kEventClassesMax; + } + } + else + { + if ( specificClass >= 0 ) + { + if ( classId.first != specificClass ) + { + continue; + } + } + + codexIDs.push_back(classId); + if ( def.attributes.find("stats") != def.attributes.end() && valueType == "max_class" ) + { + // we want to store the stat names rather than the class + if ( cat == "str" ) { codexIDs.back().first = STAT_STR; } + if ( cat == "dex" ) { codexIDs.back().first = STAT_DEX; } + if ( cat == "con" ) { codexIDs.back().first = STAT_CON; } + if ( cat == "int" ) { codexIDs.back().first = STAT_INT; } + if ( cat == "per" ) { codexIDs.back().first = STAT_PER; } + if ( cat == "chr" ) { codexIDs.back().first = STAT_CHR; } + } + } + } + } + } + else if ( def.attributes.find("race") != def.attributes.end() ) + { + codexIDs.clear(); + auto findClassTag = eventClassIds.find(tag); + if ( findClassTag != eventClassIds.end() ) + { + // iterate through classes + for ( auto classId : findClassTag->second ) + { + codexIDs.push_back(classId); + } + } + } + + for ( auto& pair : codexIDs ) + { + int codexID = pair.second; + int classnum = pair.first; + + if ( formatType == "skills" ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( cat == getSkillStringForCompendium(i) ) + { + classnum = i; + break; + } + } + } + + if ( codexID < kEventCodexOffset ) + { + codexID += kEventCodexOffset; // convert to offset + } + + auto findVal = playerTags.find(codexID); + int val = 0; + if ( findVal != playerTags.end() ) + { + val = findVal->second.value; + } + + std::string output = ""; + int numFormats = 0; + if ( valueType == "sum_category_max" || valueType == "sum_category_min" ) + { + int categoryValue = findCat->second; + if ( formatType == "skills" ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( cat == getSkillStringForCompendium(i) ) + { + categoryValue = i; + break; + } + } + } + mapValueTotals[categoryValue] += val; + } + else if ( valueType == "class_max_total" ) + { + mapValueTotals[classnum] += val; + continue; + } + else if ( formatType == "skills" ) + { + output = formatEventRecordText(val, d["value"]["format"].GetString(), classnum, eventCustomLangEntries[key]); + } + else if ( def.attributes.find("stats") != def.attributes.end() ) + { + output = formatEventRecordText(val, "stats", classnum, eventCustomLangEntries[key]); + } + else if ( def.attributes.find("class") != def.attributes.end() ) + { + output = formatEventRecordText(val, "class", classnum, eventCustomLangEntries[key]); + } + else if ( def.attributes.find("race") != def.attributes.end() ) + { + output = formatEventRecordText(val, "race", classnum, eventCustomLangEntries[key]); + } + + results.push_back(std::make_pair(output, val)); + maxValue = std::max(maxValue, val); + if ( firstResult ) + { + minValue = val; + } + else + { + minValue = std::min(minValue, val); + } + firstResult = false; + } + } + } + } + } + } + + Sint32 sum = 0; + if ( valueType == "sum_items" ) + { + results.clear(); + for ( auto& pair : mapValueTotals ) + { + sum += pair.second; + } + std::string output = formatEventRecordText(sum, formatType.c_str(), 0, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, maxValue)); + return results; + } + else if ( valueType == "sum_category_max" ) + { + results.clear(); + for ( auto& pair : mapValueTotals ) + { + maxValue = std::max(maxValue, pair.second); + } + for ( auto& pair : mapValueTotals ) + { + if ( pair.second == maxValue ) + { + std::string output = formatEventRecordText(maxValue, formatType.c_str(), pair.first, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, maxValue)); + } + } + return results; + } + else if ( valueType == "sum_category_min" ) + { + results.clear(); + firstResult = true; + minValue = 0; + for ( auto& pair : mapValueTotals ) + { + if ( firstResult ) + { + minValue = pair.second; + } + else + { + minValue = std::min(minValue, pair.second); + } + firstResult = false; + } + for ( auto& pair : mapValueTotals ) + { + if ( pair.second == minValue ) + { + std::string output = formatEventRecordText(minValue, formatType.c_str(), pair.first, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, minValue)); + } + } + return results; + } + else if ( valueType == "class_max_total" ) + { + maxValue = 0; + results.clear(); + for ( auto& pair : mapValueTotals ) + { + maxValue = std::max(maxValue, pair.second); + } + for ( auto& pair : mapValueTotals ) + { + if ( pair.second == maxValue ) + { + std::string output = formatEventRecordText(maxValue, formatType.c_str(), pair.first, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, maxValue)); + } + } + return results; + } + else if ( valueType == "list" ) + { + if ( eventCustomLangEntries[key].find("format") != eventCustomLangEntries[key].end() ) + { + if ( results.size() >= 1 ) + { + char buf[128] = ""; + snprintf(buf, sizeof(buf), eventCustomLangEntries[key]["format"].c_str(), + results[0].first != "-" ? std::to_string(results[0].second).c_str() : "-", + results[1].first != "-" ? std::to_string(results[1].second).c_str() : "-"); + results.clear(); + results.push_back(std::make_pair(buf, 0)); + } + else + { + char buf[128] = ""; + snprintf(buf, sizeof(buf), eventCustomLangEntries[key]["format"].c_str(), + "-", + "-"); + results.clear(); + results.push_back(std::make_pair(buf, 0)); + } + return results; + } + } + else + { + for ( auto itr = results.begin(); itr != results.end(); ) + { + if ( type == MAX && itr->second != maxValue ) + { + itr = results.erase(itr); + } + else if ( type == MIN && itr->second != minValue ) + { + itr = results.erase(itr); + } + else + { + if ( type == SUM ) + { + sum += itr->second; + } + ++itr; + } + } + } + if ( type == SUM && results.size() > 0 ) + { + results.clear(); + results.push_back(std::make_pair(formatEventRecordText(sum, nullptr, 0, eventCustomLangEntries[key]), sum)); + } + } + + return results; } void Compendium_t::Events_t::readEventsFromFile() @@ -11589,7 +12259,7 @@ void Compendium_t::Events_t::readEventsFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11606,6 +12276,7 @@ void Compendium_t::Events_t::readEventsFromFile() events.clear(); eventIdLookup.clear(); eventClassIds.clear(); + int classIdIndex = kEventCodexClassOffset; int index = -1; for ( auto itr = d["tags"].Begin(); itr != d["tags"].End(); ++itr ) { @@ -11678,14 +12349,44 @@ void Compendium_t::Events_t::readEventsFromFile() } if ( entry.attributes.find("class") != entry.attributes.end() || entry.attributes.find("race") != entry.attributes.end() ) { - size_t idStart = kEventCodexClassOffset + eventClassIds.size() * kEventClassesMax; - for ( int i = 0; i <= CLASS_HUNTER; ++i ) + if ( entry.attributes.find("skills") != entry.attributes.end() ) { - eventClassIds[id][i] = (idStart + i); + for ( int skillnum = 0; skillnum < 16; ++skillnum ) + { + for ( int i = 0; i <= CLASS_HUNTER; ++i ) + { + int index = i + skillnum * kEventClassesMax; + eventClassIds[id][index] = (classIdIndex + index); + } + } + classIdIndex += kEventClassesMax * 16; + } + else + { + for ( int i = 0; i <= CLASS_HUNTER; ++i ) + { + eventClassIds[id][i] = (classIdIndex + i); + } + classIdIndex += kEventClassesMax; } } } } + + if ( d.HasMember("custom_tags") ) + { + for ( auto itr = d["custom_tags"].MemberBegin(); itr != d["custom_tags"].MemberEnd(); ++itr ) + { + eventIdLookup[itr->name.GetString()] = EventTags::CPDM_CUSTOM_TAG; + + rapidjson::StringBuffer os; + os.Clear(); + rapidjson::Writer writer(os); + itr->value.Accept(writer); + + customEventsValues[itr->name.GetString()] = os.GetString(); + } + } } void Compendium_t::Events_t::loadItemsSaveData() @@ -11809,6 +12510,29 @@ void Compendium_t::Events_t::createDummyClientData(const int playernum) eventUpdateWorld(playernum, pair.first, world.c_str(), 1); } } + for ( auto& pair : eventCodexLookup ) + { + if ( eventClassIds.find(pair.first) != eventClassIds.end() ) + { + int oldclass = client_classes[playernum]; + for ( int c = 0; c < NUMCLASSES; ++c ) + { + client_classes[playernum] = c; + for ( auto world : pair.second ) + { + eventUpdateCodex(playernum, pair.first, world.c_str(), 1); + } + } + client_classes[playernum] = oldclass; + } + else + { + for ( auto world : pair.second ) + { + eventUpdateCodex(playernum, pair.first, world.c_str(), 1); + } + } + } } void Compendium_t::Events_t::writeItemsSaveData() @@ -11902,6 +12626,10 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) value |= val; return true; } + else + { + return false; + } } void onCompendiumLevelExit(const int playernum, const char* level, const bool enteringLvl) @@ -11925,34 +12653,142 @@ void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) return; } - auto entity = players[playernum]->entity; - auto myStats = stats[playernum]; + if ( ticks % TICKS_PER_SECOND == 25 ) { - real_t resistance = 100.0 * Entity::getDamageTableMultiplier(entity, *myStats, DAMAGE_TABLE_MAGIC); - resistance /= (Entity::getMagicResistance(myStats) + 1); - resistance = -(resistance - 100.0); - eventUpdateCodex(playernum, CPDM_RES_MAX, "res", (int)resistance); - eventUpdateCodex(playernum, CPDM_CLASS_RES_MAX, "res", (int)resistance); - } + auto entity = players[playernum]->entity; + auto myStats = stats[playernum]; + { + real_t resistance = 100.0 * Entity::getDamageTableMultiplier(entity, *myStats, DAMAGE_TABLE_MAGIC); + resistance /= (Entity::getMagicResistance(myStats) + 1); + resistance = -(resistance - 100.0); + eventUpdateCodex(playernum, CPDM_RES_MAX, "res", (int)resistance); + eventUpdateCodex(playernum, CPDM_CLASS_RES_MAX, "res", (int)resistance); + } - { - Sint32 ac = AC(myStats); - eventUpdateCodex(playernum, CPDM_AC_MAX, "ac", ac); - eventUpdateCodex(playernum, CPDM_CLASS_AC_MAX, "ac", ac); + { + { + Sint32 value = statGetSTR(myStats, entity); + value -= myStats->STR; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "str", value); + } + } + { + Sint32 value = statGetDEX(myStats, entity); + value -= myStats->DEX; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "dex", value); + } + } + { + Sint32 value = statGetCON(myStats, entity); + value -= myStats->CON; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "con", value); + } + } + { + Sint32 value = statGetINT(myStats, entity); + value -= myStats->INT; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "int", value); + } + } + { + Sint32 value = statGetPER(myStats, entity); + value -= myStats->PER; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "per", value); + } + } + { + Sint32 value = statGetCHR(myStats, entity); + value -= myStats->CHR; + if ( value > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_STAT_MAX, "chr", value); + } + } + } + + { + bool oldDefending = myStats->defending; + myStats->defending = false; - Sint32 con = myStats->CON; - myStats->CON = 0; - ac = AC(myStats); - eventUpdateCodex(playernum, CPDM_AC_MAX_FROM_BLESS, "ac", ac); - myStats->CON = con; + Sint32 ac = AC(myStats); + + Sint32 con = myStats->CON; + myStats->CON = 0; + int numBlessings = 0; + Sint32 acFromArmor = AC(myStats); + + real_t targetACEffectiveness = Entity::getACEffectiveness(entity, myStats, true, nullptr, nullptr, numBlessings); + int effectiveness = targetACEffectiveness * 100.0; + + myStats->CON = con; + myStats->defending = oldDefending; + + eventUpdateCodex(playernum, CPDM_CLASS_AC_MAX, "ac", ac); + eventUpdateCodex(playernum, CPDM_AC_MAX_FROM_BLESS, "ac", numBlessings); + eventUpdateCodex(playernum, CPDM_AC_EFFECTIVENESS_MAX, "ac", effectiveness); + eventUpdateCodex(playernum, CPDM_AC_EQUIPMENT_MAX, "ac", acFromArmor); + } + + { + eventUpdateCodex(playernum, CPDM_HP_MAX, "hp", myStats->MAXHP); + eventUpdateCodex(playernum, CPDM_CLASS_HP_MAX, "hp", myStats->MAXHP); + + eventUpdateCodex(playernum, CPDM_MP_MAX, "mp", myStats->MAXMP); + eventUpdateCodex(playernum, CPDM_CLASS_MP_MAX, "mp", myStats->MAXMP); + } + + { + { + // base PWR INT Bonus + real_t bonus = getSpellBonusFromCasterINT(entity, myStats) * 100.0; + real_t val = bonus; + eventUpdateCodex(playernum, CPDM_CLASS_PWR_MAX, "pwr", (int)val); + } + + // equip/effect bonus (minus INT) + { + real_t val = (getBonusFromCasterOfSpellElement(entity, myStats, nullptr, SPELL_NONE) * 100.0); + // look for damage/healing spell bonus for mitre/magus hat + val = std::max(val, getBonusFromCasterOfSpellElement(entity, myStats, nullptr, SPELL_FIREBALL) * 100.0); + val = std::max(val, getBonusFromCasterOfSpellElement(entity, myStats, nullptr, SPELL_HEALING) * 100.0); + real_t bonus = getSpellBonusFromCasterINT(entity, myStats); + val -= bonus * 100.0; + eventUpdateCodex(playernum, CPDM_PWR_MAX_EQUIP, "pwr", (int)val); + } + } } + if ( ticks % (5 * TICKS_PER_SECOND) == 25 ) { - eventUpdateCodex(playernum, CPDM_HP_MAX, "hp", myStats->MAXHP); - eventUpdateCodex(playernum, CPDM_CLASS_HP_MAX, "hp", myStats->MAXHP); - - eventUpdateCodex(playernum, CPDM_MP_MAX, "mp", myStats->MAXMP); - eventUpdateCodex(playernum, CPDM_CLASS_MP_MAX, "mp", myStats->MAXMP); + int weight = 0; + for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) + { + Item* item = (Item*)node->element; + if ( !item ) + { + continue; + } + if ( itemCategory(item) == SPELL_CAT ) + { + continue; + } + if ( ::items[item->type].item_slot != NO_EQUIP + && itemIsEquipped(item, playernum) ) + { + weight += item->getWeight(); + } + } + Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_WGT_EQUIPPED_MAX, "wgt", weight); } } @@ -11998,6 +12834,13 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto eventUpdateCodex(playernum, Compendium_t::CPDM_RACE_GAMES_WON_CLASSIC, "races", 1); } } + else + { + //if ( stats[playernum]->HP <= 0 ) + //{ + // //Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + //} + } } } } @@ -12287,32 +13130,6 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p sendClientDataOverNet(i); } } - - ////std::set slots; - //int numItems = 0; - //for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) - //{ - // Item* item = (Item*)node->element; - // if ( !item ) - // { - // continue; - // } - // if ( itemCategory(item) == SPELL_CAT ) - // { - // continue; - // } - // numItems += item->count; - // //if ( item->x >= 0 && item->x < players[playernum]->inventoryUI.getSizeX() ) - // //{ - // // if ( item->y >= 0 && item->y < players[playernum]->inventoryUI.getSizeY() ) - // // { - // // //Uint32 key = item->x + 10000 * item->y; - // // //slots.insert(key); - // // } - // //} - //} - //size_t maxSlots = players[playernum]->inventoryUI.getSizeX() * players[playernum]->inventoryUI.getSizeY(); - //Compendium_t::Events_t::eventUpdate(playernum, Compendium_t::CPDM_INVENTORY_QTY_MAX, CLOAK_BACKPACK, numItems); } bool allowedCompendiumProgress() @@ -12856,7 +13673,21 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag auto findClassTag = eventClassIds.find(tag); if ( findClassTag != eventClassIds.end() ) { - auto findClassId = findClassTag->second.find(client_classes[playernum]); + // iterate through classes + int classId = client_classes[playernum]; + if ( def.attributes.find("skills") != def.attributes.end() ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( !strcmp(category, getSkillStringForCompendium(i)) ) + { + classId = client_classes[playernum] + i * kEventClassesMax; + break; + } + } + } + + auto findClassId = findClassTag->second.find(classId); if ( findClassId != findClassTag->second.end() ) { codexID = findClassId->second; @@ -12896,7 +13727,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag return; } - if ( codexID < kEventCodexOffset ) + if ( codexID < kEventCodexOffset || loadingValue ) { codexID += kEventCodexOffset; // convert to offset } @@ -12941,7 +13772,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag } else if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_FLOOR && floorEvent ) { - players[playernum]->compendiumProgress.floorEvents[tag][category][codexID] = value; + players[playernum]->compendiumProgress.floorEvents[tag][category][codexID] += value; return; } From 59eee228dd2ce04ac5403503c51eac4b8868029e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 21 Jul 2024 00:42:52 +1000 Subject: [PATCH 031/244] * fix inventory bug holding space and opening activating items --- src/interface/drawstatus.cpp | 7 +++++++ src/interface/playerinventory.cpp | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/interface/drawstatus.cpp b/src/interface/drawstatus.cpp index 62878ff1a..6a68bed43 100644 --- a/src/interface/drawstatus.cpp +++ b/src/interface/drawstatus.cpp @@ -3273,6 +3273,13 @@ void drawStatusNew(const int player) { bindingPressed = true; + if ( Input::inputs[player].getPlayerControlType() == Input::PLAYER_CONTROLLED_BY_KEYBOARD ) + { + // rare bug causes rogue activations on keyboard controls holding space + opening inventory + // skip this section and consume presses + break; + } + if ( option == ItemContextMenuPrompts::PROMPT_DROP && players[player]->paperDoll.isItemOnDoll(*item) ) { // need to unequip diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index e760700f5..814e026b4 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -7118,6 +7118,13 @@ void Player::Inventory_t::openInventory() bFirstTimeSnapCursor = false; slideOutPercent = 1.0; isInteractable = false; + + // consume tooltip prompt buttons e.g so holding 'A' doesn't activate when opening inventory + auto& input = Input::inputs[player.playernum]; + input.consumeBinaryToggle("MenuConfirm"); + input.consumeBinaryToggle("MenuCancel"); + input.consumeBinaryToggle("MenuAlt1"); + input.consumeBinaryToggle("MenuAlt2"); } player.hotbar.hotbarTooltipLastGameTick = 0; } @@ -8844,6 +8851,13 @@ void Player::Inventory_t::updateInventory() { bindingPressed = true; + if ( Input::inputs[player].getPlayerControlType() == Input::PLAYER_CONTROLLED_BY_KEYBOARD ) + { + // rare bug causes rogue activations on keyboard controls holding space + opening inventory + // skip this section and consume presses + break; + } + if ( option == ItemContextMenuPrompts::PROMPT_DROP && players[player]->paperDoll.isItemOnDoll(*item) ) { // need to unequip @@ -9276,6 +9290,13 @@ void Player::Inventory_t::updateInventory() { bindingPressed = true; + if ( Input::inputs[player].getPlayerControlType() == Input::PLAYER_CONTROLLED_BY_KEYBOARD ) + { + // rare bug causes rogue activations on keyboard controls holding space + opening inventory + // skip this section and consume presses + break; + } + if ( option == ItemContextMenuPrompts::PROMPT_DROP && players[player]->paperDoll.isItemOnDoll(*item) ) { // need to unequip From 856a23490af8f97d7d79ad26f6c9e194d5e9cacb Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 25 Jul 2024 01:09:44 +1000 Subject: [PATCH 032/244] * compendium unlocking mechanics * compendium previous level tracking fix * compendium game timer tracking * fix item icon crash for translations being too long --- src/actgeneral.cpp | 2 + src/actladder.cpp | 6 + src/actplayer.cpp | 2 + src/game.cpp | 18 +- src/init_game.cpp | 2 + src/interface/consolecommand.cpp | 3 + src/menu.cpp | 61 +- src/mod_tools.cpp | 1229 ++++++++++++++++++++++-------- src/mod_tools.hpp | 278 ++++--- src/net.cpp | 6 + src/player.hpp | 2 + 11 files changed, 1176 insertions(+), 433 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 24d17196e..e450ecdff 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -1538,6 +1538,8 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input) if ( result != k_ScriptError ) { loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; skipLevelsOnLoad = result; } } diff --git a/src/actladder.cpp b/src/actladder.cpp index cdc6e01a2..138938264 100644 --- a/src/actladder.cpp +++ b/src/actladder.cpp @@ -105,6 +105,8 @@ void actLadder(Entity* my) messagePlayer(i, MESSAGE_INTERACTION, Language::get(507)); } loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; if (secretlevel) { switch (currentlevel) @@ -330,6 +332,8 @@ void actPortal(Entity* my) messagePlayer(i, MESSAGE_INTERACTION, Language::get(511)); } loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; if ( secretlevel ) { switch ( currentlevel ) @@ -1388,6 +1392,8 @@ void actCustomPortal(Entity* my) messagePlayer(i, MESSAGE_INTERACTION, Language::get(507)); } loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; skipLevelsOnLoad = 0; if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index ec8f0b7f4..c7eb06af6 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -4542,6 +4542,8 @@ void actPlayer(Entity* my) players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; players[PLAYER_NUM]->compendiumProgress.playerAliveTimeMoving = 0; players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped = 0; + players[PLAYER_NUM]->compendiumProgress.playerAliveTimeTotal = 0; + players[PLAYER_NUM]->compendiumProgress.playerGameTimeTotal = 0; Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = my->x; diff --git a/src/game.cpp b/src/game.cpp index 4370aa513..ae1cf46fe 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1988,8 +1988,6 @@ void gameLogic(void) } } - int prevcurrentlevel = currentlevel; - bool prevsecretfloor = secretlevel; std::string prevmapname = map.name; bool loadingTheSameFloorAsCurrent = false; @@ -2491,7 +2489,9 @@ void gameLogic(void) for ( c = 0; c < MAXPLAYERS; c++ ) { - Compendium_t::Events_t::onLevelChangeEvent(c, prevcurrentlevel, prevsecretfloor, prevmapname); + Compendium_t::Events_t::onLevelChangeEvent(c, Compendium_t::Events_t::previousCurrentLevel, Compendium_t::Events_t::previousSecretlevel, prevmapname); + players[c]->compendiumProgress.playerAliveTimeTotal = 0; + players[c]->compendiumProgress.playerGameTimeTotal = 0; } // save at end of level change @@ -2500,6 +2500,7 @@ void gameLogic(void) saveGame(); } Compendium_t::Events_t::writeItemsSaveData(); + Compendium_t::writeUnlocksSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif @@ -3522,6 +3523,17 @@ void gameLogic(void) if (!gamePaused && !intro && playeralive) { ++completionTime; + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + players[c]->compendiumProgress.playerAliveTimeTotal++; + } + } + if ( !gamePaused && !intro ) + { + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + players[c]->compendiumProgress.playerGameTimeTotal++; + } } } diff --git a/src/init_game.cpp b/src/init_game.cpp index abceea255..a678036ae 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -100,6 +100,7 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); Compendium_t::Events_t::readEventsTranslations(); + Compendium_t::readUnlocksSaveData(); Compendium_t::Events_t::loadItemsSaveData(); CompendiumEntries.readModelLimbsFromFile("monster"); CompendiumEntries.readModelLimbsFromFile("world"); @@ -490,6 +491,7 @@ void deinitGame() UIToastNotificationManager.term(true); Compendium_t::Events_t::writeItemsSaveData(); + Compendium_t::writeUnlocksSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 9e90eac35..c10a64836 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -633,6 +633,8 @@ namespace ConsoleCommands { { messagePlayer(clientnum, MESSAGE_MISC, Language::get(285)); loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; } }); @@ -2616,6 +2618,7 @@ namespace ConsoleCommands { messagePlayer(clientnum, MESSAGE_MISC, Language::get(299)); return; } + Compendium_t::Events_t::previousSecretlevel = secretlevel; secretlevel = (secretlevel == false); }); diff --git a/src/menu.cpp b/src/menu.cpp index 0988b95b3..00ea04126 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8548,6 +8548,12 @@ void doNewGame(bool makeHighscore) { EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); PingNetworkStatus_t::reset(); + + bool bOldSecretLevel = secretlevel; + int oldCurrentLevel = currentlevel; + Compendium_t::Events_t::previousCurrentLevel = 0; + Compendium_t::Events_t::previousSecretlevel = false; + currentlevel = startfloor; secretlevel = false; victory = 0; @@ -9438,10 +9444,14 @@ void doNewGame(bool makeHighscore) { } // restarting from a trial, this is a failure - if ( died ) + if ( died && !bWasOnMainMenu ) { - Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, "hall of trials", players[clientnum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS_SLOWEST, "hall of trials", players[clientnum]->compendiumProgress.playerAliveTimeTotal); } + + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_TIME_SPENT, "hall of trials", players[clientnum]->compendiumProgress.playerAliveTimeTotal); } else { @@ -9453,15 +9463,57 @@ void doNewGame(bool makeHighscore) { if ( multiplayer == SERVER || multiplayer == CLIENT || (multiplayer == SINGLE && splitscreen) ) { Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_CLASS_GAMES_MULTI, "class", 1); + if ( multiplayer != SINGLE ) + { + if ( directConnect ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER_LAN_MP, "minehead", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER_ONLINE_MP, "minehead", 1); + } + } + else if ( multiplayer == SINGLE ) + { + if ( splitscreen ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER_SPLIT_MP, "minehead", 1); + } + } } else { Compendium_t::Events_t::eventUpdateCodex(clientnum, Compendium_t::CPDM_CLASS_GAMES_SOLO, "class", 1); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_MINEHEAD_ENTER_SOLO, "minehead", 1); + } + + if ( !bWasOnMainMenu ) + { + const char* currentWorldString = Compendium_t::compendiumCurrentLevelToWorldString(oldCurrentLevel, bOldSecretLevel); + if ( strcmp(currentWorldString, "") ) + { + if ( died ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS, currentWorldString, 1); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, currentWorldString, players[clientnum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_DEATHS_SLOWEST, currentWorldString, players[clientnum]->compendiumProgress.playerAliveTimeTotal); + } + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_LEVELS_TIME_SPENT, currentWorldString, players[clientnum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TOTAL_TIME_SPENT, "minehead", + players[clientnum]->compendiumProgress.playerGameTimeTotal); + } } } } } Compendium_t::Events_t::writeItemsSaveData(); + Compendium_t::writeUnlocksSaveData(); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + players[i]->compendiumProgress.playerAliveTimeTotal = 0; + players[i]->compendiumProgress.playerGameTimeTotal = 0; + } } void doCredits() { @@ -9881,6 +9933,7 @@ void doEndgame(bool saveHighscore) { } Compendium_t::Events_t::writeItemsSaveData(); + Compendium_t::writeUnlocksSaveData(); gameModeManager.currentSession.seededRun.reset(); gameModeManager.currentSession.challengeRun.reset(); @@ -10080,9 +10133,11 @@ void doEndgame(bool saveHighscore) { Compendium_t::Events_t::clientDataStrings[c].clear(); players[c]->compendiumProgress.itemEvents.clear(); players[c]->compendiumProgress.floorEvents.clear(); + players[c]->compendiumProgress.playerAliveTimeTotal = 0; + players[c]->compendiumProgress.playerGameTimeTotal = 0; } #ifdef LOCAL_ACHIEVEMENTS - LocalAchievements.writeToFile(); + LocalAchievements_t::writeToFile(); #endif } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 012c0f3d1..d5f492edd 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -2712,7 +2712,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I #ifndef EDITOR auto itemTooltip = tooltips[tooltipType]; static Stat itemDummyStat(0); - char buf[128]; + static char buf[1024]; memset(buf, 0, sizeof(buf)); if ( conditionalAttribute.find("magicstaff_") != std::string::npos ) @@ -10651,17 +10651,114 @@ Entity Compendium_t::compendiumItemModel(-1, 0, nullptr, nullptr); bool Compendium_t::tooltipNeedUpdate = false; SDL_Rect Compendium_t::tooltipPos; -std::vector> Compendium_t::CompendiumMonsters_t::contents; +std::map>> Compendium_t::CompendiumMonsters_t::contents; std::map Compendium_t::CompendiumMonsters_t::contentsMap; -std::vector> Compendium_t::CompendiumWorld_t::contents; +std::map Compendium_t::CompendiumMonsters_t::unlocks; +std::map>> Compendium_t::CompendiumWorld_t::contents; std::map Compendium_t::CompendiumWorld_t::contentsMap; -std::vector> Compendium_t::CompendiumCodex_t::contents; +std::map Compendium_t::CompendiumWorld_t::unlocks; +std::map>> Compendium_t::CompendiumCodex_t::contents; std::map Compendium_t::CompendiumCodex_t::contentsMap; -std::vector> Compendium_t::CompendiumItems_t::contents; +std::map Compendium_t::CompendiumCodex_t::unlocks; +std::map>> Compendium_t::CompendiumItems_t::contents; std::map Compendium_t::CompendiumItems_t::contentsMap; -std::vector> Compendium_t::CompendiumMagic_t::contents; +std::map Compendium_t::CompendiumItems_t::unlocks; +std::map Compendium_t::CompendiumItems_t::itemUnlocks; +std::map>> Compendium_t::CompendiumMagic_t::contents; std::map Compendium_t::CompendiumMagic_t::contentsMap; +std::map Compendium_t::Events_t::monsterIDToString; +std::map Compendium_t::Events_t::codexIDToString; +std::map Compendium_t::Events_t::worldIDToString; +std::map Compendium_t::Events_t::itemIDToString; + +void Compendium_t::readContentsLang(std::string name, std::map>>& contents, + std::map& contentsMap) +{ + contents.clear(); + contentsMap.clear(); + + std::string filename = "lang/compendium_lang/"; + filename += name; + filename += ".json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[65536]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("contents") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + contents["default"].push_back(std::make_pair(itr2->value.GetString(), itr2->name.GetString())); + contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + } + } + + if ( d.HasMember("contents_alphabetical") ) + { + for ( auto itr = d["contents_alphabetical"].Begin(); itr != d["contents_alphabetical"].End(); ++itr ) + { + for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) + { + contents["alphabetical"].push_back(std::make_pair(itr2->value.GetString(), itr2->name.GetString())); + contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + } + } + } +} + +void Compendium_t::CompendiumMonsters_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_monsters", contents, contentsMap); +} + +void Compendium_t::CompendiumWorld_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_world", contents, contentsMap); +} + +void Compendium_t::CompendiumCodex_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_codex", contents, contentsMap); +} + +void Compendium_t::CompendiumItems_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_items", contents, contentsMap); +} + +void Compendium_t::CompendiumMagic_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_magic", contents, contentsMap); +} + void Compendium_t::updateTooltip() { bool update = tooltipNeedUpdate; @@ -10716,18 +10813,10 @@ void Compendium_t::readItemsFromFile() } items.clear(); - CompendiumItems_t::contents.clear(); - CompendiumItems_t::contentsMap.clear(); Compendium_t::Events_t::itemEventLookup.clear(); Compendium_t::Events_t::eventItemLookup.clear(); - for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) - { - for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) - { - CompendiumItems_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumItems_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); - } - } + Compendium_t::Events_t::itemIDToString.clear(); + Compendium_t::CompendiumItems_t::readContentsLang(); auto& entries = d["items"]; for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) @@ -10751,47 +10840,51 @@ void Compendium_t::readItemsFromFile() obj.items_in_category.push_back(item); } } - if ( w.HasMember("events") ) - { - std::set alwaysTrackedEvents = { - "APPRAISED", - "RUNS_COLLECTED" - }; - for ( auto& item : obj.items_in_category ) + std::set alwaysTrackedEvents = { + "APPRAISED", + "RUNS_COLLECTED" + }; + + for ( auto& item : obj.items_in_category ) + { + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; - if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + item.itemID = itemType; + Compendium_t::Events_t::itemIDToString[itemType] = name; + if ( ::items[itemType].item_slot != NO_EQUIP ) { - if ( ::items[itemType].item_slot != NO_EQUIP ) - { - alwaysTrackedEvents.insert("BROKEN"); - alwaysTrackedEvents.insert("DEGRADED"); - alwaysTrackedEvents.insert("REPAIRS"); - } + alwaysTrackedEvents.insert("BROKEN"); + alwaysTrackedEvents.insert("DEGRADED"); + alwaysTrackedEvents.insert("REPAIRS"); } } + } - for ( auto& s : alwaysTrackedEvents ) + for ( auto& s : alwaysTrackedEvents ) + { + auto find = Compendium_t::Events_t::eventIdLookup.find(s); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) { - auto find = Compendium_t::Events_t::eventIdLookup.find(s); - if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) { - auto find2 = Compendium_t::Events_t::events.find(find->second); - if ( find2 != Compendium_t::Events_t::events.end() ) + for ( auto& item : obj.items_in_category ) { - for ( auto& item : obj.items_in_category ) + const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - const int itemType = ItemTooltips.itemNameStringToItemID[item.name]; - if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) - { - Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); - Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); - } + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); } } } } + } + + if ( w.HasMember("events") ) + { for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) { std::string eventName = itr->GetString(); @@ -10880,18 +10973,9 @@ void Compendium_t::readMagicFromFile() } magic.clear(); - CompendiumMagic_t::contents.clear(); - CompendiumMagic_t::contentsMap.clear(); //Compendium_t::Events_t::itemEventLookup.clear(); //Compendium_t::Events_t::eventItemLookup.clear(); - for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) - { - for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) - { - CompendiumMagic_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumMagic_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); - } - } + Compendium_t::CompendiumMagic_t::readContentsLang(); auto& entries = d["items"]; std::unordered_set ignoredSpells; @@ -11069,54 +11153,64 @@ void Compendium_t::readMagicFromFile() // } //} - if ( w.HasMember("events") ) - { - std::set alwaysTrackedEvents = { - "APPRAISED", - "RUNS_COLLECTED" - }; + std::set alwaysTrackedEvents = { + "APPRAISED", + "RUNS_COLLECTED" + }; - for ( auto& item : obj.items_in_category ) + for ( auto& item : obj.items_in_category ) + { + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { - bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); - const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; - if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + item.itemID = itemType; + if ( !isSpell ) { - if ( itemType != SPELL_ITEM && ::items[itemType].item_slot != NO_EQUIP ) - { - alwaysTrackedEvents.insert("BROKEN"); - alwaysTrackedEvents.insert("DEGRADED"); - alwaysTrackedEvents.insert("REPAIRS"); - } + Compendium_t::Events_t::itemIDToString[itemType] = name; + } + else + { + Compendium_t::Events_t::itemIDToString[Compendium_t::Events_t::kEventSpellOffset + item.spellID] = name; + } + if ( itemType != SPELL_ITEM && ::items[itemType].item_slot != NO_EQUIP ) + { + alwaysTrackedEvents.insert("BROKEN"); + alwaysTrackedEvents.insert("DEGRADED"); + alwaysTrackedEvents.insert("REPAIRS"); } } + } - for ( auto& s : alwaysTrackedEvents ) + for ( auto& s : alwaysTrackedEvents ) + { + auto find = Compendium_t::Events_t::eventIdLookup.find(s); + if ( find != Compendium_t::Events_t::eventIdLookup.end() ) { - auto find = Compendium_t::Events_t::eventIdLookup.find(s); - if ( find != Compendium_t::Events_t::eventIdLookup.end() ) + auto find2 = Compendium_t::Events_t::events.find(find->second); + if ( find2 != Compendium_t::Events_t::events.end() ) { - auto find2 = Compendium_t::Events_t::events.find(find->second); - if ( find2 != Compendium_t::Events_t::events.end() ) + for ( auto& item : obj.items_in_category ) { - for ( auto& item : obj.items_in_category ) + bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); + const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; + if ( itemType == SPELL_ITEM ) { - bool isSpell = (objSpellsLookup.find(item.name) != objSpellsLookup.end()); - const int itemType = isSpell ? SPELL_ITEM : ItemTooltips.itemNameStringToItemID[item.name]; - if ( itemType == SPELL_ITEM ) - { - Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); - Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert(Compendium_t::Events_t::kEventSpellOffset + item.spellID); - } - else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) - { - Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); - Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); - } + Compendium_t::Events_t::itemEventLookup[Compendium_t::Events_t::kEventSpellOffset + item.spellID].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert(Compendium_t::Events_t::kEventSpellOffset + item.spellID); + } + else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + { + Compendium_t::Events_t::itemEventLookup[(ItemType)itemType].insert((Compendium_t::EventTags)find2->second.id); + Compendium_t::Events_t::eventItemLookup[(Compendium_t::EventTags)find2->second.id].insert((ItemType)itemType); } } } } + } + + if ( w.HasMember("events") ) + { for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) { std::string eventName = itr->GetString(); @@ -11221,18 +11315,11 @@ void Compendium_t::readCodexFromFile() } codex.clear(); - CompendiumCodex_t::contents.clear(); - CompendiumCodex_t::contentsMap.clear(); + Compendium_t::Events_t::eventCodexIDLookup.clear(); Compendium_t::Events_t::eventCodexLookup.clear(); - for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) - { - for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) - { - CompendiumCodex_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumCodex_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); - } - } + Compendium_t::Events_t::codexIDToString.clear(); + Compendium_t::CompendiumCodex_t::readContentsLang(); auto& entries = d["codex"]; for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) @@ -11255,6 +11342,7 @@ void Compendium_t::readCodexFromFile() } Compendium_t::Events_t::eventCodexIDLookup[name] = obj.id; + Compendium_t::Events_t::codexIDToString[obj.id + Compendium_t::Events_t::kEventCodexOffset] = name; if ( w.HasMember("events") ) { for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) @@ -11364,18 +11452,10 @@ void Compendium_t::readWorldFromFile() } worldObjects.clear(); - CompendiumWorld_t::contents.clear(); - CompendiumWorld_t::contentsMap.clear(); Compendium_t::Events_t::eventWorldIDLookup.clear(); Compendium_t::Events_t::eventWorldLookup.clear(); - for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) - { - for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) - { - CompendiumWorld_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumWorld_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); - } - } + Compendium_t::Events_t::worldIDToString.clear(); + Compendium_t::CompendiumWorld_t::readContentsLang(); auto& entries = d["world"]; for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) @@ -11394,6 +11474,7 @@ void Compendium_t::readWorldFromFile() } Compendium_t::Events_t::eventWorldIDLookup[name] = obj.id; + Compendium_t::Events_t::worldIDToString[obj.id + Compendium_t::Events_t::kEventWorldOffset] = name; if ( w.HasMember("events") ) { for ( auto itr = w["events"].Begin(); itr != w["events"].End(); ++itr ) @@ -11410,6 +11491,8 @@ void Compendium_t::readWorldFromFile() } } } + Compendium_t::Events_t::itemDisplayedEventsList.erase(Compendium_t::Events_t::kEventWorldOffset + obj.id); + Compendium_t::Events_t::itemDisplayedCustomEventsList.erase(Compendium_t::Events_t::kEventWorldOffset + obj.id); if ( w.HasMember("events_display") ) { for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) @@ -11423,7 +11506,7 @@ void Compendium_t::readWorldFromFile() { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventWorldOffset + obj.id]; if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) - == vec.end() ) + == vec.end() || find2->second.id == EventTags::CPDM_CUSTOM_TAG ) { vec.push_back((Compendium_t::EventTags)find2->second.id); } @@ -11431,6 +11514,37 @@ void Compendium_t::readWorldFromFile() } } } + if ( w.HasMember("custom_events_display") ) + { + std::vector customEvents; + for ( auto itr = w["custom_events_display"].Begin(); itr != w["custom_events_display"].End(); ++itr ) + { + customEvents.push_back(itr->GetString()); + } + + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventWorldOffset + obj.id]; + int index = -1; + for ( auto& v : vec ) + { + ++index; + auto& vec2 = Compendium_t::Events_t::itemDisplayedCustomEventsList[Compendium_t::Events_t::kEventWorldOffset + obj.id]; + if ( v == EventTags::CPDM_CUSTOM_TAG ) + { + if ( index < customEvents.size() ) + { + vec2.push_back(customEvents[index]); + } + else + { + vec2.push_back(""); + } + } + else + { + vec2.push_back(""); + } + } + } } } @@ -11472,18 +11586,12 @@ void Compendium_t::readMonstersFromFile() } monsters.clear(); - CompendiumMonsters_t::contents.clear(); - CompendiumMonsters_t::contentsMap.clear(); + Compendium_t::Events_t::monsterUniqueIDLookup.clear(); Compendium_t::Events_t::eventMonsterLookup.clear(); - for ( auto itr = d["contents"].Begin(); itr != d["contents"].End(); ++itr ) - { - for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) - { - CompendiumMonsters_t::contents.push_back(std::make_pair(itr2->name.GetString(), itr2->value.GetString())); - CompendiumMonsters_t::contentsMap[itr2->name.GetString()] = itr2->value.GetString(); - } - } + Compendium_t::Events_t::monsterIDToString.clear(); + + Compendium_t::CompendiumMonsters_t::readContentsLang(); if ( d.HasMember("unique_tags") ) { @@ -11498,13 +11606,16 @@ void Compendium_t::readMonstersFromFile() int type = i + Compendium_t::Events_t::kEventMonsterOffset; Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); - Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); + + Compendium_t::Events_t::monsterIDToString[type] = monstertypename[i]; } for ( auto pair : Compendium_t::Events_t::monsterUniqueIDLookup ) { int type = pair.second + Compendium_t::Events_t::kEventMonsterOffset; + Compendium_t::Events_t::monsterIDToString[type] = pair.first; if ( pair.first == "ghost" ) { Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GHOST_SPAWNED].insert(type); @@ -11516,9 +11627,9 @@ void Compendium_t::readMonstersFromFile() { Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_SOLO].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_PARTY].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); - Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); } } @@ -11661,7 +11772,17 @@ void Compendium_t::Events_t::readEventsTranslations() std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const char* formatType, int formatVal, std::map& langMap) { std::string resultsFormatting = "%d"; - if ( langMap.find("format") != langMap.end() ) + if ( formatType && !strcmp(formatType, "cycle") ) + { + std::string fmt = "format"; + fmt += std::to_string(formatVal); + + if ( langMap.find(fmt) != langMap.end() ) + { + resultsFormatting = langMap[fmt]; + } + } + else if ( langMap.find("format") != langMap.end() ) { resultsFormatting = langMap["format"]; } @@ -11680,16 +11801,68 @@ std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const ch // dist to meters float meters = value / (8.f); char buf[32]; - snprintf(buf, sizeof(buf), "%.1f", meters); + if ( meters >= 1000.f ) + { + float km = meters / 1000.f; + snprintf(buf, sizeof(buf), "%.2f km", km); + } + else + { + snprintf(buf, sizeof(buf), "%.1f m", meters); + } output += buf; } else if ( resultsFormatting[c + 1] == 't' ) { - // ticks to seconds - float seconds = value / (50.f); - char buf[32]; - snprintf(buf, sizeof(buf), "%.1f", seconds); - output += buf; + if ( langMap.find("format_time") != langMap.end() ) + { + int numSymbols = 0; + std::string fmt = langMap["format_time"]; + for ( size_t c = 0; c < fmt.size(); ++c ) + { + if ( fmt[c] == '%' ) + { + numSymbols++; + } + } + Uint32 sec = (value / TICKS_PER_SECOND) % 60; + Uint32 min = ((value / TICKS_PER_SECOND) / 60); + Uint32 hour = (((value / TICKS_PER_SECOND) / 60) / 60); + Uint32 day = ((value / TICKS_PER_SECOND) / 60) / 60 / 24; + char buf[32] = ""; + if ( numSymbols == 2 ) + { + // mins/secs + min = std::min(min, (Uint32)9999); + snprintf(buf, sizeof(buf), fmt.c_str(), min, sec); + output += buf; + } + else if ( numSymbols == 3 ) + { + // hours/mins/secs + min = min % 60; + hour = std::min(hour, (Uint32)9999); + snprintf(buf, sizeof(buf), fmt.c_str(), hour, min, sec); + output += buf; + } + else if ( numSymbols == 4 ) + { + // days also + min = min % 60; + hour = hour % 24; + day = std::min(day, (Uint32)9999); + snprintf(buf, sizeof(buf), fmt.c_str(), day, hour, min, sec); + output += buf; + } + } + else + { + // ticks to seconds + float seconds = value / (50.f); + char buf[32]; + snprintf(buf, sizeof(buf), "%.1f", seconds); + output += buf; + } } else if ( resultsFormatting[c + 1] == 'd' ) { @@ -11781,7 +11954,8 @@ std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const ch return output; } -std::vector> Compendium_t::Events_t::getCustomEventValue(std::string key, int specificClass) +std::vector> Compendium_t::Events_t::getCustomEventValue(std::string key, + std::string compendiumSection, std::string compendiumContentsSelected, int specificClass) { std::vector> results; if ( customEventsValues.find(key) == customEventsValues.end() ) @@ -11799,7 +11973,7 @@ std::vector> Compendium_t::Events_t::getCustomEve if ( d.HasMember("value") ) { Events_t::Type type = MAX; - int minValue = 0; + int minValue = INT_MAX; int maxValue = 0; bool firstResult = true; std::string valueType = ""; @@ -11831,6 +12005,8 @@ std::vector> Compendium_t::Events_t::getCustomEve std::map mapValueTotals; std::string formatType = ""; + int cycleResults = 0; + bool foundTag = false; if ( d["value"].HasMember("format") ) { formatType = d["value"]["format"].GetString(); @@ -11841,6 +12017,10 @@ std::vector> Compendium_t::Events_t::getCustomEve { std::string name = (*itr)["name"].GetString(); std::string cat = (*itr)["category"].GetString(); + if ( cat == "" ) + { + cat = compendiumContentsSelected; + } if ( valueType == "sum_items" ) { @@ -11914,8 +12094,9 @@ std::vector> Compendium_t::Events_t::getCustomEve continue; } - auto findCat = eventCodexIDLookup.find(cat); - if ( findCat != eventCodexIDLookup.end() ) + auto& eventSectionIDLookup = compendiumSection == "codex" ? eventCodexIDLookup : eventWorldIDLookup; + auto findCat = eventSectionIDLookup.find(cat); + if ( findCat != eventSectionIDLookup.end() ) { auto findTag = eventIdLookup.find(name); if ( findTag != eventIdLookup.end() ) @@ -11927,13 +12108,16 @@ std::vector> Compendium_t::Events_t::getCustomEve { results.push_back(std::make_pair("-", 0)); } + ++cycleResults; continue; } auto& playerTags = playerEvents[tag]; std::vector> codexIDs; codexIDs.push_back(std::make_pair(-1, findCat->second)); - if ( eventCodexLookup[tag].find(cat) != eventCodexLookup[tag].end() ) + auto& eventSectionLookup = compendiumSection == "codex" ? eventCodexLookup : eventWorldLookup; + + if ( eventSectionLookup[tag].find(cat) != eventSectionLookup[tag].end() ) { auto& def = events[tag]; if ( def.attributes.find("stats") != def.attributes.end() && valueType != "max_class" ) @@ -12038,9 +12222,19 @@ std::vector> Compendium_t::Events_t::getCustomEve } } - if ( codexID < kEventCodexOffset ) + if ( compendiumSection == "codex" ) + { + if ( codexID < kEventCodexOffset ) + { + codexID += kEventCodexOffset; // convert to offset + } + } + else { - codexID += kEventCodexOffset; // convert to offset + if ( codexID < kEventWorldOffset ) + { + codexID += kEventWorldOffset; // convert to offset + } } auto findVal = playerTags.find(codexID); @@ -12048,6 +12242,7 @@ std::vector> Compendium_t::Events_t::getCustomEve if ( findVal != playerTags.end() ) { val = findVal->second.value; + foundTag = true; } std::string output = ""; @@ -12089,18 +12284,35 @@ std::vector> Compendium_t::Events_t::getCustomEve { output = formatEventRecordText(val, "race", classnum, eventCustomLangEntries[key]); } + else if ( valueType == "cycle" ) + { + if ( findVal != playerTags.end() ) + { + output = formatEventRecordText(val, valueType.c_str(), cycleResults, eventCustomLangEntries[key]); + } + else + { + output = "-"; + } + } results.push_back(std::make_pair(output, val)); maxValue = std::max(maxValue, val); - if ( firstResult ) - { - minValue = val; - } - else + + if ( findVal != playerTags.end() ) { - minValue = std::min(minValue, val); + if ( firstResult ) + { + minValue = val; + } + else + { + minValue = std::min(minValue, val); + } + firstResult = false; } - firstResult = false; + + ++cycleResults; } } } @@ -12109,18 +12321,41 @@ std::vector> Compendium_t::Events_t::getCustomEve } Sint32 sum = 0; - if ( valueType == "sum_items" ) + if ( valueType == "cycle" ) { - results.clear(); - for ( auto& pair : mapValueTotals ) + bool filledEntry = false; + for ( auto& pair : results ) { - sum += pair.second; + if ( pair.first != "-" ) + { + filledEntry = true; + } } - std::string output = formatEventRecordText(sum, formatType.c_str(), 0, eventCustomLangEntries[key]); - results.push_back(std::make_pair(output, maxValue)); - return results; - } - else if ( valueType == "sum_category_max" ) + for ( auto itr = results.begin(); itr != results.end(); ) + { + if ( filledEntry && itr->first == "-" ) + { + itr = results.erase(itr); // don't display empty entries if something has data in it + } + else + { + ++itr; + } + } + return results; + } + else if ( valueType == "sum_items" ) + { + results.clear(); + for ( auto& pair : mapValueTotals ) + { + sum += pair.second; + } + std::string output = formatEventRecordText(sum, formatType.c_str(), 0, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, maxValue)); + return results; + } + else if ( valueType == "sum_category_max" ) { results.clear(); for ( auto& pair : mapValueTotals ) @@ -12188,7 +12423,7 @@ std::vector> Compendium_t::Events_t::getCustomEve { if ( results.size() >= 1 ) { - char buf[128] = ""; + char buf[1024] = ""; snprintf(buf, sizeof(buf), eventCustomLangEntries[key]["format"].c_str(), results[0].first != "-" ? std::to_string(results[0].second).c_str() : "-", results[1].first != "-" ? std::to_string(results[1].second).c_str() : "-"); @@ -12197,7 +12432,7 @@ std::vector> Compendium_t::Events_t::getCustomEve } else { - char buf[128] = ""; + char buf[1024] = ""; snprintf(buf, sizeof(buf), eventCustomLangEntries[key]["format"].c_str(), "-", "-"); @@ -12230,9 +12465,20 @@ std::vector> Compendium_t::Events_t::getCustomEve } } if ( type == SUM && results.size() > 0 ) + { + if ( foundTag ) + { + results.clear(); + results.push_back(std::make_pair(formatEventRecordText(sum, nullptr, 0, eventCustomLangEntries[key]), sum)); + } + else + { + results.clear(); + } + } + else if ( (type == MIN || type == MAX) && !foundTag ) { results.clear(); - results.push_back(std::make_pair(formatEventRecordText(sum, nullptr, 0, eventCustomLangEntries[key]), sum)); } } @@ -12535,6 +12781,222 @@ void Compendium_t::Events_t::createDummyClientData(const int playernum) } } +void Compendium_t::readUnlocksSaveData() +{ + CompendiumItems_t::unlocks.clear(); + CompendiumItems_t::itemUnlocks.clear(); + CompendiumWorld_t::unlocks.clear(); + CompendiumCodex_t::unlocks.clear(); + CompendiumMonsters_t::unlocks.clear(); + + CompendiumWorld_t::unlocks["minehead"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumWorld_t::unlocks["hall of trials"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumWorld_t::unlocks["portcullis"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumWorld_t::unlocks["lever"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumWorld_t::unlocks["door"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumMonsters_t::unlocks["human"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + for ( auto& data : CompendiumEntries.codex ) + { + CompendiumCodex_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + for ( auto& data : CompendiumEntries.items ) + { + for ( auto& entry : data.second.items_in_category ) + { + if ( entry.itemID == TOOL_TORCH + || entry.itemID == BRONZE_SWORD + || entry.itemID == WOODEN_SHIELD + || entry.itemID == BRONZE_AXE + || entry.itemID == QUARTERSTAFF + || entry.itemID == BRONZE_MACE + || (entry.itemID == SPELL_CAT && entry.spellID == SPELL_FORCEBOLT) + || entry.itemID == SCROLL_BLANK + || entry.itemID == MAGICSTAFF_OPENING + || entry.itemID == MAGICSTAFF_LOCKING ) + { + CompendiumItems_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumItems_t::itemUnlocks[entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + + for ( auto& data : CompendiumEntries.magic ) + { + for ( auto& entry : data.second.items_in_category ) + { + if ( entry.itemID == TOOL_TORCH + || entry.itemID == BRONZE_SWORD + || entry.itemID == WOODEN_SHIELD + || entry.itemID == BRONZE_AXE + || entry.itemID == QUARTERSTAFF + || entry.itemID == BRONZE_MACE + || (entry.itemID == SPELL_ITEM && entry.spellID == SPELL_FORCEBOLT) + || entry.itemID == SCROLL_BLANK + || entry.itemID == MAGICSTAFF_OPENING + || entry.itemID == MAGICSTAFF_LOCKING ) + { + CompendiumItems_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumItems_t::itemUnlocks[entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + + + const std::string filename = "savegames/compendium_progress.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Warning: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + const int bufSize = 120000; + char buf[bufSize]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + if ( d.HasMember("items") ) + { + for ( auto itr = d["items"].MemberBegin(); itr != d["items"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + CompendiumItems_t::unlocks[itr->name.GetString()] = static_cast(val); + } + } + + if ( d.HasMember("world") ) + { + for ( auto itr = d["world"].MemberBegin(); itr != d["world"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + CompendiumWorld_t::unlocks[itr->name.GetString()] = static_cast(val); + } + } + + if ( d.HasMember("codex") ) + { + for ( auto itr = d["codex"].MemberBegin(); itr != d["codex"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + CompendiumCodex_t::unlocks[itr->name.GetString()] = static_cast(val); + } + } + + if ( d.HasMember("monsters") ) + { + for ( auto itr = d["monsters"].MemberBegin(); itr != d["monsters"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + CompendiumMonsters_t::unlocks[itr->name.GetString()] = static_cast(val); + } + } +} + +void Compendium_t::writeUnlocksSaveData() +{ + return; + char path[PATH_MAX] = ""; + completePath(path, "savegames/compendium_progress.json", outputdir); + + rapidjson::Document exportDocument; + exportDocument.SetObject(); + + const int VERSION = 1; + + CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(VERSION)); + rapidjson::Value obj(rapidjson::kObjectType); + obj.RemoveAllMembers(); + for ( auto& pair : CompendiumItems_t::unlocks ) + { + obj.AddMember(rapidjson::Value(pair.first.c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "items", obj); + + obj.RemoveAllMembers(); + for ( auto& pair : CompendiumWorld_t::unlocks ) + { + obj.AddMember(rapidjson::Value(pair.first.c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "world", obj); + + obj.RemoveAllMembers(); + for ( auto& pair : CompendiumCodex_t::unlocks ) + { + obj.AddMember(rapidjson::Value(pair.first.c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "codex", obj); + + obj.RemoveAllMembers(); + for ( auto& pair : CompendiumMonsters_t::unlocks ) + { + obj.AddMember(rapidjson::Value(pair.first.c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "monsters", obj); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::Writer writer(os); + exportDocument.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + + printlog("[JSON]: Successfully wrote json file %s", path); + return; +} + void Compendium_t::Events_t::writeItemsSaveData() { char path[PATH_MAX] = ""; @@ -12634,6 +13096,8 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) void onCompendiumLevelExit(const int playernum, const char* level, const bool enteringLvl) { + if ( !level ) { return; } + if ( !strcmp(level, "") ) { return; } if ( enteringLvl ) { Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_ENTERED, level, 1); @@ -12643,6 +13107,19 @@ void onCompendiumLevelExit(const int playernum, const char* level, const bool en Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_EXITED, level, 1); Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_GOLD, level, stats[playernum]->GOLD); Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_LVL, level, stats[playernum]->LVL); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MIN_LVL, level, stats[playernum]->LVL); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MIN_COMPLETION, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_COMPLETION, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); + + if ( stats[playernum]->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS, level, 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_SLOWEST, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); + } + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_TIME_SPENT, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TOTAL_TIME_SPENT, "minehead", + players[playernum]->compendiumProgress.playerGameTimeTotal); } } @@ -12792,6 +13269,114 @@ void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) } } +const char* Compendium_t::compendiumCurrentLevelToWorldString(const int currentlevel, const bool secretlevel) +{ + if ( !secretlevel ) + { + if ( currentlevel == 0 ) + { + return "minehead"; + } + else if ( currentlevel == 5 || currentlevel == 10 || currentlevel == 15 + || currentlevel == 30 ) + { + return "transition floor"; + } + else if ( currentlevel >= 1 && currentlevel <= 4 ) + { + return "mines"; + } + else if ( currentlevel >= 6 && currentlevel <= 9 ) + { + return "swamps"; + } + else if ( currentlevel >= 11 && currentlevel <= 14 ) + { + return "labyrinth"; + } + else if ( currentlevel >= 16 && currentlevel <= 19 ) + { + return "ruins"; + } + else if ( currentlevel == 20 ) + { + return "herx lair"; + } + else if ( currentlevel >= 21 && currentlevel <= 23 ) + { + return "hell"; + } + else if ( currentlevel == 24 ) + { + return "molten throne"; + } + else if ( currentlevel == 25 ) + { + return "hamlet"; + } + else if ( currentlevel >= 26 && currentlevel <= 29 ) + { + return "crystal caves"; + } + else if ( currentlevel >= 31 && currentlevel <= 34 ) + { + return "arcane citadel"; + } + else if ( currentlevel == 35 ) + { + return "citadel sanctum"; + } + } + else + { + if ( currentlevel == 3 ) + { + return "gnomish mines"; + } + else if ( currentlevel == 4 ) + { + return "minetown"; + } + else if ( currentlevel == 8 ) + { + return "temple"; + } + else if ( currentlevel == 9 ) + { + return "haunted castle"; + } + else if ( currentlevel == 12 ) + { + return "sokoban"; + } + else if ( currentlevel == 14 ) + { + return "minotaur maze"; + } + else if ( currentlevel == 17 ) + { + return "mystic library"; + } + else if ( currentlevel == 19 || currentlevel == 20 ) + { + return "underworld"; + } + else if ( currentlevel == 6 || currentlevel == 7 ) + { + return "underworld"; + } + else if ( currentlevel == 29 ) + { + return "cockatrice lair"; + } + else if ( currentlevel == 34 ) + { + return "brams castle"; + } + } + return ""; +} + void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore) { if ( players[playernum]->isLocalPlayer() ) @@ -12800,8 +13385,11 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto { if ( stats[playernum]->HP <= 0 ) { - Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, "hall of trials", players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_SLOWEST, "hall of trials", players[playernum]->compendiumProgress.playerAliveTimeTotal); } + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_TIME_SPENT, "hall of trials", players[playernum]->compendiumProgress.playerAliveTimeTotal); } else { @@ -12818,8 +13406,7 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto } else if ( currentlevel == 24 ) { - onCompendiumLevelExit(playernum, "hell", false); - eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "hell", 1); + onCompendiumLevelExit(playernum, "molten throne", false); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON_HELL, "class", 1); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MAX, "leveling up", stats[playernum]->LVL); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MIN, "leveling up", stats[playernum]->LVL); @@ -12836,10 +13423,19 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto } else { - //if ( stats[playernum]->HP <= 0 ) - //{ - // //Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_DEATHS, "hall of trials", 1); - //} + const char* currentWorldString = compendiumCurrentLevelToWorldString(currentlevel, secretlevel); + if ( strcmp(currentWorldString, "") ) + { + if ( stats[playernum]->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS, currentWorldString, 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, currentWorldString, players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_SLOWEST, currentWorldString, players[playernum]->compendiumProgress.playerAliveTimeTotal); + } + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_TIME_SPENT, currentWorldString, players[playernum]->compendiumProgress.playerAliveTimeTotal); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TOTAL_TIME_SPENT, "minehead", + players[playernum]->compendiumProgress.playerGameTimeTotal); + } } } } @@ -12889,7 +13485,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p if ( mapname.find("Tutorial Hub") == std::string::npos && mapname.find("Tutorial ") != std::string::npos ) { - Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_ATTEMPTS, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_ATTEMPTS, "hall of trials", 1); } if ( prevmapname.find("Tutorial Hub") == std::string::npos && prevmapname.find("Tutorial ") != std::string::npos ) @@ -12897,226 +13493,145 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p if ( mapname.find("Tutorial Hub") != std::string::npos ) { // returning to hub from a trial, success - Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_TRIALS_PASSED, "hall of trials", 1); + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_TRIALS_PASSED, "hall of trials", 1); } } + Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_TIME_SPENT, "hall of trials", players[playernum]->compendiumProgress.playerAliveTimeTotal); } else { - if ( !secretlevel ) + const char* currentWorldString = compendiumCurrentLevelToWorldString(currentlevel, secretlevel); + if ( strcmp(currentWorldString, "") ) { - if ( currentlevel == 5 || currentlevel == 10 || currentlevel == 15 - || currentlevel == 30 ) - { - onCompendiumLevelExit(playernum, "transition floor", true); - } - else if ( currentlevel >= 1 && currentlevel <= 4 ) - { - onCompendiumLevelExit(playernum, "mines", true); - } - else if ( currentlevel >= 6 && currentlevel <= 9 ) - { - onCompendiumLevelExit(playernum, "swamps", true); - } - else if ( currentlevel >= 11 && currentlevel <= 14 ) - { - onCompendiumLevelExit(playernum, "labyrinth", true); - } - else if ( currentlevel >= 16 && currentlevel <= 19 ) - { - onCompendiumLevelExit(playernum, "ruins", true); - } - else if ( currentlevel == 20 ) - { - onCompendiumLevelExit(playernum, "herx lair", true); - } - else if ( currentlevel >= 21 && currentlevel <= 24 ) - { - onCompendiumLevelExit(playernum, "hell", true); - } - else if ( currentlevel == 25 ) - { - onCompendiumLevelExit(playernum, "hamlet", true); - } - else if ( currentlevel >= 26 && currentlevel <= 29 ) - { - onCompendiumLevelExit(playernum, "crystal caves", true); - } - else if ( currentlevel >= 31 && currentlevel <= 34 ) - { - onCompendiumLevelExit(playernum, "arcane citadel", true); - } - else if ( currentlevel == 35 ) - { - onCompendiumLevelExit(playernum, "citadel sanctum", true); - } - } - else - { - if ( currentlevel == 3 ) - { - onCompendiumLevelExit(playernum, "gnomish mines", true); - } - else if ( currentlevel == 4 ) - { - onCompendiumLevelExit(playernum, "minetown", true); - } - else if ( currentlevel == 8 ) - { - onCompendiumLevelExit(playernum, "temple", true); - } - else if ( currentlevel == 9 ) - { - onCompendiumLevelExit(playernum, "haunted castle", true); - } - else if ( currentlevel == 12 ) - { - onCompendiumLevelExit(playernum, "sokoban", true); - } - else if ( currentlevel == 14 ) - { - onCompendiumLevelExit(playernum, "minotaur maze", true); - } - else if ( currentlevel == 17 ) - { - onCompendiumLevelExit(playernum, "mystic library", true); - } - else if ( currentlevel == 19 || currentlevel == 20 ) - { - onCompendiumLevelExit(playernum, "underworld", true); - } - else if ( currentlevel == 6 || currentlevel == 7 ) - { - onCompendiumLevelExit(playernum, "underworld", true); - } - else if ( currentlevel == 29 ) - { - onCompendiumLevelExit(playernum, "cockatrice lair", true); - } - else if ( currentlevel == 34 ) - { - onCompendiumLevelExit(playernum, "brams castle", true); - } + onCompendiumLevelExit(playernum, currentWorldString, true); } + const char* prevWorldString = compendiumCurrentLevelToWorldString(prevlevel, prevsecretfloor); if ( !prevsecretfloor ) { - if ( prevlevel == 5 || prevlevel == 10 || prevlevel == 15 + if ( prevlevel == 0 ) + { + onCompendiumLevelExit(playernum, prevWorldString, false); + } + else if ( prevlevel == 5 || prevlevel == 10 || prevlevel == 15 || prevlevel == 30 ) { - onCompendiumLevelExit(playernum, "transition floor", false); + onCompendiumLevelExit(playernum, prevWorldString, false); } else if ( prevlevel >= 1 && prevlevel <= 4 ) { onCompendiumLevelExit(playernum, "mines", false); + bool commitUniqueValue = false; if ( prevlevel == 4 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "mines", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "mines", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "mines", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } else if ( prevlevel >= 6 && prevlevel <= 9 ) { onCompendiumLevelExit(playernum, "swamps", false); + bool commitUniqueValue = false; if ( prevlevel == 9 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "swamps", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "swamps", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "swamps", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } else if ( prevlevel >= 11 && prevlevel <= 14 ) { onCompendiumLevelExit(playernum, "labyrinth", false); + bool commitUniqueValue = false; if ( prevlevel == 14 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "labyrinth", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "labyrinth", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "labyrinth", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } else if ( prevlevel >= 16 && prevlevel <= 19 ) { onCompendiumLevelExit(playernum, "ruins", false); + bool commitUniqueValue = false; if ( prevlevel == 19 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "ruins", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "ruins", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "ruins", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } else if ( prevlevel == 20 ) { - onCompendiumLevelExit(playernum, "herx lair", false); + onCompendiumLevelExit(playernum, prevWorldString, false); } - else if ( prevlevel >= 21 && prevlevel <= 24 ) + else if ( prevlevel >= 21 && prevlevel <= 23 ) { onCompendiumLevelExit(playernum, "hell", false); - if ( prevlevel == 24 ) + bool commitUniqueValue = false; + if ( prevlevel == 23 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "hell", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "hell", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "hell", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + } + else if ( prevlevel == 24 ) + { + onCompendiumLevelExit(playernum, prevWorldString, false); } else if ( prevlevel == 25 ) { - onCompendiumLevelExit(playernum, "hamlet", false); + onCompendiumLevelExit(playernum, prevWorldString, false); } else if ( prevlevel >= 26 && prevlevel <= 29 ) { onCompendiumLevelExit(playernum, "crystal caves", false); + bool commitUniqueValue = false; if ( prevlevel == 29 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "crystal caves", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "crystal caves", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "crystal caves", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } else if ( prevlevel >= 31 && prevlevel <= 34 ) { onCompendiumLevelExit(playernum, "arcane citadel", false); + bool commitUniqueValue = false; if ( prevlevel == 34 ) { eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_BIOME_CLEAR, "arcane citadel", 1); + commitUniqueValue = true; // commit at end of biome to save to file } + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MIN_COMPLETION, "arcane citadel", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); + eventUpdateWorld(playernum, Compendium_t::CPDM_BIOMES_MAX_COMPLETION, "arcane citadel", + players[playernum]->compendiumProgress.playerAliveTimeTotal, false, -1, commitUniqueValue); } } else { - if ( prevlevel == 3 ) - { - onCompendiumLevelExit(playernum, "gnomish mines", false); - } - else if ( prevlevel == 4 ) - { - onCompendiumLevelExit(playernum, "minetown", false); - } - else if ( prevlevel == 8 ) - { - onCompendiumLevelExit(playernum, "temple", false); - } - else if ( prevlevel == 9 ) - { - onCompendiumLevelExit(playernum, "haunted castle", false); - } - else if ( prevlevel == 12 ) - { - onCompendiumLevelExit(playernum, "sokoban", false); - } - else if ( prevlevel == 14 ) - { - onCompendiumLevelExit(playernum, "minotaur maze", false); - } - else if ( prevlevel == 17 ) - { - onCompendiumLevelExit(playernum, "mystic library", false); - } - else if ( prevlevel == 19 || prevlevel == 20 ) - { - onCompendiumLevelExit(playernum, "underworld", false); - } - else if ( prevlevel == 6 || prevlevel == 7 ) - { - onCompendiumLevelExit(playernum, "underworld", false); - } - else if ( prevlevel == 29 ) - { - onCompendiumLevelExit(playernum, "cockatrice lair", false); - } - else if ( prevlevel == 34 ) - { - onCompendiumLevelExit(playernum, "brams castle", false); - } + onCompendiumLevelExit(playernum, prevWorldString, false); } } } @@ -13262,6 +13777,45 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } } } + + if ( playernum == clientnum ) + { + if ( tag == CPDM_RUNS_COLLECTED ) + { + bool itemUnlocked = false; + { + auto& unlockStatus = Compendium_t::CompendiumItems_t::itemUnlocks[itemType]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + itemUnlocked = true; + } + } + auto find = itemIDToString.find(itemType); + if ( find != itemIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumItems_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) + { + if ( itemUnlocked ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) + { + if ( itemUnlocked ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED; + } + } + } + } + } } void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, @@ -13413,10 +13967,29 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t } } } + + if ( playernum == clientnum ) + { + if ( tag == EventTags::CPDM_KILLED_BY + || tag == EventTags::CPDM_KILLED_SOLO + || tag == EventTags::CPDM_KILLED_MULTIPLAYER + || tag == EventTags::CPDM_RECRUITED ) + { + auto find = monsterIDToString.find(monsterType); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + } } void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, - const bool loadingValue, const int entryID) + const bool loadingValue, const int entryID, const bool commitUniqueValue) { if ( !allowedCompendiumProgress() ) { return; } if ( intro && !loadingValue ) { return; } @@ -13514,10 +14087,6 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; - if ( e.find(worldID) == e.end() ) - { - e[worldID] = EventVal_t(tag); - } if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) { @@ -13530,9 +14099,13 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag players[playernum]->compendiumProgress.itemEvents[def.name][worldID] += value; } - auto& val = e[worldID]; if ( loadingValue ) { + if ( e.find(worldID) == e.end() ) + { + e[worldID] = EventVal_t(tag); + } + auto& val = e[worldID]; val.value = value; // reading from savefile } else @@ -13548,9 +14121,20 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag { players[playernum]->compendiumProgress.itemEvents[def.name][worldID] += value; } - value = players[playernum]->compendiumProgress.itemEvents[def.name][worldID]; + if ( commitUniqueValue ) + { + value = players[playernum]->compendiumProgress.itemEvents[def.name][worldID]; + } + else + { + return; + } } - + if ( e.find(worldID) == e.end() ) + { + e[worldID] = EventVal_t(tag); + } + auto& val = e[worldID]; if ( val.applyValue(value) ) { if ( *cvar_compendiumDebugSave ) @@ -13562,6 +14146,23 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag } } } + + if ( playernum == clientnum ) + { + auto find = worldIDToString.find(worldID); + if ( find != worldIDToString.end() ) + { + auto& worldDefs = CompendiumEntries.worldObjects[find->second]; + if ( worldDefs.unlockTags.find(tag) != worldDefs.unlockTags.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + } } void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag, const char* category, @@ -13790,6 +14391,8 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag } Uint8 Compendium_t::Events_t::clientSequence = 0; +int Compendium_t::Events_t::previousCurrentLevel = 0; +bool Compendium_t::Events_t::previousSecretlevel = false; std::map Compendium_t::Events_t::clientDataStrings[MAXPLAYERS]; std::map> Compendium_t::Events_t::clientReceiveData; void Compendium_t::Events_t::sendClientDataOverNet(const int playernum) diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 06ba7188c..35b13758f 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3495,120 +3495,17 @@ struct Compendium_t bool rotateLimit = true; bool inUse = false; }; - struct CompendiumMonsters_t - { - struct Monster_t - { - int monsterType = NOTHING; - std::string unique_npc = ""; - std::vector blurb; - std::vector hp; - std::vector spd; - std::vector ac; - std::vector atk; - std::vector rangeatk; - std::vector pwr; - std::array resistances; - std::vector abilities; - std::vector inventory; - std::string imagePath = ""; - std::vector models; - }; - static std::vector> contents; - static std::map contentsMap; - }; - std::map monsters; - void readMonstersFromFile(); - void exportCurrentMonster(Entity* monster); - void readModelLimbsFromFile(std::string section); - CompendiumView_t defaultCamera; - struct ObjectLimbs_t - { - CompendiumView_t baseCamera; - CompendiumView_t currentCamera; - std::vector entities; - }; - std::map compendiumObjectLimbs; - CompendiumView_t currentView; - struct CompendiumMap_t - { - Uint32 width = 0; - Uint32 height = 0; - Uint32 ceiling = -1; - }; - std::map>> compendiumObjectMapTiles; - map_t compendiumMap; - struct CompendiumWorld_t - { - struct World_t - { - int modelIndex = -1; - std::string imagePath = ""; - std::vector models; - std::vector blurb; - std::vector details; - int id = -1; - }; - static std::vector> contents; - static std::map contentsMap; - }; - std::map worldObjects; - void readWorldFromFile(); - - struct CompendiumCodex_t - { - struct Codex_t - { - int modelIndex = -1; - std::string imagePath = ""; - std::vector renderedImagePaths; - std::vector blurb; - std::vector details; - std::vector models; - int id = -1; - CompendiumView_t view; - }; - static std::vector> contents; - static std::map contentsMap; - }; - std::map codex; - void readCodexFromFile(); - - struct CompendiumItems_t - { - struct Codex_t - { - struct CodexItem_t - { - std::string name = ""; - int rotation = 0; - int spellID = -1; - int effectID = -1; - }; - int modelIndex = -1; - std::string imagePath = ""; - std::vector blurb; - std::vector items_in_category; - }; - static std::vector> contents; - static std::map contentsMap; - }; - std::map items; - void readItemsFromFile(); - struct CompendiumMagic_t - { - static std::vector> contents; - static std::map contentsMap; + static void readContentsLang(std::string name, std::map>>& contents, + std::map& contentsMap); + enum CompendiumUnlockStatus : int { + LOCKED_UNKNOWN, + LOCKED_REVEALED_UNVISITED, + LOCKED_REVEALED_VISITED, + UNLOCKED_UNVISITED, + UNLOCKED_VISITED, + COMPENDIUMUNLOCKSTATUS_MAX }; - std::map magic; - void readMagicFromFile(); - static Item compendiumItem; - static bool tooltipNeedUpdate; - static void updateTooltip(); - static SDL_Rect tooltipPos; - static Entity compendiumItemModel; - static Uint32 lastTickUpdate; enum EventTags { @@ -3857,9 +3754,156 @@ struct Compendium_t CPDM_HP_LOST_TOTAL, CPDM_AC_EFFECTIVENESS_MAX, CPDM_AC_EQUIPMENT_MAX, + CPDM_LEVELS_MIN_COMPLETION, + CPDM_LEVELS_MAX_COMPLETION, + CPDM_LEVELS_DEATHS, + CPDM_LEVELS_DEATHS_FASTEST, + CPDM_LEVELS_DEATHS_SLOWEST, + CPDM_LEVELS_MIN_LVL, + CPDM_BIOMES_MIN_COMPLETION, + CPDM_BIOMES_MAX_COMPLETION, + CPDM_LEVELS_TIME_SPENT, + CPDM_MINEHEAD_ENTER_SOLO, + CPDM_MINEHEAD_ENTER_ONLINE_MP, + CPDM_MINEHEAD_ENTER_LAN_MP, + CPDM_MINEHEAD_TOTAL_PLAYTIME, + CPDM_MINEHEAD_ENTER_SPLIT_MP, + CPDM_TOTAL_TIME_SPENT, CPDM_EVENT_TAGS_MAX }; + struct CompendiumMonsters_t + { + struct Monster_t + { + int monsterType = NOTHING; + std::string unique_npc = ""; + std::vector blurb; + std::vector hp; + std::vector spd; + std::vector ac; + std::vector atk; + std::vector rangeatk; + std::vector pwr; + std::array resistances; + std::vector abilities; + std::vector inventory; + std::string imagePath = ""; + std::vector models; + std::set unlockAchievements; + }; + static std::map>> contents; + static std::map contentsMap; + static void readContentsLang(); + static std::map unlocks; + }; + std::map monsters; + void readMonstersFromFile(); + void exportCurrentMonster(Entity* monster); + void readModelLimbsFromFile(std::string section); + CompendiumView_t defaultCamera; + struct ObjectLimbs_t + { + CompendiumView_t baseCamera; + CompendiumView_t currentCamera; + std::vector entities; + }; + std::map compendiumObjectLimbs; + CompendiumView_t currentView; + struct CompendiumMap_t + { + Uint32 width = 0; + Uint32 height = 0; + Uint32 ceiling = -1; + }; + std::map>> compendiumObjectMapTiles; + map_t compendiumMap; + struct CompendiumWorld_t + { + struct World_t + { + int modelIndex = -1; + std::string imagePath = ""; + std::vector models; + std::vector blurb; + std::vector details; + std::set unlockAchievements; + std::set unlockTags; + int id = -1; + }; + static std::map>> contents; + static std::map contentsMap; + static void readContentsLang(); + static std::map unlocks; + }; + std::map worldObjects; + void readWorldFromFile(); + + struct CompendiumCodex_t + { + struct Codex_t + { + int modelIndex = -1; + std::string imagePath = ""; + std::vector renderedImagePaths; + std::vector blurb; + std::vector details; + std::vector models; + int id = -1; + CompendiumView_t view; + }; + static std::map>> contents; + static std::map contentsMap; + static void readContentsLang(); + static std::map unlocks; + }; + std::map codex; + void readCodexFromFile(); + static const char* compendiumCurrentLevelToWorldString(const int currentlevel, const bool secretlevel); + + struct CompendiumItems_t + { + struct Codex_t + { + struct CodexItem_t + { + std::string name = ""; + int rotation = 0; + int spellID = -1; + int effectID = -1; + int itemID = -1; + }; + int modelIndex = -1; + std::string imagePath = ""; + std::vector blurb; + std::vector items_in_category; + }; + static std::map>> contents; + static std::map contentsMap; + static void readContentsLang(); + static std::map unlocks; + static std::map itemUnlocks; + }; + std::map items; + void readItemsFromFile(); + + struct CompendiumMagic_t + { + static std::map>> contents; + static std::map contentsMap; + static void readContentsLang(); + }; + std::map magic; + void readMagicFromFile(); + static Item compendiumItem; + static bool tooltipNeedUpdate; + static void updateTooltip(); + static SDL_Rect tooltipPos; + static Entity compendiumItemModel; + static Uint32 lastTickUpdate; + static void writeUnlocksSaveData(); + static void readUnlocksSaveData(); + static const char* Compendium_t::getSkillStringForCompendium(const int skill) { switch ( skill ) @@ -3954,6 +3998,10 @@ struct Compendium_t static std::map eventIdLookup; static std::map> itemEventLookup; static std::map monsterUniqueIDLookup; + static std::map itemIDToString; + static std::map monsterIDToString; + static std::map codexIDToString; + static std::map worldIDToString; static std::map> itemDisplayedEventsList; static std::map> itemDisplayedCustomEventsList; static std::map customEventsValues; @@ -3967,7 +4015,7 @@ struct Compendium_t static const int kEventClassesMax = 40; static std::map> eventLangEntries; static std::map> eventCustomLangEntries; - static std::vector> getCustomEventValue(std::string key, int specificClass = -1); + static std::vector> getCustomEventValue(std::string key, std::string compendiumSection, std::string compendiumContentsSelected, int specificClass = -1); static std::string formatEventRecordText(Sint32 value, const char* formatType, int formatVal, std::map& langMap); static void readEventsFromFile(); static void writeItemsSaveData(); @@ -3976,7 +4024,7 @@ struct Compendium_t static void createDummyClientData(const int playernum); static void eventUpdate(int playernum, const EventTags tag, const ItemType type, Sint32 value, const bool loadingValue = false, const int spellID = -1); static void eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, Sint32 value, const bool loadingValue = false, const int entryID = -1); - static void eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1); + static void eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1, const bool commitUniqueValue = true); static void eventUpdateCodex(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1, const bool floorEvent = false); static std::map> playerEvents; static std::map> serverPlayerEvents[MAXPLAYERS]; @@ -3993,6 +4041,8 @@ struct Compendium_t static const int kEventCodexOffset = 3000; static const int kEventCodexClassOffset = 3500; static const int kEventCodexOffsetMax = 9999; + static int previousCurrentLevel; + static bool previousSecretlevel; }; }; diff --git a/src/net.cpp b/src/net.cpp index 984cc9dcb..144429e68 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2309,6 +2309,11 @@ static void changeLevel() { } Compendium_t::Events_t::onLevelChangeEvent(clientnum, prevcurrentlevel, prevsecretfloor, prevmapname); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + players[i]->compendiumProgress.playerAliveTimeTotal = 0; + players[i]->compendiumProgress.playerGameTimeTotal = 0; + } if ( gameModeManager.allowsSaves() ) { @@ -2316,6 +2321,7 @@ static void changeLevel() { } Compendium_t::Events_t::writeItemsSaveData(); + Compendium_t::writeUnlocksSaveData(); #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); #endif diff --git a/src/player.hpp b/src/player.hpp index 1a109fa5e..7efd5407a 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2275,6 +2275,8 @@ class Player Uint32 playerSneakTime = 0; Uint32 playerAliveTimeMoving = 0; Uint32 playerAliveTimeStopped = 0; + Uint32 playerAliveTimeTotal = 0; + Uint32 playerGameTimeTotal = 0; CompendiumProgress_t(Player& p) : player(p) {}; ~CompendiumProgress_t() {}; From 5580bbc9b96d6c9d37b3c82d597d408809dbc0a9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 25 Jul 2024 01:09:55 +1000 Subject: [PATCH 033/244] * ghost compendium animations --- src/ui/GameUI.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index f3f9a1815..64a24de51 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -22316,12 +22316,56 @@ void drawObjectPreview(std::string modelsPath, Entity* object, SDL_Rect pos, rea { if ( doTick ) { + int limbIndex = -1; for ( auto& limb : *limbsArray ) { - if ( limb.sprite >= 1238 && limb.sprite <= 1242 ) + ++limbIndex; + if ( limb.sprite >= 1237 && limb.sprite <= 1242 ) { // ghost limbs - limb.yaw += 0.05; + if ( limb.sprite >= 1238 && limb.sprite <= 1242 ) + { + limb.yaw += 0.05; // spin body + } + + auto& squishTime = limb.skill[5]; + auto& squishGhost = limb.fskill[8]; + auto& squishAngle = limb.fskill[9]; + + Uint32 activeTick = 10; + if ( limb.sprite == 1240 || (limbIndex > 0 && limb.sprite == 1237 && (*limbsArray)[limbIndex - 1].sprite == 1240) ) + { + activeTick = 2 * TICKS_PER_SECOND - 25; + } + else if ( limb.sprite == 1239 || (limbIndex > 0 && limb.sprite == 1237 && (*limbsArray)[limbIndex - 1].sprite == 1239) ) + { + activeTick = 4 * TICKS_PER_SECOND + 25; + } + else if ( limb.sprite == 1241 || (limbIndex > 0 && limb.sprite == 1237 && (*limbsArray)[limbIndex - 1].sprite == 1241) ) + { + activeTick = 6 * TICKS_PER_SECOND - 25; + } + if ( limb.ticks % (8 * TICKS_PER_SECOND) == activeTick ) + { + squishAngle = Player::Ghost_t::GHOST_SQUISH_START_ANGLE / 100.f; + } + ++limb.ticks; + + const real_t squishRate = 6.0; + real_t squishFactor = 0.3; + if ( squishAngle < 0.0 ) + { + squishFactor *= std::max(0.0, (1.0 + squishAngle)); + } + const real_t inc = squishRate * (PI / TICKS_PER_SECOND); + squishGhost = squishAngle * 2 * PI; + const real_t squish = sin(squishGhost) * squishFactor; + squishAngle -= squishRate * (0.5 / TICKS_PER_SECOND); + squishAngle = std::max(squishAngle, -1.0); + + limb.scalex = 1.0 - squish; + limb.scaley = 1.0 - squish; + limb.scalez = 1.0 + squish; } else if ( limb.sprite == 3 ) // torch { From 66a41ad24d6a76b808dc554fdf7f6d7fb5ef1238 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 25 Jul 2024 01:10:56 +1000 Subject: [PATCH 034/244] * compendium unlock mainmenu code, shift around stuff --- src/ui/MainMenu.cpp | 1793 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 1426 insertions(+), 367 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 7344dc95e..609434e72 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -24992,8 +24992,26 @@ namespace MainMenu { }; auto classicEnding = [](){(void)beginFade(FadeDestination::Victory);}; auto fullEnding = [](){(void)beginFade(FadeDestination::Victory); steamAchievement("BARONY_ACH_ALWAYS_WAITING");}; - auto loadNextLevel = [](){if (multiplayer != CLIENT) {loadnextlevel = true; skipLevelsOnLoad = 0; pauseGame(1, false);}}; - auto skipHellLevels = [](){if (multiplayer != CLIENT) {loadnextlevel = true; skipLevelsOnLoad = 5; pauseGame(1, false);}}; + auto loadNextLevel = [](){ + if (multiplayer != CLIENT) + { + loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; + skipLevelsOnLoad = 0; + pauseGame(1, false); + } + }; + auto skipHellLevels = [](){ + if (multiplayer != CLIENT) + { + loadnextlevel = true; + Compendium_t::Events_t::previousCurrentLevel = currentlevel; + Compendium_t::Events_t::previousSecretlevel = secretlevel; + skipLevelsOnLoad = 5; + pauseGame(1, false); + } + }; Scene scenes[] = { {"data/story/HerxMidpointHuman.json", skipHellLevels}, {"data/story/HerxMidpointAutomaton.json", skipHellLevels}, @@ -32620,6 +32638,9 @@ namespace MainMenu { } static ConsoleVariable compendiumMonsterSectionPadY("/compendiumMonsterSectionPadY", 32); + static const int compendiumPageRightInnerY = 32; + static const int compendiumPageRightInnerHeight = 412 - 114 + 22; + static const int compendiumPageRightInnerHeightExpanded = 412; static void refreshCompendiumEntryWorld(std::string name, Frame* parent) { @@ -32692,42 +32713,30 @@ namespace MainMenu { txt += str; } details->setText(txt.c_str()); - } - } - } - } - constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); - constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); + const int numLines = details->getNumTextLines(); + auto actualFont = Font::get(details->getFont()); + int height = 0; + if ( actualFont ) + { + height = std::max(24, 24 + (numLines - 1) * actualFont->height(true)); + } - static auto compendium_items_activate_fn = [](Frame::entry_t& entry) - { - assert(main_menu_frame); - if ( !main_menu_frame ) { return; } - auto compendiumFrame = main_menu_frame->findFrame("compendium"); - if ( !compendiumFrame ) { return; } + SDL_Rect pos = details->getSize(); + pos.h = height; + details->setSize(pos); - if ( auto page_right = compendiumFrame->findFrame("page_right") ) - { - if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) - { - for ( auto& e : page_right_inner->getEntries() ) - { - e->color = compendiumContentsDefaultColor; + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(compendiumPageRightInnerHeight, pos.y + pos.h); + page_right->setActualSize(actualPos); } } } + } - entry.color = compendiumContentsSelectedColor; - //if ( auto page_right = compendiumFrame->findFrame("page_right") ) - //{ - // if ( auto slider = page_right->findSlider("right_slider") ) - // { - // slider->setValue(0); - // slider->getCallback()(*slider); - // } - //} - }; + constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); + constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); + constexpr auto compendiumContentsDivColor = makeColorRGB(42, 22, 18); struct CompendiumTooltipFrames_t { @@ -32746,7 +32755,8 @@ namespace MainMenu { } }; static CompendiumTooltipFrames_t compendiumItemTooltip; - static std::string compendium_contents_current = ""; + static std::map compendium_contents_current; + static std::map> compendium_contents_list_current; static std::vector compendiumCategories = { "monsters", "items", @@ -32805,6 +32815,153 @@ namespace MainMenu { static std::map> compendiumRecordsSectionLoadedValues; static Uint32 compendiumRecordsSectionRandSequence = 0; static Uint32 compendiumRecordsProcessedOnTick = 0; + static std::vector> getRecordEventValue(const char* entryName, Compendium_t::EventTags tag) + { + std::vector> results; + auto findEvent = Compendium_t::Events_t::events.find(tag); + if ( findEvent != Compendium_t::Events_t::events.end() ) + { + auto findTag = Compendium_t::Events_t::playerEvents.find(tag); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + bool firstValue = true; + int entryNum = -1; + + int startOffsetId = -1; + if ( findEvent->second.attributes.find("skills") != findEvent->second.attributes.end() ) + { + for ( int i = 0; i < NUMPROFICIENCIES; ++i ) + { + if ( !strcmp(entryName, Compendium_t::getSkillStringForCompendium(i)) ) + { + startOffsetId = Compendium_t::Events_t::eventClassIds[tag][0] + i * Compendium_t::Events_t::kEventClassesMax; + break; + } + } + } + + int minValue = 0; + int maxValue = 0; + int sum = 0; + bool useSum = findEvent->second.attributes.find("display_sum_classes") != findEvent->second.attributes.end(); + auto type = Compendium_t::Events_t::Type::MAX; + for ( auto& pair : Compendium_t::Events_t::eventClassIds[tag] ) + { + if ( startOffsetId >= 0 ) + { + if ( pair.second < startOffsetId || pair.second >= startOffsetId + Compendium_t::Events_t::kEventClassesMax ) + { + continue; + } + } + + auto find = findTag->second.find(pair.second); + if ( find != findTag->second.end() ) + { + int value = 0; + if ( useSum ) + { + sum += find->second.value; + type = Compendium_t::Events_t::Type::SUM; + continue; + } + else + { + type = find->second.type; + entryNum = pair.first; + value = find->second.value; + } + + if ( firstValue ) + { + minValue = find->second.value; + } + else + { + minValue = std::min(minValue, find->second.value); + } + maxValue = std::max(maxValue, find->second.value); + + firstValue = false; + + std::string output = ""; + if ( findEvent->second.attributes.find("stats") != findEvent->second.attributes.end() + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_STR_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_DEX_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CON_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_INT_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_PER_MAX + && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CHR_MAX ) + { + if ( !strcmp(entryName, "str") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_STR, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "dex") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_DEX, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "con") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CON, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "int") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_INT, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "per") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_PER, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( !strcmp(entryName, "chr") ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CHR, Compendium_t::Events_t::eventLangEntries[tag]); + } + } + else if ( findEvent->second.attributes.find("class") != findEvent->second.attributes.end() ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "class", entryNum % Compendium_t::Events_t::kEventClassesMax, Compendium_t::Events_t::eventLangEntries[tag]); + } + else if ( findEvent->second.attributes.find("race") != findEvent->second.attributes.end() ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, "race", entryNum, Compendium_t::Events_t::eventLangEntries[tag]); + } + else + { + output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + } + results.push_back(std::make_pair(value, output)); + } + } + if ( useSum ) + { + std::string output = Compendium_t::Events_t::formatEventRecordText(sum, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + results.push_back(std::make_pair(sum, output)); + } + else + { + for ( auto itr = results.begin(); itr != results.end(); ) + { + if ( (type == Compendium_t::Events_t::MAX || type == Compendium_t::Events_t::SUM) + && itr->first != maxValue ) + { + itr = results.erase(itr); + } + else if ( type == Compendium_t::Events_t::MIN && itr->first != minValue ) + { + itr = results.erase(itr); + } + else + { + ++itr; + } + } + } + } + } + return results; + } + static void populateRecordsSectionItems(Frame* page_right, int entryType, const char* entryName, int specificClass) { if ( !page_right ) { return; } @@ -32851,8 +33008,8 @@ namespace MainMenu { displayedEvents = { Compendium_t::EventTags::CPDM_KILLED_SOLO, Compendium_t::EventTags::CPDM_KILLED_MULTIPLAYER, - Compendium_t::EventTags::CPDM_KILLED_PARTY, - Compendium_t::EventTags::CPDM_KILLED_BY + Compendium_t::EventTags::CPDM_KILLED_BY, + Compendium_t::EventTags::CPDM_RECRUITED }; } } @@ -32992,11 +33149,61 @@ namespace MainMenu { if ( val ) { val->setDisabled(false); - int value = 0; - if ( customTagKey != "" ) { - auto results = Compendium_t::Events_t::getCustomEventValue(customTagKey, specificClass); + auto results = Compendium_t::Events_t::getCustomEventValue(customTagKey, compendium_current, + compendium_contents_current[compendium_current], specificClass); + if ( customTagKey == "CUSTOM_CLASS_GAMES_WON" ) + { + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_GAMES_WON_CLASSIC", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_GAMES_WON_HELL", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + } + else if ( customTagKey == "CUSTOM_CLASS_LEAST_GAMES_WON" ) + { + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_LEAST_GAMES_WON_CLASSIC", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_LEAST_GAMES_WON_HELL", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + } + if ( results.size() == 0 ) { val->setText("-"); @@ -33017,158 +33224,40 @@ namespace MainMenu { && Compendium_t::Events_t::eventClassIds.find(tag) != Compendium_t::Events_t::eventClassIds.end() ) { val->setText("-"); - auto findEvent = Compendium_t::Events_t::events.find(tag); - if ( findEvent != Compendium_t::Events_t::events.end() ) + std::vector> results = getRecordEventValue(entryName, tag); + if ( tag == Compendium_t::CPDM_CLASS_GAMES_WON ) { - auto findTag = Compendium_t::Events_t::playerEvents.find(tag); - if ( findTag != Compendium_t::Events_t::playerEvents.end() ) { - bool firstValue = true; - int entryNum = -1; - - int startOffsetId = -1; - if ( findEvent->second.attributes.find("skills") != findEvent->second.attributes.end() ) - { - for ( int i = 0; i < NUMPROFICIENCIES; ++i ) - { - if ( !strcmp(entryName, Compendium_t::getSkillStringForCompendium(i)) ) - { - startOffsetId = Compendium_t::Events_t::eventClassIds[tag][0] + i * Compendium_t::Events_t::kEventClassesMax; - break; - } - } - } - - std::vector> results; - int minValue = 0; - int maxValue = 0; - int sum = 0; - bool useSum = findEvent->second.attributes.find("display_sum_classes") != findEvent->second.attributes.end(); - auto type = Compendium_t::Events_t::Type::MAX; - for ( auto& pair : Compendium_t::Events_t::eventClassIds[tag] ) - { - if ( startOffsetId >= 0 ) - { - if ( pair.second < startOffsetId || pair.second >= startOffsetId + Compendium_t::Events_t::kEventClassesMax ) - { - continue; - } - } - - auto find = findTag->second.find(pair.second); - if ( find != findTag->second.end() ) - { - if ( useSum ) - { - sum += find->second.value; - type = Compendium_t::Events_t::Type::SUM; - continue; - } - else - { - type = find->second.type; - entryNum = pair.first; - value = find->second.value; - } - - if ( firstValue ) - { - minValue = find->second.value; - } - else - { - minValue = std::min(minValue, find->second.value); - } - maxValue = std::max(maxValue, find->second.value); - - firstValue = false; - - std::string output = ""; - if ( findEvent->second.attributes.find("stats") != findEvent->second.attributes.end() - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_STR_MAX - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_DEX_MAX - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CON_MAX - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_INT_MAX - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_PER_MAX - && tag != Compendium_t::EventTags::CPDM_CLASS_STAT_CHR_MAX ) - { - if ( !strcmp(entryName, "str") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_STR, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( !strcmp(entryName, "dex") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_DEX, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( !strcmp(entryName, "con") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CON, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( !strcmp(entryName, "int") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_INT, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( !strcmp(entryName, "per") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_PER, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( !strcmp(entryName, "chr") ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "stats", STAT_CHR, Compendium_t::Events_t::eventLangEntries[tag]); - } - } - else if ( findEvent->second.attributes.find("class") != findEvent->second.attributes.end() ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "class", entryNum % Compendium_t::Events_t::kEventClassesMax, Compendium_t::Events_t::eventLangEntries[tag]); - } - else if ( findEvent->second.attributes.find("race") != findEvent->second.attributes.end() ) - { - output = Compendium_t::Events_t::formatEventRecordText(value, "race", entryNum, Compendium_t::Events_t::eventLangEntries[tag]); - } - else - { - output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); - } - results.push_back(std::make_pair(value, output)); - } - } - if ( useSum ) - { - std::string output = Compendium_t::Events_t::formatEventRecordText(sum, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); - results.push_back(std::make_pair(sum, output)); - } - else + auto res2 = getRecordEventValue(entryName, Compendium_t::CPDM_CLASS_GAMES_WON_CLASSIC); + for ( auto& r : res2 ) { - for ( auto itr = results.begin(); itr != results.end(); ) + if ( r.second != "-" ) { - if ( (type == Compendium_t::Events_t::MAX || type == Compendium_t::Events_t::SUM) - && itr->first != maxValue ) - { - itr = results.erase(itr); - } - else if ( type == Compendium_t::Events_t::MIN && itr->first != minValue ) - { - itr = results.erase(itr); - } - else - { - ++itr; - } + results.push_back(r); } } - - if ( results.size() > 0 ) + } + { + auto res2 = getRecordEventValue(entryName, Compendium_t::CPDM_CLASS_GAMES_WON_HELL); + for ( auto& r : res2 ) { - compendiumRecordsSectionLoadedValues[index].clear(); - for ( auto& pair : results ) + if ( r.second != "-" ) { - compendiumRecordsSectionLoadedValues[index].push_back(pair.second); + results.push_back(r); } - val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence - % compendiumRecordsSectionLoadedValues[index].size()].c_str()); } } } + if ( results.size() > 0 ) + { + compendiumRecordsSectionLoadedValues[index].clear(); + for ( auto& pair : results ) + { + compendiumRecordsSectionLoadedValues[index].push_back(pair.second); + } + val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence + % compendiumRecordsSectionLoadedValues[index].size()].c_str()); + } } else { @@ -33176,6 +33265,7 @@ namespace MainMenu { if ( findTag != Compendium_t::Events_t::playerEvents.end() ) { auto find = findTag->second.find(entryType); + int value = 0; if ( find != findTag->second.end() ) { if ( find->second.type == Compendium_t::Events_t::BITFIELD ) @@ -33248,18 +33338,60 @@ namespace MainMenu { } } - static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner) + static void scrollToCompendiumListItem(Frame& toSelect, Frame& page_right_inner) + { + Frame* parent = page_right_inner.getParent(); + if ( Slider* slider = parent->findSlider("right_slider") ) + { + SDL_Rect actualSize = page_right_inner.getActualSize(); + SDL_Rect entryPos = toSelect.getSize(); + SDL_Rect size = page_right_inner.getSize(); + const int diff = actualSize.h - page_right_inner.getSize().h; + if ( diff > 0 ) + { + int entrySize = entryPos.y + entryPos.h; + if ( actualSize.y > entryPos.y ) + { + slider->setValue(100.0 * std::min(std::max(0, entryPos.y - entryPos.h / 4), diff) / diff); + } + else if ( entryPos.y + entryPos.h > actualSize.y + size.h ) + { + slider->setValue(100.0 * std::min(std::max(0, entryPos.y + entryPos.h + entryPos.h / 4 - size.h), diff) / diff); + } + } + else + { + slider->setValue(0.f); + } + slider->getCallback()(*slider); + } + } + + static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner, bool scrollTo) { + if ( scrollTo ) + { + scrollToCompendiumListItem(toSelect, page_right_inner); + } if ( compendium_current == "codex" ) { + int index = -1; for ( auto f : page_right_inner.getFrames() ) { + if ( f->isToBeDeleted() ) + { + continue; + } if ( auto txt = f->findField("item name") ) { + ++index; auto txtRight = f->findField("item txt right"); if ( f == &toSelect ) { txt->setColor(compendiumContentsSelectedColor); + + compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]] = index; + if ( txtRight ) { txtRight->setColor(compendiumContentsSelectedColor); @@ -33296,13 +33428,33 @@ namespace MainMenu { Compendium_t::compendiumItemModel.focalz = 0.5; Compendium_t::compendiumItemModel.roll = 0.0; Compendium_t::compendiumItemModel.flags[SPRITE] = false; + int index = -1; for ( auto f : page_right_inner.getFrames() ) { + if ( f->isToBeDeleted() ) + { + continue; + } if ( auto txt = f->findField("item name") ) { + ++index; if ( f == &toSelect ) { txt->setColor(compendiumContentsSelectedColor); + + compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]] = index; + + if ( auto notifTxt = f->findField("item notif") ) + { + notifTxt->removeSelf(); + } + if ( auto details = f->findField("item detail") ) + { + details->setDisabled(false); + details->setColor(txt->getColor()); + } + txt->dirty = true; + auto find = ItemTooltips.itemNameStringToItemID.find(toSelect.getName()); int itemType = find != ItemTooltips.itemNameStringToItemID.end() ? find->second : -1; std::string modelsPath = "items_single"; @@ -33320,8 +33472,8 @@ namespace MainMenu { appearance = (reinterpret_cast(toSelect.getUserData()) & 0x7F); } - auto& contents = compendium_current == "items" ? CompendiumEntries.items[Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current]].items_in_category - : CompendiumEntries.magic[Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current]].items_in_category; + auto& contents = compendium_current == "items" ? CompendiumEntries.items[compendium_contents_current[compendium_current]].items_in_category + : CompendiumEntries.magic[compendium_contents_current[compendium_current]].items_in_category; int itemLookupIndex = itemType; for ( auto& i : contents ) { @@ -33351,32 +33503,78 @@ namespace MainMenu { else { txt->setColor(compendiumContentsDefaultColor); + if ( auto details = f->findField("item detail") ) + { + details->setColor(txt->getColor()); + } } } } } - static const int compendiumPageRightInnerY = 24; - static const int compendiumPageRightInnerHeight = 412 - 114 + 22; - static const int compendiumPageRightInnerHeightExpanded = 412; - static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) + static void itemListNotificationAnimate(Frame& frame) { - if ( compendium_current == "items" ) + static Uint32 animTick = 0; + static Uint32 animState = 0; + bool animate = false; + if ( ticks != animTick ) { - if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + animTick = ticks; + animate = true; + if ( ticks % (TICKS_PER_SECOND / 2) == 0 ) { - return; + ++animState; + if ( animState >= 2 ) { animState = 0; } } } - else + for ( auto f : frame.getFrames() ) { - if ( CompendiumEntries.magic.find(name) == CompendiumEntries.magic.end() ) + auto notifTxt = f->findField("item notif"); + if ( !notifTxt ) { continue; } + auto itemName = f->findField("item name"); + if ( !itemName ) { continue; } + + Uint32 color = 0; + if ( animState == 0 ) + { + color = makeColorRGB(30, 203, 177); + } + else if ( animState == 1 ) + { + color = makeColorRGB(43, 254, 207); + } + + for ( int i = 0; i < 10; ++i ) + { + notifTxt->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, color); + } + } + }; + +#if !defined(NDEBUG) + static ConsoleVariablecvar_compendium_reveal("/compendium_reveal", false); +#endif + static bool compendiumEntryControlEnabled = false; + + static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) + { + if ( compendium_current == "items" ) + { + if ( CompendiumEntries.items.find(name) == CompendiumEntries.items.end() ) + { + return; + } + } + else + { + if ( CompendiumEntries.magic.find(name) == CompendiumEntries.magic.end() ) { return; } } auto& entry = (compendium_current == "items") ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; + Compendium_t::compendiumEntityCurrent.modelRNG++; if ( Frame* page_right = parent->findFrame("page_right") ) { @@ -33386,6 +33584,18 @@ namespace MainMenu { { f->removeSelf(); } + page_right->remove("selector_bg"); + + auto notif_frame = page_right->addFrame("notif"); + notif_frame->setHollow(true); + notif_frame->setClickable(false); + notif_frame->setInvisible(true); + notif_frame->setTickCallback([](Widget& widget) { + if ( auto parent = static_cast(widget.getParent()) ) + { + itemListNotificationAnimate(*parent); + } + }); const int entrySize = 64; Compendium_t::compendiumItem.status = EXCELLENT; @@ -33416,16 +33626,21 @@ namespace MainMenu { } });*/ + int lastSelection = compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]]; + if ( lastSelection >= entry.items_in_category.size() ) + { + lastSelection = 0; + } + Frame* toSelect = nullptr; + int modelRNGCycle = -1; for ( int i = 0; i < entry.items_in_category.size(); ++i ) { + ++modelRNGCycle; auto& data = entry.items_in_category[i]; auto entry = page_right->addFrame(data.name.c_str()); entry->setHollow(true); entry->setClickable(false); entry->setSize(SDL_Rect{ 8, 0 + i * entrySize, page_right->getSize().w - 32, entrySize }); - /*Button* btn = entry->addButton("item btn"); - btn->setSize(entry->getSize()); - btn->setText("click");*/ if ( i == 0 ) { @@ -33451,7 +33666,7 @@ namespace MainMenu { } } auto txt = frame->findField("item name"); - /*if ( parent = parent->getParent() )*/ + if ( compendiumEntryControlEnabled ) { if ( Frame* parent = page_right_inner->getParent() ) { @@ -33460,9 +33675,14 @@ namespace MainMenu { SDL_Rect absolutePos = frame->getAbsoluteSize(); if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) { + auto itemName = frame->findField("item name"); + bool selectable = !(itemName && !strcmp(itemName->getText(), "???")); if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) { - selectCompendiumItemInList(*frame, *page_right_inner); + if ( selectable ) + { + selectCompendiumItemInList(*frame, *page_right_inner, false); + } } SDL_Rect pos = frame->getSize(); @@ -33485,7 +33705,7 @@ namespace MainMenu { } frame->setSize(pos); - if ( hovered ) + if ( hovered && selectable ) { //frame->select(); const int itemType = strstr(widget.getName(), "spell_") ? SPELL_ITEM @@ -33523,7 +33743,22 @@ namespace MainMenu { auto itemName = entry->addField("item name", 128); itemName->setFont(smallfont_outline); itemName->setSize(SDL_Rect{ itemBg->pos.x + itemBg->pos.w + 4, 8, entry->getSize().w - 8, 48 }); - if ( id == SPELL_ITEM ) + auto itemDetail = entry->addField("item detail", 128); + itemDetail->setFont(smallfont_outline); + itemDetail->setSize(itemName->getSize()); + auto& unlockStatus = Compendium_t::CompendiumItems_t::itemUnlocks[id == SPELL_ITEM + ? data.spellID + Compendium_t::Events_t::kEventSpellOffset : + data.itemID]; + + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN +#if !defined(NDEBUG) + && !*cvar_compendium_reveal +#endif + ) + { + itemName->setText("???"); + } + else if ( id == SPELL_ITEM ) { if ( auto spell = getSpellFromID(data.spellID) ) { @@ -33531,16 +33766,15 @@ namespace MainMenu { camelCaseString(name); if ( auto s = getSpellFromID(data.spellID) ) { - name += '\n'; - char buf[64]; + char buf[128]; snprintf(buf, sizeof(buf), Language::get(6172), s->difficulty, ItemTooltips.getProficiencyLevelName(spell->difficulty).c_str()); - name += buf; + itemDetail->setText(buf); } itemName->setText(name.c_str()); for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line { - itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + itemDetail->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); } } } @@ -33548,31 +33782,55 @@ namespace MainMenu { { std::string name = items[id].getIdentifiedName(); camelCaseString(name); - name += '\n'; - char buf[64]; + char buf[128]; if ( items[id].level >= 0 ) { - snprintf(buf, sizeof(buf), "%s %d", Language::get(6173), items[id].level); + snprintf(buf, sizeof(buf), "\n%s %d", Language::get(6173), items[id].level); } else { - snprintf(buf, sizeof(buf), "%s ???", Language::get(6173)); + snprintf(buf, sizeof(buf), "\n%s ???", Language::get(6173)); } - name += buf; + itemDetail->setText(buf); itemName->setText(name.c_str()); for ( int i = 0; i < 10; ++i ) // highlight 10 words on second line { - itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + itemDetail->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); + } + + + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + itemDetail->setDisabled(true); + if ( auto notifTxt = entry->addField("item notif", 128) ) + { + notifTxt->setSize(itemName->getSize()); + notifTxt->setFont(itemName->getFont()); + std::string str = "\n"; + str += Language::get(6183); + notifTxt->setText(str.c_str()); + notifTxt->setColor(makeColorRGB(255, 255, 255)); + for ( int i = 0; i < 10; ++i ) + { + notifTxt->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(30, 203, 177)); + } + } } } Compendium_t::compendiumItem.type = (ItemType)id; - Compendium_t::compendiumItem.appearance = local_rng.rand() % items[id].variations; + Compendium_t::compendiumItem.appearance = (modelRNGCycle + Compendium_t::compendiumEntityCurrent.modelRNG) % items[id].variations; if ( id == TOOL_PLAYER_LOOT_BAG ) { - Compendium_t::compendiumItem.appearance = local_rng.rand() % 4; + Compendium_t::compendiumItem.appearance = (modelRNGCycle + Compendium_t::compendiumEntityCurrent.modelRNG) % 4; + } + + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + itemImg->path = "*images/ui/Main Menus/AdventureArchives/unknownitem.png"; } - if ( id == SPELL_ITEM ) + else if ( id == SPELL_ITEM ) { itemImg->path = ItemTooltips.getSpellIconPath(clientnum, Compendium_t::compendiumItem, data.spellID); Compendium_t::compendiumItem.appearance = data.spellID; @@ -33582,25 +33840,32 @@ namespace MainMenu { itemImg->path = getItemSpritePath(0, Compendium_t::compendiumItem); } Uint8 userData = std::min((Uint8)127, (Uint8)(0x7F & Compendium_t::compendiumItem.appearance)); + + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); + page_right->setActualSize(actualPos); + if ( i == 0 ) { - userData |= 1 << 7; - entry->setUserData((void*)(intptr_t)userData); - selectCompendiumItemInList(*entry, *page_right); + userData |= 1 << 7; // designate this as first in list + } + entry->setUserData((void*)(intptr_t)userData); + + if ( unlockStatus != Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN && (!toSelect || i == lastSelection) ) + { + toSelect = entry; } else { - entry->setUserData((void*)(intptr_t)userData); itemName->setColor(compendiumContentsDefaultColor); + itemDetail->setColor(compendiumContentsDefaultColor); } + } - SDL_Rect actualPos = page_right->getActualSize(); - actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); - page_right->setActualSize(actualPos); + if ( toSelect ) + { + selectCompendiumItemInList(*toSelect, *page_right, true); } - //page_right->setSelection(0); - //page_right->scrollToSelection(true); - //page_right->activateSelection(); } } } @@ -33612,16 +33877,6 @@ namespace MainMenu { return; } auto& entry = CompendiumEntries.codex[name]; - std::vector entries; - if ( name == "classes list" ) - { - for ( int i = 0; i < NUMCLASSES; ++i ) - { - std::string classname = playerClassLangEntry(i, 0); - camelCaseString(classname); - entries.push_back(classname); - } - } if ( Frame* page_right = parent->findFrame("page_right") ) { if ( page_right = page_right->findFrame("page_right_inner") ) @@ -33630,39 +33885,90 @@ namespace MainMenu { { f->removeSelf(); } + page_right->remove("selector_bg"); const int entrySize = 64; std::vector> rankedScores; + std::map classRank; for ( int j = 0; j < NUMCLASSES; ++j ) { - auto r = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", j); + auto r = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", "codex", "", j); if ( r.size() > 0 ) { - if ( r[0].second > 0 ) - { - rankedScores.push_back(std::make_pair(r[0].second, j)); - } + rankedScores.push_back(std::make_pair(r[0].second, j)); + } + else + { + rankedScores.push_back(std::make_pair(0, j)); } } std::sort(rankedScores.begin(), rankedScores.end(), [](const std::pair& lhs, const std::pair& rhs) { - return lhs > rhs; + return lhs.first > rhs.first + || (lhs.first == rhs.first && lhs.second < rhs.second); }); - int lastRank = rankedScores[0].first; + int lastValue = rankedScores[0].first; int rankNum = 1; for ( auto& pair : rankedScores ) { - if ( lastRank != pair.first ) + if ( lastValue != pair.first ) { ++rankNum; } + lastValue = pair.first; pair.first = rankNum; } + std::vector entries; + static ConsoleVariable cvar_compendium_class_sort_playtime("/compendium_class_sort_playtime", true); + if ( name == "classes list" ) + { + if ( *cvar_compendium_class_sort_playtime ) + { + for ( auto& pair : rankedScores ) + { + for ( int i = 0; i < NUMCLASSES; ++i ) + { + if ( pair.second == i ) + { + std::string classname = playerClassLangEntry(i, 0); + camelCaseString(classname); + entries.push_back(classname); + } + } + } + } + else + { + for ( int i = 0; i < NUMCLASSES; ++i ) + { + std::string classname = playerClassLangEntry(i, 0); + camelCaseString(classname); + entries.push_back(classname); + for ( auto& pair : rankedScores ) + { + if ( pair.second == i ) + { + classRank[i] = pair.first; + } + } + } + } + } + + int lastSelection = compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]]; + if ( lastSelection >= entries.size() ) + { + lastSelection = 0; + } + Frame* toSelect = nullptr; for ( int i = 0; i < entries.size(); ++i ) { auto& data = entries[i]; auto entry = page_right->addFrame(data.c_str()); + + int classnum = *cvar_compendium_class_sort_playtime ? rankedScores[i].second : i; + entry->setHollow(true); entry->setClickable(false); entry->setSize(SDL_Rect{ 8, 0 + i * entrySize, page_right->getSize().w - 32, entrySize }); @@ -33679,80 +33985,64 @@ namespace MainMenu { entry->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); - if ( Frame* page_right_inner = frame->getParent() ) - { - auto selector_bg = page_right_inner->findImage("selector_bg"); - if ( frame->getUserData() ) + if ( Frame* page_right_inner = frame->getParent() ) { - bool first = (reinterpret_cast(frame->getUserData()) >> 7) & 1; - if ( first ) + auto selector_bg = page_right_inner->findImage("selector_bg"); + if ( frame->getUserData() ) { - if ( selector_bg ) + bool first = (reinterpret_cast(frame->getUserData()) >> 7) & 1; + if ( first ) { - selector_bg->disabled = true; + if ( selector_bg ) + { + selector_bg->disabled = true; + } } } - } - auto txt = frame->findField("item name"); - /*if ( parent = parent->getParent() )*/ - { - if ( Frame* parent = page_right_inner->getParent() ) + auto txt = frame->findField("item name"); + if ( compendiumEntryControlEnabled ) { - if ( parent = parent->getParent() ) + if ( Frame* parent = page_right_inner->getParent() ) { - SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) + if ( parent = parent->getParent() ) { - if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) - { - selectCompendiumItemInList(*frame, *page_right_inner); - } - - SDL_Rect pos = frame->getSize(); - bool hovered = false; - if ( selector_bg ) - { - selector_bg->disabled = false; - selector_bg->pos.y = frame->getSize().y; - } - auto itemBg = frame->findImage("item bg"); - if ( itemBg ) - { - SDL_Rect tmp = frame->getSize(); - tmp.x += itemBg->pos.x; - tmp.y += itemBg->pos.y; - tmp.w = itemBg->pos.w; - tmp.h = itemBg->pos.h; - frame->setSize(tmp); - hovered = frame->capturesMouseInRealtimeCoords(); - } - frame->setSize(pos); - - if ( hovered ) + SDL_Rect absolutePos = frame->getAbsoluteSize(); + if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) { - //frame->select(); - /*const int itemType = strstr(widget.getName(), "spell_") ? SPELL_ITEM - : ItemTooltips.itemNameStringToItemID[widget.getName()]; - if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) { - Compendium_t::compendiumItem.type = (ItemType)itemType; - if ( frame->getUserData() ) - { - Compendium_t::compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); - } + selectCompendiumItemInList(*frame, *page_right_inner, false); + } - Compendium_t::tooltipPos.x = absolutePos.x - 16; - Compendium_t::tooltipPos.y = absolutePos.y; - Compendium_t::tooltipNeedUpdate = true; + SDL_Rect pos = frame->getSize(); + bool hovered = false; + if ( selector_bg ) + { + selector_bg->disabled = false; + selector_bg->pos.y = frame->getSize().y; + } + auto itemBg = frame->findImage("item bg"); + if ( itemBg ) + { + SDL_Rect tmp = frame->getSize(); + tmp.x += itemBg->pos.x; + tmp.y += itemBg->pos.y; + tmp.w = itemBg->pos.w; + tmp.h = itemBg->pos.h; + frame->setSize(tmp); + hovered = frame->capturesMouseInRealtimeCoords(); + } + frame->setSize(pos); - }*/ - page_right_inner->setAllowScrollBinds(false); + if ( hovered ) + { + page_right_inner->setAllowScrollBinds(false); + } } } } } } - } }); /*auto itemBg = entry->addImage(SDL_Rect{ 8, 8, 48, 48 }, @@ -33790,7 +34080,7 @@ namespace MainMenu { name += findLang->second["default"]; name += ' '; } - auto results = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", i); + auto results = Compendium_t::Events_t::getCustomEventValue("CUSTOM_CLASS_PLAYTIME", "codex", "", classnum); if ( results.size() > 0 ) { Uint32 playtimeTicks = results[0].second; @@ -33804,23 +34094,17 @@ namespace MainMenu { Uint32 min = ((playtimeTicks / TICKS_PER_SECOND) / 60) % 60; Uint32 hour = (((playtimeTicks / TICKS_PER_SECOND) / 60) / 60) % 24; Uint32 day = ((playtimeTicks / TICKS_PER_SECOND) / 60) / 60 / 24; + day = std::min(day, (Uint32)99); snprintf(buf, sizeof(buf), "%02d:%02d:%02d:%02d", day, hour, min, sec); name += buf; - for ( auto& pair : rankedScores ) + std::string txt = "\n"; + if ( findLang != Compendium_t::Events_t::eventCustomLangEntries.end() ) { - if ( pair.second == i ) - { - std::string txt = "\n"; - if ( findLang != Compendium_t::Events_t::eventCustomLangEntries.end() ) - { - txt += findLang->second["format"]; - } - txt += std::to_string(pair.first); - itemNameRight->setText(txt.c_str()); - break; - } + txt += findLang->second["format"]; } + txt += std::to_string(*cvar_compendium_class_sort_playtime ? rankedScores[i].first : classRank[i]); + itemNameRight->setText(txt.c_str()); } } else @@ -33833,7 +34117,7 @@ namespace MainMenu { itemName->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); } - const auto class_name = classes_in_order[i]; + const auto class_name = classes_in_order[classnum]; const auto class_find = classes.find(class_name); if ( class_find != classes.end() ) { @@ -33842,22 +34126,30 @@ namespace MainMenu { } } - Uint8 userData = i; + SDL_Rect actualPos = page_right->getActualSize(); + actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); + page_right->setActualSize(actualPos); + + Uint8 userData = classnum; if ( i == 0 ) { userData |= 1 << 7; - entry->setUserData((void*)(intptr_t)userData); - selectCompendiumItemInList(*entry, *page_right); + } + + entry->setUserData((void*)(intptr_t)userData); + if ( i == lastSelection || i == 0 ) + { + toSelect = entry; } else { - entry->setUserData((void*)(intptr_t)userData); itemName->setColor(compendiumContentsDefaultColor); } + } - SDL_Rect actualPos = page_right->getActualSize(); - actualPos.h = std::max(compendiumPageRightInnerHeight, entry->getSize().y + entry->getSize().h); - page_right->setActualSize(actualPos); + if ( toSelect ) + { + selectCompendiumItemInList(*toSelect, *page_right, true); } } } @@ -34336,24 +34628,140 @@ namespace MainMenu { } } - static auto contents_activate_fn = [](Frame::entry_t& entry) + static Uint32 compendiumRevealAnimState = 0; + static void compendiumPageRightHideUnlocked(Frame* page_right, const bool unlocked, const bool hideAll, std::string to_unlock = "") + { + if ( !page_right ) { return; } + if ( hideAll ) + { + page_right->setInvisible(true); + page_right->setOpacity(0.0); + if ( Frame* parent = page_right->getParent() ) + { + if ( auto img = parent->findImage("page right img") ) + { + img->disabled = true; + } + if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) + { + if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) + { + reveal_txt->setText(""); + } + if ( auto reveal_btn = parent->findButton("page_right_unlock_btn") ) + { + reveal_btn->setDisabled(true); + reveal_btn->setInvisible(true); + } + } + if ( auto reveal_top = parent->findFrame("page_right_reveal_top") ) + { + if ( auto page_right_reveal = reveal_top->findFrame("page_right_reveal_top") ) + { + page_right_reveal->setDisabled(true); + page_right_reveal->removeSelf(); + } + } + } + return; + } + else if ( unlocked ) + { + page_right->setInvisible(false); + page_right->setOpacity(100.0); + if ( Frame* parent = page_right->getParent() ) + { + if ( auto img = parent->findImage("page right img") ) + { + img->disabled = false; + } + if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) + { + if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) + { + reveal_txt->setText(""); + } + if ( auto reveal_btn = reveal_frame->findButton("page_right_unlock_btn") ) + { + reveal_btn->setDisabled(true); + reveal_btn->setInvisible(true); + } + } + if ( auto reveal_top = parent->findFrame("page_right_reveal_top") ) + { + if ( auto page_right_reveal = reveal_top->findFrame("page_right_reveal_top") ) + { + page_right_reveal->setDisabled(true); + page_right_reveal->removeSelf(); + } + } + } + } + else if ( !unlocked ) + { + page_right->setInvisible(true); + page_right->setOpacity(0.0); + + if ( Frame* parent = page_right->getParent() ) + { + if ( auto img = parent->findImage("page right img") ) + { + img->disabled = true; + } + if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) + { + if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) + { + reveal_txt->setText(to_unlock.c_str()); + } + if ( auto reveal_btn = reveal_frame->findButton("page_right_unlock_btn") ) + { + reveal_btn->setDisabled(false); + reveal_btn->setInvisible(false); + } + } + if ( auto reveal_top = parent->findFrame("page_right_reveal_top") ) + { + if ( auto page_right_reveal = reveal_top->findFrame("page_right_reveal_top") ) + { + page_right_reveal->setDisabled(true); + page_right_reveal->removeSelf(); + } + } + } + } + } + + static std::string compendium_sorting = "default"; + static auto contents_activate_fn = [](Frame::entry_t& frameEntry) { + static ConsoleCommand ccmd_compendium_sorting( + "/compendium_sorting", "", + [](int argc, const char** argv) { + compendium_sorting = compendium_sorting == "default" ? "alphabetical" : "default"; + }); + assert(main_menu_frame); if ( !main_menu_frame ) { return; } auto compendiumFrame = main_menu_frame->findFrame("compendium"); if ( !compendiumFrame ) { return; } - if ( auto contentsFrame = compendiumFrame->findFrame("contents") ) + auto contentsFrame = compendiumFrame->findFrame("contents"); + if ( contentsFrame ) { for ( auto& e : contentsFrame->getEntries() ) { - e->color = compendiumContentsDefaultColor; + if ( e->color != compendiumContentsDivColor ) + { + e->color = compendiumContentsDefaultColor; + } } } - entry.color = compendiumContentsSelectedColor; + frameEntry.color = compendiumContentsSelectedColor; - if ( auto page_right = compendiumFrame->findFrame("page_right") ) + auto page_right = compendiumFrame->findFrame("page_right"); + if ( page_right ) { if ( auto slider = page_right->findSlider("right_slider") ) { @@ -34366,55 +34774,265 @@ namespace MainMenu { } } - auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents : nullptr)))); + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[compendium_sorting] + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] : nullptr)))); + + auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); std::string content = ""; + std::string previousEntry = compendium_contents_current[compendium_current]; + + compendiumEntryControlEnabled = false; + if ( entries ) { - auto id = (intptr_t)entry.data; + auto id = (intptr_t)frameEntry.data; if ( id >= entries->size() ) { return; } auto& entry = (*entries)[id]; - compendium_contents_current = entry.first; + compendium_contents_current[compendium_current] = entry.first; if ( auto page_left_title = compendiumFrame->findField("page_left_title") ) { - page_left_title->setText(entry.first.c_str()); + page_left_title->setText(entry.second.c_str()); + } + + bool unlocked = false; + if ( unlockStatus ) + { + auto findUnlock = unlockStatus->find(entry.first); + if ( findUnlock != unlockStatus->end() ) + { + if ( findUnlock->second == Compendium_t::UNLOCKED_UNVISITED ) + { + findUnlock->second = Compendium_t::UNLOCKED_VISITED; + } + else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + findUnlock->second = Compendium_t::LOCKED_REVEALED_VISITED; + } + + if ( findUnlock->second == Compendium_t::UNLOCKED_VISITED + || findUnlock->second == Compendium_t::LOCKED_UNKNOWN + || findUnlock->second == Compendium_t::UNLOCKED_UNVISITED ) + { + unlocked = true; + } + } + else + { + unlocked = false; + } + + compendiumPageRightHideUnlocked(page_right, unlocked, false, unlocked ? "" : entry.first); + + if ( contentsFrame ) + { + if ( auto notifs = contentsFrame->findFrame("notifs") ) + { + std::vector toRemove; + for ( auto img : notifs->getImages() ) + { + std::string name = ""; + auto find = img->name.find("notif_"); + if ( find != std::string::npos ) + { + name = img->name.substr(strlen("notif_")); + //if ( name == entry.first ) + //{ + // continue; // don't unhighlight the current selection + //} + auto findUnlock = unlockStatus->find(name); + if ( findUnlock != unlockStatus->end() ) + { + if ( findUnlock->second == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED + || findUnlock->second == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) + { + toRemove.push_back(img->name); + } + } + } + } + for ( auto& r : toRemove ) + { + notifs->remove(r.c_str()); + } + } + } } - content = entry.second; + + compendiumEntryControlEnabled = unlocked; if ( Compendium_t::compendiumEntityCurrent.modelRNG == 0 ) { Compendium_t::compendiumEntityCurrent.modelRNG = local_rng.rand(); } + + { + auto pageNumberLeft = compendiumFrame->findField("page_left_number"); + auto pageNumberRight = compendiumFrame->findField("page_right_number"); + auto pagePrev = compendiumFrame->findField("page_left_prev"); + auto pageNext = compendiumFrame->findField("page_right_next"); + + { + std::string txt = Language::get(6179); + int prevId = -1; + if ( id > 0 && id < entries->size() ) + { + prevId = id - 1; + } + else if ( entries->size() > 0 ) + { + prevId = entries->size() - 1; + } + + bool firstLoop = true; + while ( prevId >= 0 ) + { + if ( prevId == id ) + { + // looped around once, quit + txt = ""; + break; + } + if ( (*entries)[prevId].first != "-" ) // not a divider slot + { + auto findUnlock = unlockStatus->find((*entries)[prevId].first); + if ( findUnlock != unlockStatus->end() && findUnlock->second != Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + txt += (*entries)[prevId].second; + break; + } + } + --prevId; + if ( prevId < 0 ) + { + if ( !firstLoop ) + { + break; + } + else + { + firstLoop = false; + prevId = entries->size() - 1; + } + } + } + if ( pagePrev ) + { + pagePrev->setText(txt.c_str()); + } + } + + { + std::string txt = Language::get(6180); + int nextId = -1; + if ( (id + 1) < entries->size() ) + { + nextId = id + 1; + } + else if ( id == entries->size() - 1 ) + { + nextId = 0; + } + + bool firstLoop = true; + while ( nextId >= 0 ) + { + if ( nextId == id ) + { + // looped around once, quit + txt = ""; + break; + } + if ( (*entries)[nextId].first != "-" ) // not a divider slot + { + auto findUnlock = unlockStatus->find((*entries)[nextId].first); + if ( findUnlock != unlockStatus->end() && findUnlock->second != Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + txt += (*entries)[nextId].second; + break; + } + } + ++nextId; + if ( nextId >= entries->size() ) + { + if ( !firstLoop ) + { + break; + } + else + { + firstLoop = false; + nextId = 0; + } + } + } + if ( pageNext ) + { + pageNext->setText(txt.c_str()); + } + } + + { + char buf[32] = ""; + int numEntries = 0; + int pageNum = 0; + int index = -1; + for ( auto& e : *entries ) + { + ++index; + if ( e.first != "-" ) + { + if ( index == id ) + { + pageNum = numEntries; + } + ++numEntries; + } + } + snprintf(buf, sizeof(buf), Language::get(6181), pageNum + 1, (int)numEntries); + if ( pageNumberLeft ) + { + pageNumberLeft->setText(buf); + } + if ( pageNumberRight ) + { + pageNumberRight->setText(buf); + } + } + } } if ( compendium_current == "codex" ) { - refreshCompendiumEntryCodex(content, compendiumFrame); + refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], compendiumFrame); } else if ( compendium_current == "items" ) { - refreshCompendiumEntryItemsBlurb(content, compendiumFrame); - refreshCompendiumEntryItemsList(content, compendiumFrame); + refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame); } else if ( compendium_current == "magic" ) { - refreshCompendiumEntryItemsBlurb(content, compendiumFrame); - refreshCompendiumEntryItemsList(content, compendiumFrame); + refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame); } else if ( compendium_current == "world" ) { - refreshCompendiumEntryWorld(content, compendiumFrame); + refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], compendiumFrame); } else if ( compendium_current == "monsters" ) { - if ( auto monster = createCompendiumMonster((Monster)CompendiumEntries.monsters[content].monsterType, 8, 8) ) + if ( auto monster = createCompendiumMonster((Monster)CompendiumEntries.monsters[compendium_contents_current[compendium_current]].monsterType, 8, 8) ) { if ( auto myStats = monster->getStats() ) { @@ -34426,7 +35044,7 @@ namespace MainMenu { monsterAnimate(compendiumMonster, myStats, 0.0); } } - refreshCompendiumEntryMonster(content, compendiumFrame); + refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], compendiumFrame); } }; @@ -34434,11 +35052,17 @@ namespace MainMenu { { if ( auto contents = frame->findFrame("contents") ) { - auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents : nullptr)))); + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[compendium_sorting] + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] : nullptr)))); + + auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); auto toRemove = contents->getEntries(); for ( auto r : toRemove ) @@ -34446,32 +35070,187 @@ namespace MainMenu { contents->removeEntry(r->name.c_str(), false); } + contents->remove("notifs"); + auto contents_notif = contents->addFrame("notifs"); + contents_notif->enableScroll(false); + contents_notif->setAllowScrollBinds(false); + contents_notif->setHollow(true); + contents_notif->setClickable(false); + contents_notif->setTickCallback([](Widget& widget) { + auto frame = static_cast(&widget); + static Uint32 notifCycleTicks = 0; + static int notifAnim = 0; + if ( ticks != notifCycleTicks ) + { + notifCycleTicks = ticks; + if ( ticks % (TICKS_PER_SECOND / 5) == 0 ) + { + notifAnim++; + if ( notifAnim > 2 ) + { + notifAnim = 0; + } + + std::string imgPath = "*#images/ui/Inventory/tooltips/ExclamationAnim00.png"; + switch ( notifAnim ) + { + case 1: + imgPath = "*#images/ui/Inventory/tooltips/ExclamationAnim01.png"; + break; + case 2: + imgPath = "*#images/ui/Inventory/tooltips/ExclamationAnim02.png"; + break; + default: + break; + } + + for ( auto img : frame->getImages() ) + { + img->path = imgPath; + } + } + } + }); + + auto imgs = contents->getImages(); + for ( auto i : imgs ) + { + if ( i->name == "entry div" ) + { + contents->remove(i->name.c_str()); + } + if ( i->name == "notif" ) + { + contents->remove(i->name.c_str()); + } + } + if ( entries ) { - int indexToSelect = 0; + int indexToSelect = -1; for ( int i = 0; i < entries->size(); ++i ) { auto& data = (*entries)[i]; - auto entry = contents->addEntry(data.second.c_str(), true); + auto entry = contents->addEntry(data.first.c_str(), true); + + bool unlocked = false; + bool drawNotification = false; + entry->click = contents_activate_fn; entry->ctrlClick = contents_activate_fn; //entry->highlight = selection_fn; //entry->selected = selection_fn; - entry->color = compendiumContentsDefaultColor; - entry->text = data.first; entry->clickable = true; + + if ( data.first == "-" ) + { + entry->click = nullptr; + entry->ctrlClick = nullptr; + entry->clickable = false; + entry->text = data.second; + entry->color = makeColorRGB(42, 22, 18); + + contents->addImage(SDL_Rect{ 0, (int)(contents->getEntries().size() - 1) * contents->getEntrySize(), contents->getSize().w, 20 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/Divider_00.png", + "entry div"); + } + else + { + if ( unlockStatus ) + { + auto findUnlock = unlockStatus->find(data.first); + if ( findUnlock != unlockStatus->end() ) + { + unlocked = findUnlock->second > Compendium_t::LOCKED_UNKNOWN; + drawNotification = unlocked + && (findUnlock->second == Compendium_t::UNLOCKED_UNVISITED + || findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED); + } + else + { + (*unlockStatus)[data.first] = Compendium_t::LOCKED_UNKNOWN; + } +#if !defined(NDEBUG) + if ( *cvar_compendium_reveal ) + { + unlocked = true; + } +#endif + } + + if ( !unlocked ) + { + entry->color = compendiumContentsDefaultColor; + entry->text = "???"; + entry->click = nullptr; + entry->ctrlClick = nullptr; + } + else + { + entry->color = compendiumContentsDefaultColor; + entry->text = data.second; + + int textWidth = 0; + while ( entry->text.size() > 5 ) + { + if ( auto textGet = Text::get(entry->text.c_str(), contents->getFont(), + makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) ) + { + textWidth = textGet->getWidth(); + if ( contents->getListOffset().x + textWidth > contents->getSize().w - 4 ) + { + auto find = entry->text.find("..."); + if ( find == std::string::npos ) + { + entry->text += "..."; + } + else + { + entry->text = entry->text.erase(find - 1, 1); + } + } + else + { + break; + } + } + } + } + } memcpy(&entry->data, &i, sizeof(i)); - if ( compendium_contents_current != "" ) + if ( data.first != "-" ) { - if ( compendium_contents_current == data.first ) + if ( indexToSelect == -1 ) + { + indexToSelect = i; // grab the first available slot + } + if ( compendium_contents_current[compendium_current] != "" ) { - indexToSelect = i; + if ( compendium_contents_current[compendium_current] == data.first ) + { + indexToSelect = i; + } } } + + if ( drawNotification ) + { + contents_notif->setSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getActualSize().h }); + std::string imgName = "notif_" + data.first; + contents_notif->addImage(SDL_Rect{ 4, 4 + (int)(contents->getEntries().size() - 1) * contents->getEntrySize(), 6, 14 }, + 0xFFFFFFFF, + "*#images/ui/Inventory/tooltips/ExclamationAnim00.png", + imgName.c_str()); + } } + contents->setSelection(indexToSelect); - contents->scrollToSelection(true); + SDL_Rect actualPos = contents->getActualSize(); + actualPos.y = 0; + contents->setActualSize(actualPos); + contents->scrollToSelection(false); contents->activateSelection(); } } @@ -34481,11 +35260,13 @@ namespace MainMenu { { if ( !page_right ) { return; } + compendiumPageRightHideUnlocked(page_right, false, true); + Frame* page_right_inner = page_right->findFrame("page_right_inner"); if ( !page_right_inner ) { page_right_inner = page_right->addFrame("page_right_inner"); - page_right_inner->setSize(SDL_Rect{ 10, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); + page_right_inner->setSize(SDL_Rect{ 14, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); page_right_inner->setTickCallback([](Widget& widget) { static_cast(&widget)->setAllowScrollBinds(true); @@ -34497,7 +35278,7 @@ namespace MainMenu { { if ( page_right_overlay = page_right->addFrame("page_right_overlay") ) { - page_right_overlay->setSize(SDL_Rect{ 6, page_right->getSize().h - 114 - 14, page_right->getSize().w - 16, 120 }); + page_right_overlay->setSize(SDL_Rect{ 10, page_right->getSize().h - 114 - 18, page_right->getSize().w - 20 - 8, 120 }); page_right_overlay->addImage(SDL_Rect{ 0, 0, page_right_overlay->getSize().w, page_right_overlay->getSize().h }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Records_Frame_00.png", "page right overlay img"); @@ -34510,7 +35291,7 @@ namespace MainMenu { heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); heading->setColor(makeColor(198, 190, 179, 255)); heading->setTickCallback([](Widget& widget) { - if ( ticks % TICKS_PER_SECOND == 0 && compendiumRecordsProcessedOnTick != ticks ) + if ( ticks % (2 * TICKS_PER_SECOND) == 0 && compendiumRecordsProcessedOnTick != ticks ) { compendiumRecordsSectionRandSequence++; compendiumRecordsProcessedOnTick = ticks; @@ -34977,7 +35758,13 @@ namespace MainMenu { [](int argc, const char** argv) { CompendiumEntries.exportCurrentMonster(compendiumMonster); }); - static ConsoleVariable cvar_compendiumautoreload("/compendiumautoreload", true); + static ConsoleVariable cvar_compendiumautoreload("/compendiumautoreload", +#if !defined(NDEBUG) + true +#else + false +#endif + ); static void compendiumDebugRefresh() { @@ -34985,23 +35772,23 @@ namespace MainMenu { if ( compendium_current == "monsters" ) { CompendiumEntries.readMonstersFromFile(); - refreshCompendiumEntryMonster(Compendium_t::CompendiumMonsters_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); } else if ( compendium_current == "world" ) { CompendiumEntries.readWorldFromFile(); - refreshCompendiumEntryWorld(Compendium_t::CompendiumWorld_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); } else if ( compendium_current == "codex" ) { CompendiumEntries.readCodexFromFile(); - refreshCompendiumEntryCodex(Compendium_t::CompendiumCodex_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); } else if ( compendium_current == "items" ) { CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); - refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumItems_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); std::string modelsPath = "items_single"; if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) { @@ -35013,7 +35800,7 @@ namespace MainMenu { { CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); - refreshCompendiumEntryItemsBlurb(Compendium_t::CompendiumMagic_t::contentsMap[compendium_contents_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); std::string modelsPath = "items_single"; if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) { @@ -35023,7 +35810,147 @@ namespace MainMenu { } } + static void compendiumRevealSection(Button* button) + { + if ( !button ) { return; } + Frame* parent = static_cast(button->getParent()); + if ( !parent ) { return; } + parent = parent->getParent(); + if ( !parent ) { return; } + + if ( auto page_right = parent->findFrame("page_right") ) + { + page_right->setOpacity(0.0); + page_right->setInvisible(true); + } + if ( auto bg = parent->findImage("page right img") ) + { + bg->disabled = true; + } + + if ( auto page_right_unlock = parent->findFrame("page_right_unlock") ) + { + if ( auto to_unlock = page_right_unlock->findField("to_unlock") ) + { + if ( compendium_contents_current[compendium_current] == to_unlock->getText() ) + { + to_unlock->setText(""); + auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); + + if ( unlockStatus ) + { + auto findUnlock = unlockStatus->find(compendium_contents_current[compendium_current]); + if ( findUnlock != unlockStatus->end() ) + { + if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + findUnlock->second = Compendium_t::UNLOCKED_UNVISITED; + } + else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_VISITED ) + { + findUnlock->second = Compendium_t::UNLOCKED_VISITED; + } + } + } + } + } + } + + if ( auto reveal_top = parent->findFrame("page_right_reveal_top") ) + { + auto page_right_reveal = reveal_top->addFrame("page_right_reveal_top"); + page_right_reveal->setSize(SDL_Rect{ 0, 0, 398, 484 }); + page_right_reveal->addImage(SDL_Rect{ 0, 0, 398, 484 }, 0xFFFFFFFF, + "", "img"); + page_right_reveal->setHollow(true); + page_right_reveal->setClickable(false); + + page_right_reveal->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto frame = const_cast((Frame*)(&widget)); + static Uint32 animTicks = 0; + + if ( frame->getTicks() == 0 || frame->getTicks() == 1 ) + { + animTicks = 0; + compendiumRevealAnimState = 1; + } + + if ( ticks != animTicks ) + { + animTicks = ticks; + if ( ticks % 2 == 0 ) + { + if ( compendiumRevealAnimState > 0 ) + { + ++compendiumRevealAnimState; + } + } + } + if ( frame->getImages().size() > 0 ) + { + auto img = frame->getImages().front(); + img->disabled = true; + if ( compendiumRevealAnimState >= 1 && compendiumRevealAnimState <= 39 ) + { + img->disabled = false; + img->path = "*images/ui/Main Menus/AdventureArchives/Anim/reveal-anim"; + char buf[16]; + snprintf(buf, sizeof(buf), "%02d.png", compendiumRevealAnimState); + img->path += buf; + + Frame* parent = frame->getParent(); + if ( parent ) + { + parent = parent->getParent(); + } + if ( parent ) + { + if ( /*Frame* parent = parent->getParent()*/true ) + { + if ( auto page_right = parent->findFrame("page_right") ) + { + if ( compendiumRevealAnimState >= 19 ) + { + if ( auto bg = parent->findImage("page right img") ) + { + bg->disabled = false; + } + } + if ( compendiumRevealAnimState >= 10 ) + { + page_right->setInvisible(false); + real_t opacity = page_right->getOpacity() / 100.0; + real_t setpointDiff = getFPSScale(144.0) * std::max(.01, (1.0 - opacity)) / (20.0); + opacity += setpointDiff; + opacity = std::min(1.0, opacity); + page_right->setOpacity(opacity * 100.0); + } + else + { + page_right->setOpacity(0.0); + } + } + } + } + } + else + { + animTicks = 0; + compendiumRevealAnimState = 1; + frame->removeSelf(); + } + } + }); + } + } + static void openCompendium() { + players[0]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; + auto dimmer = main_menu_frame->addFrame("dimmer"); dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); dimmer->setActualSize(dimmer->getSize()); @@ -35365,7 +36292,7 @@ namespace MainMenu { auto nav_title = navigation->addField("nav_title", 128); nav_title->setFont("fonts/kongtext.ttf#16#0"); - nav_title->setText("CONTENTS"); + nav_title->setText(Language::get(6182)); nav_title->setHJustify(Field::justify_t::CENTER); nav_title->setVJustify(Field::justify_t::TOP); nav_title->setSize(SDL_Rect{ 0, 12, navigation->getSize().w, 28 }); @@ -35379,12 +36306,12 @@ namespace MainMenu { ); auto contents = navigation->addFrame("contents"); - contents->setSize(SDL_Rect{ nav_contents_bg->pos.x + 10, nav_contents_bg->pos.y + 14, 184, 436 }); + contents->setSize(SDL_Rect{ nav_contents_bg->pos.x + 6, nav_contents_bg->pos.y + 14, 184 + 12, 436 }); contents->setActualSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getSize().h }); contents->setEntrySize(18); contents->setFont(smallfont_no_outline); contents->setSelectedEntryColor(makeColor(71, 41, 27, 255)); - contents->setListOffset(SDL_Rect{ 32, 1, 0, 0 }); + contents->setListOffset(SDL_Rect{ 12, 1, 0, 0 }); contents->setScrollWithLeftControls(false); contents->setClickable(true); contents->setSelectorOffset(SDL_Rect{ -10, -14, 28, 28 }); @@ -35400,6 +36327,70 @@ namespace MainMenu { } }); + { + auto nav_slider = navigation->addSlider("nav_slider"); + nav_slider->setRailSize(SDL_Rect{ + navigation->getSize().w - 20 - 16, + 24 + 22, 20, navigation->getSize().h - 32 - 22 - 38 }); + nav_slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 }); + nav_slider->setBorder(16); + nav_slider->setOntop(true); + nav_slider->setHandleImage("*#images/ui/Sliders/HUD_Magic_Slider_Emerald_01.png"); + nav_slider->setValue(0); + nav_slider->setMinValue(0); + nav_slider->setMaxValue(100); + nav_slider->setOrientation(Slider::SLIDER_VERTICAL); + nav_slider->setHideGlyphs(true); + nav_slider->setHideKeyboardGlyphs(true); + nav_slider->setHideSelectors(true); + nav_slider->setMenuConfirmControlType(0); + nav_slider->setCallback([](Slider& slider) { + if ( auto frame = static_cast(slider.getParent()) ) + { + if ( frame = frame->findFrame("contents") ) + { + if ( frame->getActualSize().h > frame->getSize().h ) + { + SDL_Rect pos = frame->getActualSize(); + const int diff = frame->getActualSize().h - frame->getSize().h; + pos.y = static_cast(diff * slider.getValue() / 100.0); + frame->setActualSize(pos); + } + else + { + slider.setValue(0); + } + } + } + }); + nav_slider->setTickCallback([](Widget& widget) { + auto slider = static_cast(&widget); + if ( auto frame = static_cast(widget.getParent()) ) + { + if ( frame = frame->findFrame("contents") ) + { + if ( frame->getActualSize().h > frame->getSize().h ) + { + widget.setInvisible(false); + + const int diff = frame->getActualSize().h - frame->getSize().h; + slider->setValue(100.0 * frame->getActualSize().y / diff); + } + else + { + widget.setInvisible(true); + + slider->setValue(0); + if ( widget.isSelected() ) + { + widget.deselect(); + } + } + } + } + }); + } + auto page_left = window->addFrame("page_left"); page_left->setSize(SDL_Rect{ background->pos.x + 460 - 22 - 382, background->pos.y + 46, 382, 472 }); auto left_top_img = page_left->addImage(SDL_Rect{ 0, 0, 382, 330 }, 0xFFFFFFFF, @@ -35493,6 +36484,11 @@ namespace MainMenu { }); // debug btns +#if !defined(NDEBUG) + if ( true ) +#else + if ( false ) +#endif { auto debugBtnFrame = page_left->addFrame("debug frame"); debugBtnFrame->setSize(model_viewer->getSize()); @@ -35669,13 +36665,44 @@ namespace MainMenu { }); } + auto page_right_reveal_top = window->addFrame("page_right_reveal_top"); + auto page_right = window->addFrame("page_right"); - page_right->setSize(SDL_Rect{ background->pos.x + 480 + 40, background->pos.y + 38, 386, 472}); - page_right->addImage(SDL_Rect{ 0, 0, 386, 472 }, 0xFFFFFFFF, + page_right->setSize(SDL_Rect{ background->pos.x + 480 + 36, background->pos.y + 30, 398, 484}); + page_right->setInvisible(true); + page_right->setInheritParentFrameOpacity(false); + + auto page_right_unlock = window->addFrame("page_right_unlock"); + page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 18, 376, 116 }); + page_right_unlock->setHollow(true); + page_right_unlock->setClickable(false); + + auto page_right_to_unlock = page_right_unlock->addField("to_unlock", 128); + page_right_to_unlock->setDisabled(true); // hidden, just to save data somewhere + page_right_to_unlock->setText(""); + + auto page_right_unlock_btn = page_right_unlock->addButton("page_right_unlock_btn"); + page_right_unlock_btn->setSize(SDL_Rect{ 0, 0, 376, 116 }); + page_right_unlock_btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); + page_right_unlock_btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); + page_right_unlock_btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-press_00.png"); + page_right_unlock_btn->setOntop(true); + page_right_unlock_btn->setCallback([](Button& button) { + compendiumRevealSection(&button); + button.setInvisible(true); + button.setDisabled(true); + }); + + auto right_bg = window->addImage(SDL_Rect{ page_right->getSize().x + 4, page_right->getSize().y + 8, 386, 472 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Details_Frame_00.png", "page right img"); + right_bg->disabled = true; + + page_right_reveal_top->setHollow(true); + page_right_reveal_top->setClickable(false); + page_right_reveal_top->setSize(SDL_Rect{ page_right->getSize().x, page_right->getSize().y, 398, 484 }); auto page_right_inner = page_right->addFrame("page_right_inner"); - page_right_inner->setSize(SDL_Rect{ 10, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); + page_right_inner->setSize(SDL_Rect{ 14, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); page_right_inner->setTickCallback([](Widget& widget) { static_cast(&widget)->setAllowScrollBinds(true); @@ -35686,7 +36713,7 @@ namespace MainMenu { page_right_title->setText("DETAILS"); page_right_title->setHJustify(Field::justify_t::CENTER); page_right_title->setVJustify(Field::justify_t::TOP); - page_right_title->setSize(SDL_Rect{ page_right->getSize().x, page_right->getSize().y - 18, page_right->getSize().w, 28 }); + page_right_title->setSize(SDL_Rect{ page_right->getSize().x + 8, page_right->getSize().y - 10, page_right->getSize().w - 24, 28 }); page_right_title->setColor(makeColor(42, 22, 18, 255)); auto right_slider_bg = page_right->addImage(SDL_Rect{ @@ -35700,7 +36727,7 @@ namespace MainMenu { auto right_slider = page_right->addSlider("right_slider"); right_slider->setRailSize(SDL_Rect{ page_right_inner->getSize().x + page_right_inner->getSize().w - 20, - page_right_inner->getSize().y + 20 + 4, 20, compendiumPageRightInnerHeight - 8 }); + page_right_inner->getSize().y + 20 + 4, 20, compendiumPageRightInnerHeight - 8 - 24 }); //right_slider->setRailImage("*images/ui/Main Menus/AdventureArchives/C_Details_ScrollBar_00.png"); right_slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 }); right_slider->setBorder(16); @@ -35761,6 +36788,38 @@ namespace MainMenu { } }); + /*auto page_right_number = window->addField("page_right_number", 32); + page_right_number->setFont(smallfont_no_outline); + page_right_number->setText(""); + page_right_number->setHJustify(Field::justify_t::CENTER); + page_right_number->setVJustify(Field::justify_t::TOP); + page_right_number->setSize(SDL_Rect{ page_right->getSize().x + 156, page_right->getSize().y + 497 + 8, 72, 28 }); + page_right_number->setColor(makeColorRGB(77, 37, 16));*/ + + auto page_left_number = window->addField("page_left_number", 32); + page_left_number->setFont(smallfont_no_outline); + page_left_number->setText(""); + page_left_number->setHJustify(Field::justify_t::CENTER); + page_left_number->setVJustify(Field::justify_t::TOP); + page_left_number->setSize(SDL_Rect{ page_left->getSize().x + 156, page_right->getSize().y + 497 + 8, 72, 28 }); + page_left_number->setColor(makeColorRGB(77, 37, 16)); + + auto page_right_next = window->addField("page_right_next", 128); + page_right_next->setFont(smallfont_no_outline); + page_right_next->setText(""); + page_right_next->setHJustify(Field::justify_t::RIGHT); + page_right_next->setVJustify(Field::justify_t::TOP); + page_right_next->setSize(SDL_Rect{ page_right->getSize().x + 4, page_right->getSize().y + 475 + 8, 378, 28 }); + page_right_next->setColor(makeColorRGB(135, 94, 45)); + + auto page_left_prev = window->addField("page_left_prev", 128); + page_left_prev->setFont(smallfont_no_outline); + page_left_prev->setText(""); + page_left_prev->setHJustify(Field::justify_t::LEFT); + page_left_prev->setVJustify(Field::justify_t::TOP); + page_left_prev->setSize(SDL_Rect{ page_left->getSize().x + 6, page_right->getSize().y + 475 + 8, 378, 28 }); + page_left_prev->setColor(makeColorRGB(135, 94, 45)); + compendiumPopulatePageRight(page_right); compendiumPopulateContents(window); From b789394bc8b0e4b12f9c68569ce2b0ebac128082 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 30 Jul 2024 08:11:58 +1000 Subject: [PATCH 035/244] * i want it all achievement fix to conjurer/hunter --- src/menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu.cpp b/src/menu.cpp index 00ea04126..19d2e4246 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9333,7 +9333,7 @@ void doNewGame(bool makeHighscore) { } // new achievement usedAllClasses = true; - for ( int c = 0; c <= CLASS_HUNTER; ++c ) + for ( int c = CLASS_CONJURER; c <= CLASS_HUNTER; ++c ) { if ( !usedClass[c] ) { From 8ba349c986e2a62e2a747f679e3585ec6f7ae04a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 30 Jul 2024 08:14:25 +1000 Subject: [PATCH 036/244] * another i want it all condition fix --- src/menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu.cpp b/src/menu.cpp index 19d2e4246..7d9953061 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9341,7 +9341,7 @@ void doNewGame(bool makeHighscore) { } } bool usedAllRaces = true; - for ( int c = RACE_HUMAN; c <= RACE_INSECTOID; ++c ) + for ( int c = RACE_SKELETON; c <= RACE_INSECTOID; ++c ) { if ( !usedRace[c] ) { From fe75484f5fdd8b1ee12a1928dbe0cfdc1ee03866 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 30 Jul 2024 18:33:35 +1000 Subject: [PATCH 037/244] * sleep spellbook in mystic library toggles between that and magic missile to not softlock --- src/entity.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index ddc42ebf8..2800bb314 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7159,8 +7159,21 @@ void Entity::attack(int pose, int charge, Entity* target) castSpell(uid, &spell_lightning, true, false); break; case SPELLBOOK_SLEEP: - castSpell(uid, &spell_sleep, true, false); + { + spell_t* spell = &spell_sleep; + if ( Entity* target = uidToEntity(this->monsterTarget) ) + { + if ( Stat* stats = target->getStats() ) + { + if ( target->behavior == &actPlayer && stats->EFFECTS[EFF_ASLEEP] ) + { + spell = &spell_magicmissile; + } + } + } + castSpell(uid, spell, true, false); break; + } case SPELLBOOK_CONFUSE: castSpell(uid, &spell_confuse, true, false); break; From a4ea64d92949240764c6682627a4d7993f77e017 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 31 Jul 2024 09:12:03 +1000 Subject: [PATCH 038/244] * controller support 5/6 tabs for compendium * refactor achievements variables into compendium, add tab for achievements --- src/eos.cpp | 63 +- src/init.cpp | 6 - src/init_game.cpp | 186 +++- src/input.cpp | 2 + src/interface/ui_general.cpp | 12 +- src/main.cpp | 11 +- src/main.hpp | 11 +- src/menu.cpp | 2 +- src/mod_tools.cpp | 111 +- src/mod_tools.hpp | 44 + src/steam.cpp | 29 +- src/ui/Frame.cpp | 131 ++- src/ui/Frame.hpp | 15 + src/ui/MainMenu.cpp | 1868 +++++++++++++++++++++++++++++++--- 14 files changed, 2192 insertions(+), 299 deletions(-) diff --git a/src/eos.cpp b/src/eos.cpp index 143d52efb..e87a8cf1f 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -2945,28 +2945,28 @@ void EOS_CALL EOSFuncs::OnAchievementQueryComplete(const EOS_Achievements_OnQuer if (AchievementDef->bIsHidden) { - achievementHidden.emplace(std::string(AchievementDef->AchievementId)); + //achievementHidden.emplace(std::string(AchievementDef->AchievementId)); } if (AchievementDef->UnlockedDisplayName) { - achievementNames.emplace(std::make_pair( + /*achievementNames.emplace(std::make_pair( std::string(AchievementDef->AchievementId), - std::string(AchievementDef->UnlockedDisplayName))); + std::string(AchievementDef->UnlockedDisplayName)));*/ } if (AchievementDef->UnlockedDescription) { - auto result = achievementDesc.emplace(std::make_pair( - std::string(AchievementDef->AchievementId), - std::string(AchievementDef->UnlockedDescription))); - if (result.second == true) // insertion success - { - if (result.first->second.back() != '.') // add punctuation if missing. - { - result.first->second += '.'; - } - } + //auto result = achievementDesc.emplace(std::make_pair( + // std::string(AchievementDef->AchievementId), + // std::string(AchievementDef->UnlockedDescription))); + //if (result.second == true) // insertion success + //{ + // if (result.first->second.back() != '.') // add punctuation if missing. + // { + // result.first->second += '.'; + // } + //} } // Release Achievement Definition @@ -3011,42 +3011,29 @@ void EOSFuncs::OnPlayerAchievementQueryComplete(const EOS_Achievements_OnQueryPl { if (PlayerAchievement->UnlockTime != EOS_ACHIEVEMENTS_ACHIEVEMENT_UNLOCKTIME_UNDEFINED) { - achievementUnlockedLookup.insert(std::string(PlayerAchievement->AchievementId)); - - auto find = achievementUnlockTime.find(PlayerAchievement->AchievementId); - if (find == achievementUnlockTime.end()) - { - achievementUnlockTime.emplace(std::make_pair(std::string(PlayerAchievement->AchievementId), PlayerAchievement->UnlockTime)); - } - else - { - find->second = PlayerAchievement->UnlockTime; - } + Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(PlayerAchievement->AchievementId); + Compendium_t::achievements[PlayerAchievement->AchievementId].unlockTime = PlayerAchievement->UnlockTime; + auto& achData = Compendium_t::achievements[PlayerAchievement->AchievementId]; + achData.unlocked = true; + achData.unlockTime = PlayerAchievement->UnlockTime; } if (PlayerAchievement->StatInfoCount > 0) { - for (int statNum = 0; statNum < NUM_STEAM_STATISTICS; ++statNum) + /*for (int statNum = 0; statNum < NUM_STEAM_STATISTICS; ++statNum) { if (steamStatAchStringsAndMaxVals[statNum].first.compare(PlayerAchievement->AchievementId) == 0) { - auto find = achievementProgress.find(PlayerAchievement->AchievementId); - if (find == achievementProgress.end()) - { - achievementProgress.emplace(std::make_pair(std::string(PlayerAchievement->AchievementId), statNum)); - } - else - { - find->second = statNum; - } + Compendium_t::achievements[PlayerAchievement->AchievementId].achievementProgress = statNum; break; } - } + }*/ } } EOS_Achievements_PlayerAchievement_Release(PlayerAchievement); } + Compendium_t::AchievementData_t::achievementsNeedFirstData = false; EOS.Achievements.playerDataLoaded = true; sortAchievementsForDisplay(); @@ -3119,9 +3106,9 @@ void EOSFuncs::loadAchievementData() if (!Achievements.definitionsLoaded) { Achievements.definitionsAwaitingCallback = true; - achievementNames.clear(); - achievementDesc.clear(); - achievementHidden.clear(); + //achievementNames.clear(); + //achievementDesc.clear(); + //achievementHidden.clear(); EOS_Achievements_QueryDefinitionsOptions Options{}; Options.ApiVersion = EOS_ACHIEVEMENTS_QUERYDEFINITIONS_API_LATEST; //Options.EpicUserId = EOSFuncs::Helpers_t::epicIdFromString(EOS.CurrentUserInfo.epicAccountId.c_str()); diff --git a/src/init.cpp b/src/init.cpp index 5045338ee..601aa50d7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1279,12 +1279,6 @@ int deinitApp() free(sprites); } - // free achievement images - for (auto& item : achievementImages) { - SDL_FreeSurface(item.second); - } - achievementImages.clear(); - // free models printlog("freeing models...\n"); if (models != nullptr) { diff --git a/src/init_game.cpp b/src/init_game.cpp index a678036ae..26142d924 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -99,6 +99,7 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readWorldFromFile(); CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); + Compendium_t::AchievementData_t::readContentsLang(); Compendium_t::Events_t::readEventsTranslations(); Compendium_t::readUnlocksSaveData(); Compendium_t::Events_t::loadItemsSaveData(); @@ -174,19 +175,6 @@ int initGame() updateLoadingScreen(90); doLoadingScreen(); - // load achievement images -#ifdef NINTENDO - Directory achievementsDir(BASE_DATA_DIR"/images/achievements"); -#else - Directory achievementsDir("images/achievements"); -#endif - for (auto& item : achievementsDir.list) - { - std::string fullPath = achievementsDir.path + std::string("/") + item; - char* name = const_cast(fullPath.c_str()); // <- evil - achievementImages.emplace(std::make_pair(item, loadImage(name))); - } - // load item types initGameDatafiles(false); @@ -810,7 +798,7 @@ void loadAchievementData(const char* path) { return; } - char buf[65536]; + char buf[120000]; int count = (int)fp->read(buf, sizeof(buf[0]), sizeof(buf)); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -837,6 +825,12 @@ void loadAchievementData(const char* path) { continue; } #endif +#ifndef STEAMWORKS + if ( !strcmp(achName, "BARONY_ACH_CARTOGRAPHER") ) + { + continue; + } +#endif #ifndef USE_PLAYFAB if ( !strcmp(achName, "BARONY_ACH_BLOOM_PLANTED") ) { @@ -860,35 +854,126 @@ void loadAchievementData(const char* path) { } #endif const auto& ach = it.value.GetObject(); + auto& achData = Compendium_t::achievements[achName]; if (ach.HasMember("name") && ach["name"].IsString()) { - achievementNames[achName] = ach["name"].GetString(); + achData.name = ach["name"].GetString(); } if (ach.HasMember("description") && ach["description"].IsString()) { - achievementDesc[achName] = ach["description"].GetString(); + achData.desc = ach["description"].GetString(); } if (ach.HasMember("hidden") && ach["hidden"].IsBool()) { - if (ach["hidden"].GetBool()) { - achievementHidden.emplace(achName); + achData.hidden = ach["hidden"].GetBool(); + } + if ( ach.HasMember("category") ) + { + achData.category = ach["category"].GetString(); + } + if ( ach.HasMember("lore_points") ) + { + achData.lorePoints = ach["lore_points"].GetInt(); + } + + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_NORMAL; + if ( ach.HasMember("dlc") ) + { + if ( ach["dlc"].IsString() ) + { + if ( !strcmp(ach["dlc"].GetString(), "myths_outcasts") ) + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC1; + } + else if ( !strcmp(ach["dlc"].GetString(), "legends_pariahs") ) + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC2; + } + } + else if ( ach["dlc"].IsArray() ) + { + for ( auto it = ach["dlc"].Begin(); it != ach["dlc"].End(); ++it ) + { + if ( it->IsString() ) + { + if ( !strcmp(it->GetString(), "myths_outcasts") ) + { + if ( achData.dlcType == Compendium_t::AchievementData_t::ACH_TYPE_DLC2 ) + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC1_DLC2; + } + else + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC1; + } + } + else if ( !strcmp(it->GetString(), "legends_pariahs") ) + { + if ( achData.dlcType == Compendium_t::AchievementData_t::ACH_TYPE_DLC1 ) + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC1_DLC2; + } + else + { + achData.dlcType = Compendium_t::AchievementData_t::ACH_TYPE_DLC2; + } + } + } + } } } } + for ( int statNum = 0; statNum < NUM_STEAM_STATISTICS; ++statNum ) + { + if ( steamStatAchStringsAndMaxVals[statNum].first != "BARONY_ACH_NONE" ) + { + Compendium_t::achievements[steamStatAchStringsAndMaxVals[statNum].first].achievementProgress = statNum; + } + } + sortAchievementsForDisplay(); } + void sortAchievementsForDisplay() { - achievementsNeedResort = false; +#ifdef STEAMWORKS + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + if ( SteamUser()->BLoggedOn() ) + { + Compendium_t::AchievementData_t::achievementsNeedFirstData = false; + + for ( auto& achData : Compendium_t::achievements ) + { + Uint32 time = 0; + bool unlocked = false; + SteamUserStats()->GetAchievementAndUnlockTime(achData.first.c_str(), &achData.second.unlocked, &time); + if ( achData.second.unlocked ) + { + achData.second.unlockTime = time; + } + } + } + } +#endif + + Compendium_t::AchievementData_t::achievementsNeedResort = false; // sort achievements list - achievementNamesSorted.clear(); - Comparator compFunctor = + Compendium_t::AchievementData_t::achievementNamesSorted.clear(); + std::vector> names; + for ( auto& achData : Compendium_t::achievements ) + { + names.push_back(std::make_pair(achData.first, achData.second.name)); + } + Compendium_t::AchievementData_t::Comparator compFunctor = [](std::pair lhs, std::pair rhs) { - bool ach1 = achievementUnlocked(lhs.first.c_str()); - bool ach2 = achievementUnlocked(rhs.first.c_str()); - bool lhsAchIsHidden = (achievementHidden.find(lhs.first) != achievementHidden.end()); - bool rhsAchIsHidden = (achievementHidden.find(rhs.first) != achievementHidden.end()); + auto& achData1 = Compendium_t::achievements[lhs.first]; + auto& achData2 = Compendium_t::achievements[rhs.first]; + bool ach1 = achData1.unlocked; + bool ach2 = achData2.unlocked; + bool lhsAchIsHidden = achData1.hidden; + bool rhsAchIsHidden = achData2.hidden; if ( ach1 && !ach2 ) { return true; @@ -918,6 +1003,53 @@ void sortAchievementsForDisplay() return lhs.second < rhs.second; } }; - std::set, Comparator> sorted(achievementNames.begin(), achievementNames.end(), compFunctor); - achievementNamesSorted.swap(sorted); + + std::set, Compendium_t::AchievementData_t::Comparator> sorted( + names.begin(), + names.end(), + compFunctor); + Compendium_t::AchievementData_t::achievementNamesSorted.swap(sorted); + Compendium_t::AchievementData_t::achievementCategories.clear(); + for ( auto& entry : Compendium_t::AchievementData_t::achievementNamesSorted ) + { + auto& achData = Compendium_t::achievements[entry.first]; + Compendium_t::AchievementData_t::achievementCategories[achData.category].push_back(entry); + } + + Compendium_t::AchievementData_t::achievementsBookDisplay.clear(); + for ( auto& entry : Compendium_t::AchievementData_t::achievementCategories ) + { + auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[entry.first]; + if ( entry.second.size() > 0 ) + { + achDisplay.pages.push_back(std::vector()); + } + int numEntries = 0; + bool foundHidden = false; + for ( auto& name : entry.second ) + { + auto& achData = Compendium_t::achievements[name.first]; + if ( foundHidden ) + { + if ( achData.hidden && !achData.unlocked ) + { + achDisplay.numHidden++; + } + // hidden, so allow only 1 entry to represent all the hidden ones + continue; + } + ++numEntries; + if ( numEntries > 1 && ((numEntries - 1) % 8 == 0) ) + { + achDisplay.pages.push_back(std::vector()); + } + auto& list = achDisplay.pages.back(); + list.push_back(name.first); + if ( achData.hidden && !achData.unlocked ) + { + foundHidden = true; + achDisplay.numHidden++; + } + } + } } diff --git a/src/input.cpp b/src/input.cpp index 94639d68d..920d690e7 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -54,6 +54,7 @@ void Input::defaultBindings() { inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuDown", (std::string("Pad") + std::to_string(c) + std::string("DpadY+")).c_str())); inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuConfirm", (std::string("Pad") + std::to_string(c) + std::string("ButtonA")).c_str())); inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuCancel", (std::string("Pad") + std::to_string(c) + std::string("ButtonB")).c_str())); + inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuListCancel", (std::string("Pad") + std::to_string(c) + std::string("ButtonB")).c_str())); #ifdef NINTENDO inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuAlt1", (std::string("Pad") + std::to_string(c) + std::string("ButtonY")).c_str())); inputs[c].gamepad_system_bindings.insert(std::make_pair("MenuAlt2", (std::string("Pad") + std::to_string(c) + std::string("ButtonX")).c_str())); @@ -174,6 +175,7 @@ void Input::defaultBindings() { inputs[c].kb_system_bindings.insert(std::make_pair("MenuDown", "Down")); inputs[c].kb_system_bindings.insert(std::make_pair("MenuConfirm", "Space")); inputs[c].kb_system_bindings.insert(std::make_pair("MenuCancel", "Escape")); + inputs[c].kb_system_bindings.insert(std::make_pair("MenuListCancel", "Escape")); //inputs[c].kb_system_bindings.insert(std::make_pair("MenuAlt1", "Left Shift")); //inputs[c].kb_system_bindings.insert(std::make_pair("MenuAlt2", "Left Ctrl")); inputs[c].kb_system_bindings.insert(std::make_pair("MenuStart", "Return")); diff --git a/src/interface/ui_general.cpp b/src/interface/ui_general.cpp index 25d503ca7..7a763dda1 100644 --- a/src/interface/ui_general.cpp +++ b/src/interface/ui_general.cpp @@ -897,10 +897,10 @@ void UIToastNotificationManager_t::createAchievementNotification(const char* nam const char* achievementName = "Unknown Achievement"; { - auto it = achievementNames.find(name); - if (it != achievementNames.end()) + auto it = Compendium_t::achievements.find(name); + if (it != Compendium_t::achievements.end()) { - achievementName = it->second.c_str(); + achievementName = it->second.name.c_str(); } } @@ -961,10 +961,10 @@ void UIToastNotificationManager_t::createStatisticUpdateNotification(const char* } else { - auto it = achievementNames.find(name); - if ( it != achievementNames.end() ) + auto it = Compendium_t::achievements.find(name); + if ( it != Compendium_t::achievements.end() ) { - achievementName = it->second.c_str(); + achievementName = it->second.name.c_str(); } const std::string imgName = unlocked ? std::string("*#images/achievements/") + name + std::string(".png"): diff --git a/src/main.cpp b/src/main.cpp index 7e3bb9608..3bf7b3c22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -511,16 +511,7 @@ SDL_Surface* font12x12_bmp = nullptr; SDL_Surface* font16x16_bmp = nullptr; SDL_Surface** sprites = nullptr; SDL_Surface** tiles = nullptr; -std::unordered_map achievementImages; -std::unordered_map achievementNames; -std::unordered_map achievementDesc; -std::unordered_set achievementHidden; -std::set, Comparator> achievementNamesSorted; -std::unordered_map achievementProgress; // ->second is the associated achievement stat index -std::unordered_map achievementUnlockTime; - -std::unordered_set achievementUnlockedLookup; -bool achievementsNeedResort = true; + Uint32 imgref = 1, vboref = 1; const Uint32 ttfTextCacheLimit = 9000; GLuint* texid = nullptr; diff --git a/src/main.hpp b/src/main.hpp index f0c408be7..2004f8ff6 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -791,16 +791,7 @@ extern SDL_Surface* font12x12_bmp; extern SDL_Surface* font16x16_bmp; extern SDL_Surface** sprites; extern SDL_Surface** tiles; -extern std::unordered_map achievementImages; -extern std::unordered_map achievementNames; -extern std::unordered_map achievementDesc; -extern std::unordered_set achievementHidden; -typedef std::function, std::pair)> Comparator; -extern std::set, Comparator> achievementNamesSorted; -extern std::unordered_map achievementProgress; -extern std::unordered_map achievementUnlockTime; -extern std::unordered_set achievementUnlockedLookup; -extern bool achievementsNeedResort; + extern voxel_t** models; extern polymodel_t* polymodels; extern bool useModelCache; diff --git a/src/menu.cpp b/src/menu.cpp index 7d9953061..b6fdaf9ed 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -10628,7 +10628,7 @@ void buttonAchievementsUp(button_t* my) void buttonAchievementsDown(button_t* my) { - int num_achievements = achievementNames.size(); + int num_achievements = Compendium_t::achievements.size(); if ( num_achievements == 0 ) { return; diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index d5f492edd..156ba6b0b 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -8171,10 +8171,12 @@ void LocalAchievements_t::readFromFile() ach.name = achievement->name.GetString(); ach.unlocked = achievement->value["unlocked"].GetBool(); ach.unlockTime = achievement->value["unlock_time"].GetInt64(); - achievementUnlockTime[ach.name] = ach.unlockTime; + + auto& achData = Compendium_t::achievements[achievement->name.GetString()]; + achData.unlockTime = ach.unlockTime; if ( ach.unlocked ) { - achievementUnlockedLookup.insert(ach.name); + Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(ach.name); } } @@ -8188,18 +8190,6 @@ void LocalAchievements_t::readFromFile() for ( int statNum = 0; statNum < NUM_STEAM_STATISTICS; ++statNum ) { - if ( steamStatAchStringsAndMaxVals[statNum].first != "BARONY_ACH_NONE" ) - { - auto find = achievementProgress.find(steamStatAchStringsAndMaxVals[statNum].first); - if ( find == achievementProgress.end() ) - { - achievementProgress.emplace(std::make_pair(std::string(steamStatAchStringsAndMaxVals[statNum].first), statNum)); - } - else - { - find->second = statNum; - } - } g_SteamStats[statNum].m_iValue = LocalAchievements.statistics[statNum].value; } } @@ -8216,7 +8206,7 @@ void LocalAchievements_t::writeToFile() CustomHelpers::addMemberToRoot(exportDocument, "version", rapidjson::Value(VERSION)); rapidjson::Value allAchObj(rapidjson::kObjectType); - for ( auto& ach : achievementNames ) + for ( auto& ach : Compendium_t::achievements ) { if ( LocalAchievements.achievements.find(ach.first) == LocalAchievements.achievements.end() ) { @@ -8268,7 +8258,7 @@ void LocalAchievements_t::writeToFile() void LocalAchievements_t::init() { LocalAchievements.achievements.clear(); - for ( auto& ach : achievementNames ) + for ( auto& ach : Compendium_t::achievements ) { LocalAchievements.achievements[ach.first].unlocked = false; LocalAchievements.achievements[ach.first].unlockTime = 0; @@ -10666,6 +10656,9 @@ std::map Compendium_t::Compen std::map Compendium_t::CompendiumItems_t::itemUnlocks; std::map>> Compendium_t::CompendiumMagic_t::contents; std::map Compendium_t::CompendiumMagic_t::contentsMap; +std::map>> Compendium_t::AchievementData_t::contents; +std::map Compendium_t::AchievementData_t::unlocks; +std::map Compendium_t::AchievementData_t::contentsMap; std::map Compendium_t::Events_t::monsterIDToString; std::map Compendium_t::Events_t::codexIDToString; @@ -10759,6 +10752,11 @@ void Compendium_t::CompendiumMagic_t::readContentsLang() Compendium_t::readContentsLang("contents_magic", contents, contentsMap); } +void Compendium_t::AchievementData_t::readContentsLang() +{ + Compendium_t::readContentsLang("contents_achievements", contents, contentsMap); +} + void Compendium_t::updateTooltip() { bool update = tooltipNeedUpdate; @@ -12788,6 +12786,13 @@ void Compendium_t::readUnlocksSaveData() CompendiumWorld_t::unlocks.clear(); CompendiumCodex_t::unlocks.clear(); CompendiumMonsters_t::unlocks.clear(); + AchievementData_t::unlocks.clear(); + + AchievementData_t::unlocks["experience"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + AchievementData_t::unlocks["joker"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + AchievementData_t::unlocks["teamwork"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + AchievementData_t::unlocks["technique"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + AchievementData_t::unlocks["adventure"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; CompendiumWorld_t::unlocks["minehead"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; CompendiumWorld_t::unlocks["hall of trials"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; @@ -12864,7 +12869,7 @@ void Compendium_t::readUnlocksSaveData() return; } - const int bufSize = 120000; + const int bufSize = 200000; char buf[bufSize]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; @@ -12893,6 +12898,36 @@ void Compendium_t::readUnlocksSaveData() } } + if ( d.HasMember("items_status") ) + { + for ( auto itr = d["items_status"].MemberBegin(); itr != d["items_status"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + std::string name = itr->name.GetString(); + int id = std::stoi(name); + CompendiumItems_t::itemUnlocks[id] = static_cast(val); + } + } + + if ( d.HasMember("achievements") ) + { + for ( auto itr = d["achievements"].MemberBegin(); itr != d["achievements"].MemberEnd(); ++itr ) + { + int val = itr->value.GetInt(); + if ( !(val >= CompendiumUnlockStatus::LOCKED_UNKNOWN + && val < CompendiumUnlockStatus::COMPENDIUMUNLOCKSTATUS_MAX) ) + { + val = 0; + } + AchievementData_t::unlocks[itr->name.GetString()] = static_cast(val); + } + } + if ( d.HasMember("world") ) { for ( auto itr = d["world"].MemberBegin(); itr != d["world"].MemberEnd(); ++itr ) @@ -12957,6 +12992,22 @@ void Compendium_t::writeUnlocksSaveData() } CustomHelpers::addMemberToRoot(exportDocument, "items", obj); + obj.RemoveAllMembers(); + for ( auto& pair : CompendiumItems_t::itemUnlocks ) + { + obj.AddMember(rapidjson::Value(std::to_string(pair.first).c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "items_status", obj); + + obj.RemoveAllMembers(); + for ( auto& pair : AchievementData_t::unlocks ) + { + obj.AddMember(rapidjson::Value(pair.first.c_str(), exportDocument.GetAllocator()), + rapidjson::Value((int)pair.second), exportDocument.GetAllocator()); + } + CustomHelpers::addMemberToRoot(exportDocument, "achievements", obj); + obj.RemoveAllMembers(); for ( auto& pair : CompendiumWorld_t::unlocks ) { @@ -13970,19 +14021,13 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t if ( playernum == clientnum ) { - if ( tag == EventTags::CPDM_KILLED_BY - || tag == EventTags::CPDM_KILLED_SOLO - || tag == EventTags::CPDM_KILLED_MULTIPLAYER - || tag == EventTags::CPDM_RECRUITED ) + auto find = monsterIDToString.find(monsterType); + if ( find != monsterIDToString.end() ) { - auto find = monsterIDToString.find(monsterType); - if ( find != monsterIDToString.end() ) + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) { - auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; - if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) - { - unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; - } + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } } } @@ -14832,4 +14877,12 @@ void Compendium_t::exportCurrentMonster(Entity* monster) return; } -#endif \ No newline at end of file +#endif + +std::unordered_map Compendium_t::achievements; +bool Compendium_t::AchievementData_t::achievementsNeedResort = true; +bool Compendium_t::AchievementData_t::achievementsNeedFirstData = true; +std::set, Compendium_t::AchievementData_t::Comparator> Compendium_t::AchievementData_t::achievementNamesSorted; +std::map>> Compendium_t::AchievementData_t::achievementCategories; +std::map Compendium_t::AchievementData_t::achievementsBookDisplay; +std::unordered_set Compendium_t::AchievementData_t::achievementUnlockedLookup; \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 35b13758f..1410024d2 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3507,6 +3507,50 @@ struct Compendium_t COMPENDIUMUNLOCKSTATUS_MAX }; + class AchievementData_t + { + public: + static int compendiumAchievementPoints; + enum AchievementDLCType + { + ACH_TYPE_NORMAL, + ACH_TYPE_DLC1, + ACH_TYPE_DLC2, + ACH_TYPE_DLC1_DLC2 + }; + std::string name; + std::string desc; + std::string desc_formatted; + bool hidden = false; + AchievementDLCType dlcType = ACH_TYPE_NORMAL; + std::string category = ""; + int lorePoints = 0; + int64_t unlockTime = 0; + bool unlocked = false; + int achievementProgress = -1; // ->second is the associated achievement stat index + + static bool achievementsNeedResort; + static bool achievementsNeedFirstData; + typedef std::function, std::pair)> Comparator; + static std::set, Comparator> achievementNamesSorted; + static std::map>> achievementCategories; + static std::unordered_set achievementUnlockedLookup; + static void onAchievementUnlock(const char* ach); + static std::map>> contents; + static std::map contentsMap; + static std::map unlocks; + static void readContentsLang(); + + struct CompendiumAchievementsDisplay + { + std::vector> pages; + int currentPage = 0; + int numHidden = 0; + }; + static std::map achievementsBookDisplay; + }; + static std::unordered_map achievements; + enum EventTags { CPDM_BLOCKED_ATTACKS, diff --git a/src/steam.cpp b/src/steam.cpp index 3c2cc8c01..99473e15a 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -773,8 +773,6 @@ void SteamServerClientWrapper::OnGetNumberOfCurrentPlayers(NumberOfCurrentPlayer /* ***** END UTTER BODGE ***** */ - - /*------------------------------------------------------------------------------- achievementUnlocked @@ -787,7 +785,12 @@ void SteamServerClientWrapper::OnGetNumberOfCurrentPlayers(NumberOfCurrentPlayer bool achievementUnlocked(const char* achName) { // check internal achievement record - return (achievementUnlockedLookup.find(achName) != achievementUnlockedLookup.end()); + auto find = Compendium_t::achievements.find(achName); + if ( find == Compendium_t::achievements.end() ) + { + return false; + } + return find->second.unlocked; } /*------------------------------------------------------------------------------- @@ -858,9 +861,23 @@ void steamAchievement(const char* achName) #endif #endif - achievementUnlockedLookup.insert(std::string(achName)); - achievementUnlockTime[achName] = getTime(); - achievementsNeedResort = true; + Compendium_t::achievements[achName].unlocked = true; + Compendium_t::achievements[achName].unlockTime = getTime(); + auto& unlockStatus = Compendium_t::AchievementData_t::unlocks[Compendium_t::achievements[achName].category]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED; + } + Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(achName); + Compendium_t::AchievementData_t::achievementsNeedResort = true; } } diff --git a/src/ui/Frame.cpp b/src/ui/Frame.cpp index 6eb568d33..260b893ad 100644 --- a/src/ui/Frame.cpp +++ b/src/ui/Frame.cpp @@ -1091,7 +1091,24 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, bool usable if (activated) { // unselect list if (input.consumeBinaryToggle("MenuCancel")) { - deselect(); + if ( bListMenuListCancelOverride ) + { + // workaround for compendium list into back button + // - let menulistcancel access back_button + // in handleInput() + if ( widgetActions.find("MenuListCancel") != widgetActions.end() ) + { + // no-op, keep selected + } + else + { + deselect(); + } + } + else + { + deselect(); + } std::string deselectTarget; auto find = widgetMovements.find("MenuListCancel"); if (find != widgetMovements.end()) { @@ -1139,12 +1156,12 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, bool usable if (selection == -1) { if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("MenuDown") || - input.consumeBinaryToggle("MenuRight") || - input.consumeBinaryToggle("MenuLeft") || + (list[0]->leftright_control && input.consumeBinaryToggle("MenuRight")) || + (list[0]->leftright_control && input.consumeBinaryToggle("MenuLeft")) || input.consumeBinaryToggle("AltMenuUp") || input.consumeBinaryToggle("AltMenuDown") || - input.consumeBinaryToggle("AltMenuRight") || - input.consumeBinaryToggle("AltMenuLeft")) { + (list[0]->leftright_control && input.consumeBinaryToggle("AltMenuRight")) || + (list[0]->leftright_control && input.consumeBinaryToggle("AltMenuLeft"))) { selection = 0; scrollToSelection(); auto entry = list[selection]; @@ -1153,11 +1170,40 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, bool usable } } } else { - if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("AltMenuUp") || - input.consumeBinaryToggle("MenuLeft") || input.consumeBinaryToggle("AltMenuLeft")) { - --selection; - if (selection < 0) { - selection = (int)list.size() - 1; + entry_t* entryCurrent = nullptr; + if ( selection >= 0 && selection < list.size() ) + { + entryCurrent = list[selection]; + } + if (input.consumeBinaryToggle("MenuUp") || input.consumeBinaryToggle("AltMenuUp") + || (entryCurrent && entryCurrent->leftright_control && input.consumeBinaryToggle("MenuLeft")) + || (entryCurrent && entryCurrent->leftright_control && input.consumeBinaryToggle("AltMenuLeft")) + ) { + int selectionStart = selection; + bool leftright = input.binary("MenuLeft") || input.binary("AltMenuLeft"); + while ( list.size() > 0 ) + { + --selection; + if (selection < 0) { + selection = (int)list.size() - 1; + } + if ( selectionStart == selection ) + { + break; + } + if ( !list[selection]->navigable ) + { + continue; + } + else if ( leftright && !list[selection]->leftright_allow_nonclickable && list[selection]->movement_nonclickable ) + { + continue; + } + else if ( !leftright && !list[selection]->updown_allow_nonclickable && list[selection]->movement_nonclickable ) + { + continue; + } + break; } scrollToSelection(); auto entry = list[selection]; @@ -1165,11 +1211,36 @@ Frame::result_t Frame::process(SDL_Rect _size, SDL_Rect _actualSize, bool usable (*entry->selected)(*entry); } } - if (input.consumeBinaryToggle("MenuDown") || input.consumeBinaryToggle("AltMenuDown") || - input.consumeBinaryToggle("MenuRight") || input.consumeBinaryToggle("AltMenuRight")) { - ++selection; - if (selection >= list.size()) { - selection = 0; + if (input.consumeBinaryToggle("MenuDown") || input.consumeBinaryToggle("AltMenuDown") + || (entryCurrent && entryCurrent->leftright_control && input.consumeBinaryToggle("MenuRight")) + || (entryCurrent && entryCurrent->leftright_control && input.consumeBinaryToggle("AltMenuRight")) + ) { + bool foundSelection = false; + int selectionStart = selection; + bool leftright = input.binary("MenuRight") || input.binary("AltMenuRight"); + while ( list.size() > 0 ) + { + ++selection; + if ( selection >= list.size() ) { + selection = 0; + } + if ( selectionStart == selection ) + { + break; + } + if ( !list[selection]->navigable ) + { + continue; + } + else if ( leftright && !list[selection]->leftright_allow_nonclickable && list[selection]->movement_nonclickable ) + { + continue; + } + else if ( !leftright && !list[selection]->updown_allow_nonclickable && list[selection]->movement_nonclickable ) + { + continue; + } + break; } scrollToSelection(); auto entry = list[selection]; @@ -2755,3 +2826,33 @@ void Frame::setBlitChildren(bool _doBlit) } } } + +void Frame::scrollParent() { + if ( !allowScrollParent ) + { + return; + } + Frame* fparent = static_cast(parent); + auto fActualSize = fparent->getActualSize(); + auto fSize = fparent->getSize(); + + const auto y = std::max(0, size.y + scrollParentOffset.y); + const auto h = size.h + scrollParentOffset.h; + const auto x = std::max(0, size.x + scrollParentOffset.x); + const auto w = size.w + scrollParentOffset.w; + + if ( y < fActualSize.y ) { + fActualSize.y = y; + } + else if ( size.y + h >= fActualSize.y + fSize.h ) { + fActualSize.y = (size.y + h) - fSize.h; + fActualSize.y = std::min(std::max(0, fActualSize.y), std::max(0, fActualSize.h - fSize.h)); + } + if ( x < fActualSize.x ) { + fActualSize.x = x; + } + else if ( size.x + w >= fActualSize.x + fSize.w ) { + fActualSize.x = (size.x + w) - fSize.w; + } + fparent->setActualSize(fActualSize); +} \ No newline at end of file diff --git a/src/ui/Frame.hpp b/src/ui/Frame.hpp index 9b4d1534e..cd0159b7c 100644 --- a/src/ui/Frame.hpp +++ b/src/ui/Frame.hpp @@ -95,6 +95,11 @@ class Frame : public Widget { bool clickable = true; bool pressed = false; bool highlighted = false; + bool leftright_control = true; + bool leftright_allow_nonclickable = true; + bool updown_allow_nonclickable = true; + bool movement_nonclickable = false; + bool navigable = true; Uint32 highlightTime = 0; bool suicide = false; @@ -307,6 +312,9 @@ class Frame : public Widget { //! puts this frame on top of all others void bringToTop(); + //! scroll the parent frame (if any) to be within our bounds + virtual void scrollParent(); + virtual type_t getType() const override { return WIDGET_FRAME; } const char* getFont() const { return font.c_str(); } const int getBorder() const { return border; } @@ -370,6 +378,9 @@ class Frame : public Widget { void setScrollWithLeftControls(const bool b) { scrollWithLeftControls = b; } void setAccelerationX(const float x) { scrollAccelerationX = x; } void setAccelerationY(const float y) { scrollAccelerationY = y; } + void setListMenuCancelOverride(const bool b) { bListMenuListCancelOverride = b; } + void setAllowScrollParent(const bool b) { allowScrollParent = b; } + void setScrollParentOffset(const SDL_Rect& offset) { scrollParentOffset = offset; } void setActualSize(SDL_Rect _actualSize) { allowScrolling = true; @@ -427,6 +438,10 @@ class Frame : public Widget { bool bBlitChildrenToTexture = false; //!< if true, subframes will blit onto blitSurface and draw from this cached surface bool bBlitDirty = false; //!< if true, re-blit all subframes next draw() bool bBlitToParent = false; //!< if true, find a frame with findParentToBlitTo() and blit onto it's surface + bool bListMenuListCancelOverride = false; //!< if true, MenuListCancel will activate a widget rather than deactivating the list (i.e back_button) + SDL_Rect scrollParentOffset{ 0,0,0,0 }; //!< scrollParent() increase/decrease amount of scrolling for parent + bool allowScrollParent = false; //!< if true, scrolls parent when widget movement called + std::vector frames; std::vector buttons; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 609434e72..1b19c0886 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -9813,7 +9813,7 @@ namespace MainMenu { static void createSimpleAchievementsWindow() { soundActivate(); - if ( achievementsNeedResort ) + if ( Compendium_t::AchievementData_t::achievementsNeedResort ) { sortAchievementsForDisplay(); } @@ -9845,16 +9845,16 @@ namespace MainMenu { int y = 0; // count all the different types of achievements - const int num_achievements = (int)achievementNames.size(); + const int num_achievements = (int)Compendium_t::achievements.size(); int num_unlocked = 0; int num_locked = 0; int num_hidden = 0; - for (auto& item : achievementNames) { + for (auto& item : Compendium_t::achievements ) { if (achievementUnlocked(item.first.c_str())) { ++num_unlocked; } else { ++num_locked; - if (achievementHidden.find(item.first.c_str()) != achievementHidden.end()) { + if (item.second.hidden) { ++num_hidden; } } @@ -9900,14 +9900,11 @@ namespace MainMenu { const char* achName = nullptr; const char* achDesc = nullptr; if (name) { - auto achNameFind = achievementNames.find(name); - if (achNameFind != achievementNames.end()) { - achName = achNameFind->second.c_str(); - } - - auto achDescFind = achievementDesc.find(name); - if (achDescFind != achievementDesc.end()) { - achDesc = achDescFind->second.c_str(); + auto achData = Compendium_t::achievements.find(name); + if ( achData != Compendium_t::achievements.end() ) + { + achName = achData->second.name.c_str(); + achDesc = achData->second.desc.c_str(); } } @@ -9987,10 +9984,10 @@ namespace MainMenu { } else { // unlock time assert(name); - auto it = achievementUnlockTime.find(name); - if (it != achievementUnlockTime.end()) { + auto it = Compendium_t::achievements.find(name); + if (it != Compendium_t::achievements.end() && it->second.unlocked) { char buffer[64]; - time_t t = (time_t)it->second; + time_t t = (time_t)it->second.unlockTime; char tbuf[64]; getTimeAndDateFormatted(t, tbuf, sizeof(tbuf)); @@ -10026,7 +10023,7 @@ namespace MainMenu { // list unlocked achievements if (num_unlocked > 0) { y += settingsAddSubHeader(*subwindow, y, "unlocked", Language::get(5326), true); - for (auto& item : achievementNamesSorted) { + for (auto& item : Compendium_t::AchievementData_t::achievementNamesSorted) { if (!achievementUnlocked(item.first.c_str())) { continue; } @@ -10047,18 +10044,19 @@ namespace MainMenu { // list locked achievements if (num_locked > 0) { y += settingsAddSubHeader(*subwindow, y, "locked", Language::get(5327), true); - for (auto& item : achievementNamesSorted) { + for (auto& item : Compendium_t::AchievementData_t::achievementNamesSorted) { + + auto& achData = Compendium_t::achievements[item.first]; if (achievementUnlocked(item.first.c_str())) { continue; } - if (achievementHidden.find(item.first.c_str()) != achievementHidden.end()) { + if ( achData.hidden ) { continue; } int statCur = 0; - auto progIt = achievementProgress.find(item.first); - if (progIt != achievementProgress.end()) { - statCur = g_SteamStats[progIt->second].m_iValue; + if ( achData.achievementProgress >= 0 ) { + statCur = g_SteamStats[achData.achievementProgress].m_iValue; } int statMax = 0; @@ -13378,28 +13376,28 @@ namespace MainMenu { switch ( classnum ) { case CLASS_CONJURER: - achName = achievementNames["BARONY_ACH_BONY_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BONY_BARON"].name; break; case CLASS_ACCURSED: - achName = achievementNames["BARONY_ACH_BUCKTOOTH_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BUCKTOOTH_BARON"].name; break; case CLASS_MESMER: - achName = achievementNames["BARONY_ACH_BOMBSHELL_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BOMBSHELL_BARON"].name; break; case CLASS_BREWER: - achName = achievementNames["BARONY_ACH_BLEATING_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BLEATING_BARON"].name; break; case CLASS_MACHINIST: - achName = achievementNames["BARONY_ACH_BOILERPLATE_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BOILERPLATE_BARON"].name; break; case CLASS_PUNISHER: - achName = achievementNames["BARONY_ACH_BAD_BOY_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BAD_BOY_BARON"].name; break; case CLASS_SHAMAN: - achName = achievementNames["BARONY_ACH_BAYOU_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BAYOU_BARON"].name; break; case CLASS_HUNTER: - achName = achievementNames["BARONY_ACH_BUGGAR_BARON"]; + achName = Compendium_t::achievements["BARONY_ACH_BUGGAR_BARON"].name; break; default: break; @@ -32762,7 +32760,8 @@ namespace MainMenu { "items", "magic", "world", - "codex" + "codex", + "achievements" }; static std::string compendium_current = "monsters"; @@ -33367,7 +33366,8 @@ namespace MainMenu { } } - static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner, bool scrollTo) + static bool contents_activate_from_tab = true; + static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner, bool scrollTo, bool gamepadSelect) { if ( scrollTo ) { @@ -33397,6 +33397,16 @@ namespace MainMenu { txtRight->setColor(compendiumContentsSelectedColor); } + if ( !isMouseVisible() && gamepadSelect ) + { + toSelect.select(); + } + + if ( !contents_activate_from_tab ) + { + //soundActivate(); + } + refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); // find records for this item @@ -33453,7 +33463,6 @@ namespace MainMenu { details->setDisabled(false); details->setColor(txt->getColor()); } - txt->dirty = true; auto find = ItemTooltips.itemNameStringToItemID.find(toSelect.getName()); int itemType = find != ItemTooltips.itemNameStringToItemID.end() ? find->second : -1; @@ -33494,6 +33503,15 @@ namespace MainMenu { Compendium_t::compendiumItemModel.skill[10] = itemType; Compendium_t::compendiumItemModel.skill[14] = appearance; + if ( !isMouseVisible() && gamepadSelect ) + { + toSelect.select(); + } + if ( !contents_activate_from_tab ) + { + //soundActivate(); + } + refreshCompendiumCamera(modelsPath); // find records for this item @@ -33555,7 +33573,6 @@ namespace MainMenu { static ConsoleVariablecvar_compendium_reveal("/compendium_reveal", false); #endif static bool compendiumEntryControlEnabled = false; - static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) { if ( compendium_current == "items" ) @@ -33572,7 +33589,7 @@ namespace MainMenu { return; } } - auto& entry = (compendium_current == "items") ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; + auto& compendiumEntry = (compendium_current == "items") ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; Compendium_t::compendiumEntityCurrent.modelRNG++; @@ -33615,28 +33632,18 @@ namespace MainMenu { } compendiumItemTooltip.clear(); - /*page_right->setTickCallback([](Widget& widget) { - auto frame = static_cast(&widget); - if ( auto parent = frame->getParent() ) - { - if ( parent = parent->getParent() ) - { - players[0]->inventoryUI.updateInventoryItemTooltip(parent); - } - } - });*/ - int lastSelection = compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]]; - if ( lastSelection >= entry.items_in_category.size() ) + if ( lastSelection >= compendiumEntry.items_in_category.size() ) { lastSelection = 0; } Frame* toSelect = nullptr; + Frame* prevFrame = nullptr; int modelRNGCycle = -1; - for ( int i = 0; i < entry.items_in_category.size(); ++i ) + for ( int i = 0; i < compendiumEntry.items_in_category.size(); ++i ) { ++modelRNGCycle; - auto& data = entry.items_in_category[i]; + auto& data = compendiumEntry.items_in_category[i]; auto entry = page_right->addFrame(data.name.c_str()); entry->setHollow(true); entry->setClickable(false); @@ -33651,6 +33658,14 @@ namespace MainMenu { entry->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); + if ( isMouseVisible() ) + { + frame->setWidgetBack("back_button"); + } + else + { + frame->setWidgetBack("right_back_button"); + } if ( Frame* page_right_inner = frame->getParent() ) { auto selector_bg = page_right_inner->findImage("selector_bg"); @@ -33673,15 +33688,140 @@ namespace MainMenu { if ( parent = parent->getParent() ) { SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) + if ( (isMouseVisible() && frame->capturesMouseInRealtimeCoords()) + || frame->isSelected() ) { + if ( isMouseVisible() ) + { + if ( Frame* page_right = page_right_inner->getParent() ) + { + page_right->select(); + } + } + else if ( frame->isSelected() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuDown") + || Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuRight") ) + { + bool leftright = Input::inputs[getMenuOwner()].binary("MenuRight"); + bool foundSelection = false; + auto& frames = page_right_inner->getFrames(); + int index = -1; + for ( auto f : frames ) + { + ++index; + if ( f == frame ) + { + // look ahead for a new selection + for ( int i = index + 1; i < frames.size(); ++i ) + { + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + auto itemName = frames[i]->findField("item name"); + bool selectable = !(itemName && !strcmp(itemName->getText(), "???")); + if ( !selectable && leftright ) + { + continue; // skip over + } + if ( !itemName ) + { + continue; + } + + frames[i]->select(); + frames[i]->scrollParent(); + soundMove(); + foundSelection = true; + if ( selectable ) + { + selectCompendiumItemInList(*frames[i], *page_right_inner, false, true); + } + break; + } + break; + } + } + if ( !foundSelection ) + { + soundError(); + } + } + else if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuUp") + || Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeft") ) + { + bool leftright = Input::inputs[getMenuOwner()].binary("MenuLeft"); + bool foundSelection = false; + auto& frames = page_right_inner->getFrames(); + int index = -1; + for ( auto f : frames ) + { + ++index; + if ( f == frame ) + { + // look behind for a selection + for ( int i = index - 1; i >= 0; --i ) + { + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + auto itemName = frames[i]->findField("item name"); + bool selectable = !(itemName && !strcmp(itemName->getText(), "???")); + if ( !selectable && leftright ) + { + continue; // skip over + } + if ( !itemName ) + { + continue; + } + frames[i]->select(); + frames[i]->scrollParent(); + soundMove(); + foundSelection = true; + if ( selectable ) + { + selectCompendiumItemInList(*frames[i], *page_right_inner, false, true); + } + break; + } + break; + } + } + if ( !foundSelection ) + { + soundError(); + } + } + } + auto itemName = frame->findField("item name"); bool selectable = !(itemName && !strcmp(itemName->getText(), "???")); - if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") + && isMouseVisible() ) + { + if ( selectable ) + { + soundActivate(); + selectCompendiumItemInList(*frame, *page_right_inner, false, false); + } + else + { + soundError(); + } + } + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuConfirm") ) { if ( selectable ) { - selectCompendiumItemInList(*frame, *page_right_inner, false); + soundActivate(); + selectCompendiumItemInList(*frame, *page_right_inner, false, true); + } + else + { + soundError(); } } @@ -33701,10 +33841,18 @@ namespace MainMenu { tmp.w = itemBg->pos.w; tmp.h = itemBg->pos.h; frame->setSize(tmp); - hovered = frame->capturesMouseInRealtimeCoords(); + hovered = isMouseVisible() && frame->capturesMouseInRealtimeCoords(); } frame->setSize(pos); + if ( !isMouseVisible() ) + { + if ( frame->isSelected() ) + { + page_right_inner->setAllowScrollBinds(true); + } + } + if ( hovered && selectable ) { //frame->select(); @@ -33860,11 +34008,19 @@ namespace MainMenu { itemName->setColor(compendiumContentsDefaultColor); itemDetail->setColor(compendiumContentsDefaultColor); } + + entry->setWidgetSearchParent("compendium"); + entry->setAllowScrollParent(true); + entry->setScrollParentOffset(SDL_Rect{ 0, -16, 0, 16 }); + entry->setMenuConfirmControlType(0); + entry->addWidgetAction("MenuPageLeft", "tab_left"); + entry->addWidgetAction("MenuPageRight", "tab_right"); + entry->setWidgetBack("right_back_button"); } if ( toSelect ) { - selectCompendiumItemInList(*toSelect, *page_right, true); + selectCompendiumItemInList(*toSelect, *page_right, true, false); } } } @@ -33985,6 +34141,14 @@ namespace MainMenu { entry->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); + if ( isMouseVisible() ) + { + frame->setWidgetBack("back_button"); + } + else + { + frame->setWidgetBack("right_back_button"); + } if ( Frame* page_right_inner = frame->getParent() ) { auto selector_bg = page_right_inner->findImage("selector_bg"); @@ -33999,6 +34163,7 @@ namespace MainMenu { } } } + auto txt = frame->findField("item name"); if ( compendiumEntryControlEnabled ) { @@ -34007,11 +34172,113 @@ namespace MainMenu { if ( parent = parent->getParent() ) { SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( frame->capturesMouseInRealtimeCoords() && inputs.getVirtualMouse(getMenuOwner())->draw_cursor ) + if ( (isMouseVisible() && frame->capturesMouseInRealtimeCoords()) + || frame->isSelected() ) { - if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) + if ( isMouseVisible() ) + { + if ( Frame* page_right = page_right_inner->getParent() ) + { + page_right->select(); + } + } + else if ( frame->isSelected() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuDown") + || Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuRight") ) + { + bool foundSelection = false; + auto& frames = page_right_inner->getFrames(); + int index = -1; + for ( auto f : frames ) + { + ++index; + if ( f == frame ) + { + // look ahead for a new selection + for ( int i = index + 1; i < frames.size(); ++i ) + { + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + if ( !frames[i]->findField("item name") ) + { + continue; + } + + frames[i]->select(); + frames[i]->scrollParent(); + soundMove(); + foundSelection = true; + selectCompendiumItemInList(*frames[i], *page_right_inner, false, true); + break; + } + break; + } + } + if ( !foundSelection ) + { + soundError(); + } + } + else if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuUp") + || Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeft") ) + { + bool foundSelection = false; + auto& frames = page_right_inner->getFrames(); + int index = -1; + for ( auto f : frames ) + { + ++index; + if ( f == frame ) + { + // look behind for a selection + for ( int i = index - 1; i >= 0; --i ) + { + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + if ( !frames[i]->findField("item name") ) + { + continue; + } + + frames[i]->select(); + frames[i]->scrollParent(); + soundMove(); + selectCompendiumItemInList(*frames[i], *page_right_inner, false, true); + break; + } + break; + } + } + if ( !foundSelection ) + { + soundError(); + } + } + } + + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") + && isMouseVisible() ) + { + soundActivate(); + selectCompendiumItemInList(*frame, *page_right_inner, false, false); + } + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuConfirm") ) { - selectCompendiumItemInList(*frame, *page_right_inner, false); + //soundActivate(); + //selectCompendiumItemInList(*frame, *page_right_inner, false, true); + } + + if ( !isMouseVisible() ) + { + if ( frame->isSelected() ) + { + page_right_inner->setAllowScrollBinds(true); + } } SDL_Rect pos = frame->getSize(); @@ -34030,7 +34297,7 @@ namespace MainMenu { tmp.w = itemBg->pos.w; tmp.h = itemBg->pos.h; frame->setSize(tmp); - hovered = frame->capturesMouseInRealtimeCoords(); + hovered = isMouseVisible() && frame->capturesMouseInRealtimeCoords(); } frame->setSize(pos); @@ -34145,11 +34412,34 @@ namespace MainMenu { { itemName->setColor(compendiumContentsDefaultColor); } + + entry->setWidgetSearchParent("compendium"); + entry->setAllowScrollParent(true); + entry->setScrollParentOffset(SDL_Rect{ 0, -16, 0, 16 }); + entry->setMenuConfirmControlType(0); + entry->addWidgetAction("MenuPageLeft", "tab_left"); + entry->addWidgetAction("MenuPageRight", "tab_right"); + if ( isMouseVisible() ) + { + entry->setWidgetBack("back_button"); + } + else + { + entry->setWidgetBack("right_back_button"); + } + if ( isMouseVisible() ) + { + entry->setWidgetBack("back_button"); + } + else + { + entry->setWidgetBack("right_back_button"); + } } if ( toSelect ) { - selectCompendiumItemInList(*toSelect, *page_right, true); + selectCompendiumItemInList(*toSelect, *page_right, true, false); } } } @@ -34277,6 +34567,8 @@ namespace MainMenu { } } + static void refreshCompendiumAchievements(std::string name, Frame* parent); + static void refreshCompendiumEntryMonster(std::string name, Frame* parent) { if ( CompendiumEntries.monsters.find(name) == CompendiumEntries.monsters.end() ) @@ -34733,7 +35025,21 @@ namespace MainMenu { } static std::string compendium_sorting = "default"; - static auto contents_activate_fn = [](Frame::entry_t& frameEntry) + static auto contents_select_unknown_fn = [](Frame::entry_t& frameEntry) + { + if ( !contents_activate_from_tab && !isMouseVisible() ) + { + soundMove(); + } + }; + static auto contents_activate_unknown_fn = [](Frame::entry_t& frameEntry) + { + if ( !contents_activate_from_tab ) + { + soundError(); + } + }; + static auto contents_activate_fn = [](Frame::entry_t& frameEntry, bool gamepadClick) { static ConsoleCommand ccmd_compendium_sorting( "/compendium_sorting", "", @@ -34746,6 +35052,11 @@ namespace MainMenu { auto compendiumFrame = main_menu_frame->findFrame("compendium"); if ( !compendiumFrame ) { return; } + if ( auto page_right_number_flourish = compendiumFrame->findImage("page_right_number_flourish") ) + { + page_right_number_flourish->disabled = true; + } + auto contentsFrame = compendiumFrame->findFrame("contents"); if ( contentsFrame ) { @@ -34778,18 +35089,23 @@ namespace MainMenu { : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] : nullptr)))); + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[compendium_sorting] + : nullptr))))); auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks - : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::unlocks + : nullptr))))); std::string content = ""; std::string previousEntry = compendium_contents_current[compendium_current]; compendiumEntryControlEnabled = false; + bool unlocked = false; if ( entries ) { @@ -34805,7 +35121,6 @@ namespace MainMenu { page_left_title->setText(entry.second.c_str()); } - bool unlocked = false; if ( unlockStatus ) { auto findUnlock = unlockStatus->find(entry.first); @@ -34832,7 +35147,14 @@ namespace MainMenu { unlocked = false; } - compendiumPageRightHideUnlocked(page_right, unlocked, false, unlocked ? "" : entry.first); + if ( compendium_current == "achievements" ) + { + compendiumPageRightHideUnlocked(page_right, false, true); // hide all page_right + } + else + { + compendiumPageRightHideUnlocked(page_right, unlocked, false, unlocked ? "" : entry.first); + } if ( contentsFrame ) { @@ -34876,13 +35198,25 @@ namespace MainMenu { Compendium_t::compendiumEntityCurrent.modelRNG = local_rng.rand(); } + auto pageNumberLeft = compendiumFrame->findField("page_left_number"); + auto pageNumberRight = compendiumFrame->findField("page_right_number"); + auto pagePrev = compendiumFrame->findField("page_left_prev"); + auto pageNext = compendiumFrame->findField("page_right_next"); + if ( compendium_current == "achievements" ) { - auto pageNumberLeft = compendiumFrame->findField("page_left_number"); - auto pageNumberRight = compendiumFrame->findField("page_right_number"); - auto pagePrev = compendiumFrame->findField("page_left_prev"); - auto pageNext = compendiumFrame->findField("page_right_next"); - + if ( pageNumberLeft ) { pageNumberLeft->setDisabled(true); } + if ( pageNumberRight ) { pageNumberRight->setDisabled(true); } + if ( pagePrev ) { pagePrev->setDisabled(true); } + if ( pageNext ) { pageNext->setDisabled(true); } + } + else + { + if ( pageNumberLeft ) { pageNumberLeft->setDisabled(false); } + if ( pageNumberRight ) { pageNumberRight->setDisabled(true); } + if ( pagePrev ) { pagePrev->setDisabled(false); } + if ( pageNext ) { pageNext->setDisabled(false); } { + std::string txt = Language::get(6179); int prevId = -1; if ( id > 0 && id < entries->size() ) @@ -35046,23 +35380,71 @@ namespace MainMenu { } refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], compendiumFrame); } + else if ( compendium_current == "achievements" ) + { + refreshCompendiumAchievements(compendium_contents_current[compendium_current], compendiumFrame); + } + + if ( unlocked ) + { + if ( gamepadClick && previousEntry == compendium_contents_current[compendium_current] ) + { + // gamepad highlighted, then pressed 'a' to advance into selection + if ( auto page_right = compendiumFrame->findFrame("page_right") ) + { + page_right->select(); + } + return; + } + } + }; + + static auto contents_select_fn = [](Frame::entry_t& frameEntry) + { + if ( !contents_activate_from_tab && !isMouseVisible() ) + { + soundMove(); + } + if ( !contents_activate_from_tab && !isMouseVisible() ) + { + contents_activate_fn(frameEntry, false); + } + }; + static auto contents_click_fn = [](Frame::entry_t& frameEntry) + { + if ( !contents_activate_from_tab ) + { + soundActivate(); + } + + bool gamepadClick = !isMouseVisible() && !contents_activate_from_tab; + contents_activate_fn(frameEntry, gamepadClick); }; static void compendiumPopulateContents(Frame* frame) { + if ( auto page_right_number_flourish = frame->findImage("page_right_number_flourish") ) + { + page_right_number_flourish->disabled = true; + } + if ( auto contents = frame->findFrame("contents") ) { auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[compendium_sorting] : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] : nullptr)))); + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[compendium_sorting] + : nullptr))))); auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks - : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::unlocks + : nullptr))))); auto toRemove = contents->getEntries(); for ( auto r : toRemove ) @@ -35125,6 +35507,8 @@ namespace MainMenu { } } + bool achievementsTab = compendium_current == "achievements"; + if ( entries ) { int indexToSelect = -1; @@ -35136,17 +35520,22 @@ namespace MainMenu { bool unlocked = false; bool drawNotification = false; - entry->click = contents_activate_fn; - entry->ctrlClick = contents_activate_fn; + entry->click = contents_click_fn; + entry->ctrlClick = contents_click_fn; + entry->selected = contents_select_fn; //entry->highlight = selection_fn; - //entry->selected = selection_fn; entry->clickable = true; + entry->updown_allow_nonclickable = true; + entry->leftright_allow_nonclickable = false; + entry->leftright_control = achievementsTab ? false : true; if ( data.first == "-" ) { entry->click = nullptr; entry->ctrlClick = nullptr; + entry->selected = nullptr; entry->clickable = false; + entry->navigable = false; entry->text = data.second; entry->color = makeColorRGB(42, 22, 18); @@ -35183,8 +35572,10 @@ namespace MainMenu { { entry->color = compendiumContentsDefaultColor; entry->text = "???"; - entry->click = nullptr; - entry->ctrlClick = nullptr; + entry->click = contents_activate_unknown_fn; + entry->ctrlClick = contents_activate_unknown_fn; + entry->selected = contents_select_unknown_fn; + entry->movement_nonclickable = true; } else { @@ -35251,79 +35642,782 @@ namespace MainMenu { actualPos.y = 0; contents->setActualSize(actualPos); contents->scrollToSelection(false); + contents->activate(); contents->activateSelection(); } } } - static void compendiumPopulatePageRight(Frame* page_right) + static auto compendium_page_right_inner_fn = [](Widget& widget) { - if ( !page_right ) { return; } - - compendiumPageRightHideUnlocked(page_right, false, true); + Frame* page_right_inner = static_cast(&widget); + if ( isMouseVisible() ) + { + page_right_inner->setAllowScrollBinds(true); + } + else + { + page_right_inner->setAllowScrollBinds(false); + } - Frame* page_right_inner = page_right->findFrame("page_right_inner"); - if ( !page_right_inner ) + if ( isMouseVisible() ) { - page_right_inner = page_right->addFrame("page_right_inner"); - page_right_inner->setSize(SDL_Rect{ 14, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); - page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); - page_right_inner->setTickCallback([](Widget& widget) { - static_cast(&widget)->setAllowScrollBinds(true); - }); + return; } - Frame* page_right_overlay = page_right->findFrame("page_right_overlay"); - if ( !page_right_overlay ) + auto& frames = page_right_inner->getFrames(); + int index = -1; + bool foundSelection = false; + for ( auto f : frames ) { - if ( page_right_overlay = page_right->addFrame("page_right_overlay") ) + ++index; + if ( f->isToBeDeleted() ) { - page_right_overlay->setSize(SDL_Rect{ 10, page_right->getSize().h - 114 - 18, page_right->getSize().w - 20 - 8, 120 }); - page_right_overlay->addImage(SDL_Rect{ 0, 0, page_right_overlay->getSize().w, page_right_overlay->getSize().h }, - 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Records_Frame_00.png", "page right overlay img"); + continue; + } - auto heading = page_right_overlay->addField("records txt", 32); - heading->setFont(menu_option_font); - heading->setText("RECORDS"); - heading->setHJustify(Field::justify_t::LEFT); - heading->setVJustify(Field::justify_t::TOP); - heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); - heading->setColor(makeColor(198, 190, 179, 255)); - heading->setTickCallback([](Widget& widget) { - if ( ticks % (2 * TICKS_PER_SECOND) == 0 && compendiumRecordsProcessedOnTick != ticks ) - { - compendiumRecordsSectionRandSequence++; - compendiumRecordsProcessedOnTick = ticks; - } - }); + if ( f->isSelected() ) + { + SDL_Rect framePos = f->getSize(); + SDL_Rect actualSize = page_right_inner->getActualSize(); - const int recordSpacing = 20; - const int recordTextOffsetW = 36; - auto record1 = page_right_overlay->addField("record 1 txt", 128); - record1->setFont(smallfont_outline); - record1->setText(""); - record1->setHJustify(Field::justify_t::LEFT); - record1->setVJustify(Field::justify_t::TOP); - record1->setSize(SDL_Rect{ 18, 24 + 11, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); - record1->setColor(makeColor(135, 94, 45, 255)); + if ( framePos.y < actualSize.y ) + { + // look ahead for a new selection + for ( int i = index + 1; i < frames.size(); ++i ) + { + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + if ( !frames[i]->findField("item name") ) + { + continue; + } - auto record1val = page_right_overlay->addField("record 1 val", 64); - record1val->setFont(smallfont_outline); - record1val->setText(""); - record1val->setHJustify(Field::justify_t::RIGHT); - record1val->setVJustify(Field::justify_t::TOP); - record1val->setSize(record1->getSize()); - record1val->setColor(makeColor(159, 145, 127, 255)); - record1val->setTickCallback([](Widget& widget) { - size_t line = 0; - if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + SDL_Rect pos = frames[i]->getSize(); + if ( pos.y >= actualSize.y ) + { + frames[i]->select(); + break; + } + } + } + else if ( framePos.y + framePos.h > actualSize.y + page_right_inner->getSize().h ) + { + // look behind for a selection + for ( int i = index - 1; i >= 0; --i ) { - Field* txt = static_cast(&widget); - size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); - txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + if ( frames[i]->isToBeDeleted() ) + { + continue; + } + if ( !frames[i]->findField("item name") ) + { + continue; + } + SDL_Rect pos = frames[i]->getSize(); + if ( pos.y + pos.h <= actualSize.y + page_right_inner->getSize().h ) + { + frames[i]->select(); + break; + } } - }); + } + break; + } + } + }; + + static void refreshCompendiumAchievements(std::string name, Frame* parent) + { + if ( !parent ) { return; } + Frame* frame = parent->findFrame("achievements"); + if ( !frame ) + { + return; + } + + if ( Compendium_t::AchievementData_t::achievementsNeedResort ) + { + sortAchievementsForDisplay(); + } + + if ( auto page_right_number_flourish = parent->findImage("page_right_number_flourish") ) + { + page_right_number_flourish->disabled = true; + } + + static const std::map> backingImgs = + { + { + Compendium_t::AchievementData_t::AchievementDLCType::ACH_TYPE_DLC1, + { + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_DLCCompleted_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Myths_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Myths_Colored_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Myths_Gold_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Myths_Grey_00.png" + } + }, + { + Compendium_t::AchievementData_t::AchievementDLCType::ACH_TYPE_DLC2, + { + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_DLCCompleted_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Legends_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Legends_Colored_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Legends_Gold_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_Icon_Legends_Grey_00.png" + } + }, + { + Compendium_t::AchievementData_t::AchievementDLCType::ACH_TYPE_NORMAL, + { + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_BaseCompleted_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "", + "", + "" + } + }, + { + Compendium_t::AchievementData_t::AchievementDLCType::ACH_TYPE_DLC1_DLC2, + { + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_DLCCompleted_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "", + "", + "" + } + }, + { + -1, + { + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_BaseCompleted_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", + "", + "", + "" + } + } + }; + + static ConsoleVariable cvar_compendium_achievement_title_locked("/compendium_achievement_title_locked", { 224.f, 224.f, 224.f, 255.f }); + static Uint32 colorTitleLocked; + colorTitleLocked = makeColorRGB( + (int)cvar_compendium_achievement_title_locked->x, + (int)cvar_compendium_achievement_title_locked->y, + (int)cvar_compendium_achievement_title_locked->z); + static ConsoleVariable cvar_compendium_achievement_title_unlocked("/compendium_achievement_title_unlocked", { 67.f, 195.f, 157.f, 255.f }); + static Uint32 colorTitleUnlocked; + colorTitleUnlocked = makeColorRGB( + (int)cvar_compendium_achievement_title_unlocked->x, + (int)cvar_compendium_achievement_title_unlocked->y, + (int)cvar_compendium_achievement_title_unlocked->z); + static ConsoleVariable cvar_compendium_achievement_desc_locked("/compendium_achievement_desc_locked", { 224.f, 224.f, 224.f, 255.f }); + static Uint32 colorDescLocked; + colorDescLocked = makeColorRGB( + (int)cvar_compendium_achievement_desc_locked->x, + (int)cvar_compendium_achievement_desc_locked->y, + (int)cvar_compendium_achievement_desc_locked->z); + static ConsoleVariable cvar_compendium_achievement_desc_unlocked("/compendium_achievement_desc_unlocked", { 188.f, 154.f, 114.f, 255.f }); + static Uint32 colorDescUnlocked; + colorDescUnlocked = makeColorRGB( + (int)cvar_compendium_achievement_desc_unlocked->x, + (int)cvar_compendium_achievement_desc_unlocked->y, + (int)cvar_compendium_achievement_desc_unlocked->z); + static ConsoleVariable cvar_compendium_achievement_unlock("/compendium_achievement_unlock", { 221.f, 210.f, 84.f, 255.f }); + static Uint32 colorUnlocked; + colorUnlocked = makeColorRGB( + (int)cvar_compendium_achievement_unlock->x, + (int)cvar_compendium_achievement_unlock->y, + (int)cvar_compendium_achievement_unlock->z); + + + auto& achCategories = Compendium_t::AchievementData_t::achievementCategories[name]; + auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[name]; + + achDisplay.currentPage = std::min(achDisplay.currentPage, (int)achDisplay.pages.size() * 2); + + auto pageNumberLeft = parent->findField("page_left_number"); + auto pageNumberRight = parent->findField("page_right_number"); + int totalPages = (int)achDisplay.pages.size() * 2; + if ( achDisplay.pages.back().size() <= 4 ) + { + totalPages -= 1; + } + if ( pageNumberLeft ) + { + pageNumberLeft->setDisabled(false); + char buf[32]; + snprintf(buf, sizeof(buf), Language::get(6181), (achDisplay.currentPage * 2) + 1, totalPages); + pageNumberLeft->setText(buf); + } + if ( pageNumberRight ) + { + pageNumberRight->setDisabled(true); + if ( !((achDisplay.currentPage == achDisplay.pages.size() - 1) && achDisplay.pages.back().size() <= 4) ) // check last right page has entries + { + pageNumberRight->setDisabled(false); + char buf[32]; + snprintf(buf, sizeof(buf), Language::get(6181), (achDisplay.currentPage * 2) + 2, totalPages); + pageNumberRight->setText(buf); + } + if ( auto page_right_number_flourish = parent->findImage("page_right_number_flourish") ) + { + page_right_number_flourish->disabled = pageNumberRight->isDisabled(); + } + } + + for ( int i = 0; i < 8; ++i ) + { + std::string name = "ach_"; + name += std::to_string(i + 1); + auto ach = frame->findFrame(name.c_str()); + if ( ach ) + { + ach->setDisabled(true); + } + + if ( achDisplay.pages.size() <= 0 ) + { + continue; + } + + auto& page = achDisplay.pages[achDisplay.currentPage]; + if ( i < page.size() ) + { + auto& achName = page[i]; + auto& achData = Compendium_t::achievements[achName]; + if ( keystatus[SDLK_g] ) + { + achData.unlocked = true; + } + + bool hiddenGroup = !achData.unlocked && achData.hidden; + + ach->setDisabled(false); + + if ( auto unlockedTxt = ach->findField("unlocked") ) + { + unlockedTxt->setDisabled(true); + if ( !hiddenGroup && achData.unlocked ) + { + unlockedTxt->setDisabled(false); + char buffer[64]; + time_t t = (time_t)achData.unlockTime; + + char tbuf[64]; + struct tm* tm = localtime(&t); + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", tm); + snprintf(buffer, sizeof(buffer), Language::get(5325), tbuf); + + unlockedTxt->setText(buffer); + unlockedTxt->setColor(colorUnlocked); + } + } + if ( auto title = ach->findField("title") ) + { + if ( !strcmp(title->getFont(), smallfont_outline) ) + { + SDL_Rect pos = title->getSize(); + pos.y += 1; + title->setSize(pos); + } + title->setFont(bigfont_outline); + if ( hiddenGroup ) + { + title->setColor(colorTitleLocked); + if ( achDisplay.numHidden > 1 ) + { + title->setText(Language::get(5321)); + } + else + { + title->setText(Language::get(5322)); + } + } + else + { + title->setText(achData.name.c_str()); + if ( auto textGet = title->getTextObject() ) + { + if ( textGet->getWidth() >= title->getSize().w ) + { + if ( !strcmp(title->getFont(), bigfont_outline) ) + { + SDL_Rect pos = title->getSize(); + pos.y -= 1; + title->setSize(pos); + } + title->setFont(smallfont_outline); + } + } + title->setColor(achData.unlocked ? colorTitleUnlocked : colorTitleLocked); + } + } + if ( auto lore_points = ach->findField("lore_points") ) + { + if ( hiddenGroup ) + { + lore_points->setDisabled(true); + } + else + { + lore_points->setDisabled(false); + lore_points->setText(std::to_string(achData.lorePoints).c_str()); + lore_points->setColor(achData.unlocked ? 0xFFFFFFFF : colorTitleLocked); + } + } + if ( auto desc = ach->findField("desc") ) + { + if ( hiddenGroup ) + { + desc->setColor(colorDescLocked); + char buf[512] = ""; + if ( achDisplay.numHidden > 1 ) + { + snprintf(buf, sizeof(buf), Language::get(5323), achDisplay.numHidden); + } + else + { + snprintf(buf, sizeof(buf), Language::get(5324), achDisplay.numHidden); + } + desc->setText(buf); + desc->reflowTextToFit(0); + } + else + { + if ( achData.desc_formatted == "" ) + { + desc->setText(achData.desc.c_str()); + desc->reflowTextToFit(0); + achData.desc_formatted = desc->getText(); + } + else + { + desc->setText(achData.desc_formatted.c_str()); + } + desc->setColor(achData.unlocked ? colorDescUnlocked : colorDescLocked); + } + } + if ( auto img = ach->findImage("ach_img") ) + { + if ( !achData.unlocked ) { + img->path = std::string("*#images/achievements/") + achName + std::string("_l.png"); + if ( hiddenGroup ) + { + img->path = "*#images/achievements/LOCKED_ACHIEVEMENT.png"; + } + } + else { + img->path = std::string("*#images/achievements/") + achName + std::string(".png"); + } + } + auto dlc_badge_1 = ach->findImage("dlc_badge_1"); + if ( dlc_badge_1 ) + { + dlc_badge_1->disabled = true; + } + auto dlc_badge_2 = ach->findImage("dlc_badge_2"); + if ( dlc_badge_2 ) + { + dlc_badge_2->disabled = true; + } + auto dlc_badge_icon_1 = ach->findImage("dlc_badge_icon_1"); + if ( dlc_badge_icon_1 ) + { + dlc_badge_icon_1->disabled = true; + } + auto dlc_badge_icon_2 = ach->findImage("dlc_badge_icon_2"); + if ( dlc_badge_icon_2 ) + { + dlc_badge_icon_2->disabled = true; + } + if ( auto dlc_badge = ach->findImage("dlc_badge") ) + { + dlc_badge->disabled = true; + if ( achData.dlcType != Compendium_t::AchievementData_t::ACH_TYPE_NORMAL ) + { + if ( !hiddenGroup ) + { + if ( achData.dlcType == Compendium_t::AchievementData_t::ACH_TYPE_DLC1_DLC2 ) + { + if ( achData.unlocked ) + { + dlc_badge_2->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC1)[2]; + dlc_badge_1->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC2)[2]; + } + else + { + dlc_badge_2->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC1)[3]; + dlc_badge_1->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC2)[3]; + } + if ( dlc_badge_1->path != "" ) + { + dlc_badge_1->disabled = false; + } + if ( dlc_badge_2->path != "" ) + { + dlc_badge_2->disabled = false; + } + } + else + { + if ( achData.unlocked ) + { + dlc_badge->path = backingImgs.at(achData.dlcType)[2]; + } + else + { + dlc_badge->path = backingImgs.at(achData.dlcType)[3]; + } + if ( dlc_badge->path != "" ) + { + dlc_badge->disabled = false; + } + } + } + } + } + if ( auto dlc_badge_icon = ach->findImage("dlc_badge_icon") ) + { + dlc_badge_icon->disabled = true; + if ( achData.dlcType != Compendium_t::AchievementData_t::ACH_TYPE_NORMAL ) + { + if ( !hiddenGroup ) + { + if ( achData.dlcType == Compendium_t::AchievementData_t::ACH_TYPE_DLC1_DLC2 ) + { + if ( achData.unlocked ) + { + dlc_badge_icon_2->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC1)[4]; + dlc_badge_icon_1->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC2)[4]; + } + else + { + dlc_badge_icon_2->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC1)[6]; + dlc_badge_icon_1->path = backingImgs.at(Compendium_t::AchievementData_t::ACH_TYPE_DLC2)[6]; + } + if ( dlc_badge_icon_1->path != "" ) + { + dlc_badge_icon_1->disabled = false; + } + if ( dlc_badge_icon_2->path != "" ) + { + dlc_badge_icon_2->disabled = false; + } + } + else + { + if ( achData.unlocked ) + { + dlc_badge_icon->path = backingImgs.at(achData.dlcType)[4]; + } + else + { + dlc_badge_icon->path = backingImgs.at(achData.dlcType)[6]; + } + if ( dlc_badge_icon->path != "" ) + { + dlc_badge_icon->disabled = false; + } + } + } + } + } + if ( auto bg = ach->findImage("bg") ) + { + if ( !hiddenGroup ) + { + if ( achData.unlocked ) + { + bg->path = backingImgs.at(achData.dlcType)[1]; + } + else + { + bg->path = backingImgs.at(achData.dlcType)[0]; + } + } + else + { + bg->path = backingImgs.at(0)[0]; + } + } + if ( auto progress = ach->findFrame("progress") ) + { + progress->setDisabled(true); + if ( !hiddenGroup ) + { + if ( achData.achievementProgress >= 0 ) + { + int max = steamStatAchStringsAndMaxVals[achData.achievementProgress].second; + int statCur = g_SteamStats[achData.achievementProgress].m_iValue; + + progress->setDisabled(false); + if ( achData.unlocked ) + { + statCur = max; + } + if ( auto val = progress->findField("progress val") ) + { + char buf[32]; + snprintf(buf, sizeof(buf), "%d/%d", statCur, max); + val->setText(buf); + } + if ( auto fg = progress->findImage("progress fg") ) + { + if ( statCur > 0 ) + { + fg->disabled = false; + fg->pos.w = std::max(2, static_cast(82 * (statCur / (real_t)(max)))); + } + else + { + fg->disabled = true; + } + } + } + } + } + } + } + } + + static void compendiumPopulateAchievements(Frame* window) + { + if ( !window ) { return; } + + if ( auto frame = window->findFrame("achievements") ) + { + frame->removeSelf(); + } + + if ( auto frame = window->findFrame("page_left") ) + { + frame->removeSelf(); + } + + if ( auto frame = window->findFrame("page_right") ) + { + frame->removeSelf(); + } + + if ( auto frame = window->findFrame("page_right_unlock") ) + { + frame->removeSelf(); + } + + auto background = window->findImage("background"); + if ( !background ) { return; } + + auto frame = window->addFrame("achievements"); + frame->setSize(SDL_Rect{ background->pos.x + 32, background->pos.y, 884, 536 }); + + auto page_prev = window->addButton("page_prev"); + page_prev->setColor(makeColorRGB(128, 128, 128)); + page_prev->setSize(SDL_Rect{ background->pos.x, background->pos.y + background->pos.h / 2, 32, 32}); + page_prev->setCallback([](Button& button) { + if ( compendium_current == "achievements" ) + { + auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[compendium_contents_current[compendium_current]]; + achDisplay.currentPage = std::max(0, std::min(achDisplay.currentPage, (int)achDisplay.pages.size() - 1)); + if ( achDisplay.currentPage - 1 >= 0 ) + { + achDisplay.currentPage--; + refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + } + } + }); + + auto page_next = window->addButton("page_next"); + page_next->setColor(makeColorRGB(128, 128, 128)); + page_next->setSize(SDL_Rect{ background->pos.x + background->pos.w - 32, background->pos.y + background->pos.h / 2, 32, 32 }); + page_next->setCallback([](Button& button) { + if ( compendium_current == "achievements" ) + { + auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[compendium_contents_current[compendium_current]]; + achDisplay.currentPage = std::max(0, std::min(achDisplay.currentPage, (int)achDisplay.pages.size() - 1)); + if ( achDisplay.currentPage + 1 < achDisplay.pages.size() ) + { + achDisplay.currentPage++; + refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + } + } + }); + + for ( int i = 1; i <= 8; ++i ) + { + std::string name = "ach_"; + name += std::to_string(i); + auto ach = frame->addFrame(name.c_str()); + ach->setSize(SDL_Rect{ 0 + (i >= 5 ? 458 : 0), ((i - 1) % 4) * 132, 392 + 22, 134 + 6 }); + ach->setDisabled(true); + + + const int innerY = 14; + const int innerX = 22; + auto bg = ach->addImage(SDL_Rect{ 22, innerY, 392, 126 }, 0xFFFFFFFF, + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_00.png", "bg"); + + auto unlock = ach->addField("unlocked", 128); + unlock->setFont(smallfont_outline); + unlock->setSize(SDL_Rect{ bg->pos.x, 11, bg->pos.w, 20 }); + unlock->setHJustify(Field::justify_t::CENTER); + unlock->setVJustify(Field::justify_t::TOP); + unlock->setText(""); + unlock->setColor(makeColorRGB(255, 255, 0)); + + auto img = ach->addImage(SDL_Rect{ bg->pos.x + 24, bg->pos.y + 46, 64, 64 }, 0xFFFFFFFF, + "*#images/achievements/LOCKED_ACHIEVEMENT.png", "ach_img"); + + auto dlc_badge = ach->addImage(SDL_Rect{ bg->pos.x, bg->pos.y, 38, 38 }, 0xFFFFFFFF, + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", "dlc_badge"); + dlc_badge->disabled = true; + + auto dlc_badge_icon = ach->addImage(SDL_Rect{ dlc_badge->pos.x + 2, dlc_badge->pos.y + 2, 34, 34 }, 0xFFFFFFFF, + "", "dlc_badge_icon"); + dlc_badge_icon->disabled = true; + + { + auto dlc_badge_stack_1 = ach->addImage(SDL_Rect{ bg->pos.x + 4, bg->pos.y - 4, 38, 38 }, 0xFFFFFFFF, + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", "dlc_badge_1"); + dlc_badge_stack_1->disabled = true; + + auto dlc_badge_icon_1 = ach->addImage(SDL_Rect{ dlc_badge_stack_1->pos.x + 2, dlc_badge_stack_1->pos.y + 2, 34, 34 }, 0xFFFFFFFF, + "", "dlc_badge_icon_1"); + dlc_badge_icon_1->disabled = true; + + auto dlc_badge_stack_2 = ach->addImage(SDL_Rect{ bg->pos.x - 16, bg->pos.y + 10, 38, 38 }, 0xFFFFFFFF, + "*#images/ui/Main Menus/AdventureArchives/A_AchBox_Locked_Badge_00.png", "dlc_badge_2"); + dlc_badge_stack_2->disabled = true; + + auto dlc_badge_icon_2 = ach->addImage(SDL_Rect{ dlc_badge_stack_2->pos.x + 2, dlc_badge_stack_2->pos.y + 2, 34, 34 }, 0xFFFFFFFF, + "", "dlc_badge_icon_2"); + dlc_badge_icon_2->disabled = true; + } + + auto lore_points = ach->addField("lore_points", 32); + lore_points->setHJustify(Field::justify_t::CENTER); + lore_points->setVJustify(Field::justify_t::TOP); + lore_points->setFont(bigfont_outline); + lore_points->setSize(SDL_Rect{ bg->pos.x + bg->pos.w - 34, bg->pos.y + 10, 34, 28 }); + lore_points->setText(""); + + auto title = ach->addField("title", 128); + title->setSize(SDL_Rect{ 44 + innerX, 16 + innerY + 2, 314, 24 }); + title->setText(""); + title->setFont(bigfont_outline); + title->setColor(makeColor(224, 224, 224, 255)); + + auto desc = ach->addField("desc", 512); + desc->setSize(SDL_Rect{ 98 + innerX, 41 + innerY, 282, 80 }); + desc->setHJustify(Field::justify_t::LEFT); + desc->setVJustify(Field::justify_t::TOP); + desc->setFont(smallfont_outline); + desc->setPaddingPerLine(-2); + desc->setText(""); + desc->setColor(makeColor(224, 224, 224, 255)); + + auto progress = ach->addFrame("progress"); + progress->setSize(SDL_Rect{ bg->pos.x + bg->pos.w - 106 - 100, bg->pos.y + 96, 98 + 100, 28 }); + progress->setDisabled(true); + + auto progressBg = progress->addImage(SDL_Rect{ 100, 6, 98, 22 }, 0xFFFFFFFF, + "*#images/ui/Main Menus/AdventureArchives/A_Stat_Progress_BG_00.png", "progress bg"); + + auto progressFg = progress->addImage(SDL_Rect{ progressBg->pos.x + 8, progressBg->pos.y + 6, 10, 10 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/A_Stat_Progress_Fill_00.png", "progress fg"); + + auto progressVal = progress->addField("progress val", 32); + progressVal->setHJustify(Field::justify_t::RIGHT); + progressVal->setVJustify(Field::justify_t::TOP); + progressVal->setFont(smallfont_outline); + progressVal->setText(""); + progressVal->setSize(SDL_Rect{ 0, 1, progress->getSize().w - 4, progress->getSize().h }); + } + } + + static void compendiumPopulatePageRight(Frame* page_right) + { + if ( !page_right ) { return; } + + compendiumPageRightHideUnlocked(page_right, false, true); + + Frame* page_right_inner = page_right->findFrame("page_right_inner"); + if ( !page_right_inner ) + { + page_right_inner = page_right->addFrame("page_right_inner"); + page_right_inner->setSize(SDL_Rect{ 14, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); + page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); + page_right_inner->setScrollWithLeftControls(false); + page_right_inner->setHideGlyphs(true); + page_right_inner->setHideSelectors(true); + page_right_inner->setHideKeyboardGlyphs(true); + page_right_inner->setTickCallback(compendium_page_right_inner_fn); + } + + Frame* page_right_overlay = page_right->findFrame("page_right_overlay"); + if ( !page_right_overlay ) + { + if ( page_right_overlay = page_right->addFrame("page_right_overlay") ) + { + page_right_overlay->setSize(SDL_Rect{ 10, page_right->getSize().h - 114 - 18, page_right->getSize().w - 20 - 8, 120 }); + page_right_overlay->addImage(SDL_Rect{ 0, 0, page_right_overlay->getSize().w, page_right_overlay->getSize().h }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Records_Frame_00.png", "page right overlay img"); + + auto heading = page_right_overlay->addField("records txt", 32); + heading->setFont(menu_option_font); + heading->setText("RECORDS"); + heading->setHJustify(Field::justify_t::LEFT); + heading->setVJustify(Field::justify_t::TOP); + heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); + heading->setColor(makeColor(198, 190, 179, 255)); + heading->setTickCallback([](Widget& widget) { + if ( ticks % (2 * TICKS_PER_SECOND) == 0 && compendiumRecordsProcessedOnTick != ticks ) + { + compendiumRecordsSectionRandSequence++; + compendiumRecordsProcessedOnTick = ticks; + } + }); + + const int recordSpacing = 20; + const int recordTextOffsetW = 36; + auto record1 = page_right_overlay->addField("record 1 txt", 128); + record1->setFont(smallfont_outline); + record1->setText(""); + record1->setHJustify(Field::justify_t::LEFT); + record1->setVJustify(Field::justify_t::TOP); + record1->setSize(SDL_Rect{ 18, 24 + 11, page_right_overlay->getSize().w - recordTextOffsetW, 28 }); + record1->setColor(makeColor(135, 94, 45, 255)); + + auto record1val = page_right_overlay->addField("record 1 val", 64); + record1val->setFont(smallfont_outline); + record1val->setText(""); + record1val->setHJustify(Field::justify_t::RIGHT); + record1val->setVJustify(Field::justify_t::TOP); + record1val->setSize(record1->getSize()); + record1val->setColor(makeColor(159, 145, 127, 255)); + record1val->setTickCallback([](Widget& widget) { + size_t line = 0; + if ( compendiumRecordsSectionLoadedValues.size() > line && compendiumRecordsSectionLoadedValues[line].size() > 0 ) + { + Field* txt = static_cast(&widget); + size_t index = compendiumRecordsSectionRandSequence % compendiumRecordsSectionLoadedValues[line].size(); + txt->setText(compendiumRecordsSectionLoadedValues[line][index].c_str()); + } + }); auto record2 = page_right_overlay->addField("record 2 txt", 128); record2->setFont(smallfont_outline); @@ -35768,6 +36862,7 @@ namespace MainMenu { static void compendiumDebugRefresh() { + contents_activate_from_tab = true; consoleCommand("/reloadcompendiumlimbs"); if ( compendium_current == "monsters" ) { @@ -35808,6 +36903,7 @@ namespace MainMenu { } refreshCompendiumCamera(modelsPath); } + contents_activate_from_tab = false; } static void compendiumRevealSection(Button* button) @@ -35818,7 +36914,8 @@ namespace MainMenu { parent = parent->getParent(); if ( !parent ) { return; } - if ( auto page_right = parent->findFrame("page_right") ) + Frame* page_right = nullptr; + if ( page_right = parent->findFrame("page_right") ) { page_right->setOpacity(0.0); page_right->setInvisible(true); @@ -35828,6 +36925,7 @@ namespace MainMenu { bg->disabled = true; } + bool unlockFound = false; if ( auto page_right_unlock = parent->findFrame("page_right_unlock") ) { if ( auto to_unlock = page_right_unlock->findField("to_unlock") ) @@ -35844,6 +36942,7 @@ namespace MainMenu { if ( unlockStatus ) { auto findUnlock = unlockStatus->find(compendium_contents_current[compendium_current]); + unlockFound = true; if ( findUnlock != unlockStatus->end() ) { if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) @@ -35860,8 +36959,31 @@ namespace MainMenu { } } + if ( unlockFound && page_right ) + { + //page_right->select(); + } + else + { + if ( main_menu_frame ) + { + if ( auto compendium = main_menu_frame->findFrame("compendium") ) + { + if ( auto nav = compendium->findFrame("nav") ) + { + if ( auto contents = nav->findFrame("contents") ) + { + contents->activate(); + } + } + } + } + } + if ( auto reveal_top = parent->findFrame("page_right_reveal_top") ) { + compendiumEntryControlEnabled = true; + auto page_right_reveal = reveal_top->addFrame("page_right_reveal_top"); page_right_reveal->setSize(SDL_Rect{ 0, 0, 398, 484 }); page_right_reveal->addImage(SDL_Rect{ 0, 0, 398, 484 }, 0xFFFFFFFF, @@ -35941,6 +37063,7 @@ namespace MainMenu { { animTicks = 0; compendiumRevealAnimState = 1; + bool selected = frame->isSelected(); frame->removeSelf(); } } @@ -35949,6 +37072,7 @@ namespace MainMenu { } static void openCompendium() { + contents_activate_from_tab = true; players[0]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; auto dimmer = main_menu_frame->addFrame("dimmer"); @@ -35977,6 +37101,21 @@ namespace MainMenu { itemTooltipDisplay.scrolledToMax = 0; } + + (void)createBackWidget(window, [](Button& button) { + soundCancel(); + auto frame = static_cast(button.getParent()); + frame = static_cast(frame->getParent()); + frame = static_cast(frame->getParent()); + frame->removeSelf(); + assert(main_menu_frame); + if ( main_menu_frame ) { + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto compendium_button = buttons->findButton("Dungeon Compendium"); assert(compendium_button); + compendium_button->select(); + } + }/*, SDL_Rect{ -4, -4, 0, 0 }*/); + auto background = window->addImage( SDL_Rect{ *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, @@ -35998,6 +37137,15 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensHi_00.png"); tab->setColor(makeColorRGB(255, 255, 255)); tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft("contents"); + tab->setWidgetRight(compendiumCategories[1].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( compendium_current == button->getName() ) @@ -36012,6 +37160,34 @@ namespace MainMenu { } }); + { + Button* tab_left = window->addButton("tab_left"); + SDL_Rect pos = tab->getSize(); + pos.x -= (pos.w + 8); + pos.w = 0; + pos.h = 0; + tab_left->setSize(pos); + tab_left->setCallback([](Button& button) { + for ( int i = 0; i < compendiumCategories.size(); ++i ) + { + if ( compendium_current == compendiumCategories[i] ) + { + if ( Frame* parent = static_cast(button.getParent()) ) + { + if ( i > 0 ) + { + if ( auto tab = parent->findButton(compendiumCategories[i - 1].c_str()) ) + { + tab->getCallback()(*tab); + } + } + } + break; + } + } + }); + } + const int tab_title_y = 4; Field* tab_title = window->addField(tab->getName(), 32); tab_title->setText(Language::get(6174)); @@ -36035,8 +37211,8 @@ namespace MainMenu { tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + soundActivate(); compendium_current = "monsters"; - if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -36047,7 +37223,9 @@ namespace MainMenu { } compendiumPopulatePageRight(page_right); } + contents_activate_from_tab = true; compendiumPopulateContents(frame); + contents_activate_from_tab = false; } }); @@ -36058,6 +37236,15 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsHi_00.png"); tab->setColor(makeColorRGB(255, 255, 255)); tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft(compendiumCategories[0].c_str()); + tab->setWidgetRight(compendiumCategories[2].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( compendium_current == button->getName() ) @@ -36094,6 +37281,7 @@ namespace MainMenu { tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + soundActivate(); compendium_current = "items"; if ( auto frame = static_cast(button.getParent()) ) { @@ -36105,7 +37293,9 @@ namespace MainMenu { } compendiumPopulatePageRight(page_right); } + contents_activate_from_tab = true; compendiumPopulateContents(frame); + contents_activate_from_tab = false; } }); @@ -36115,6 +37305,15 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicHi_00.png"); tab->setColor(makeColorRGB(255, 255, 255)); tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft(compendiumCategories[1].c_str()); + tab->setWidgetRight(compendiumCategories[3].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( compendium_current == button->getName() ) @@ -36151,6 +37350,7 @@ namespace MainMenu { tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + soundActivate(); compendium_current = "magic"; if ( auto frame = static_cast(button.getParent()) ) { @@ -36162,7 +37362,9 @@ namespace MainMenu { } compendiumPopulatePageRight(page_right); } + contents_activate_from_tab = true; compendiumPopulateContents(frame); + contents_activate_from_tab = false; } }); @@ -36172,6 +37374,15 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldHi_00.png"); tab->setColor(makeColorRGB(255, 255, 255)); tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft(compendiumCategories[2].c_str()); + tab->setWidgetRight(compendiumCategories[4].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( compendium_current == button->getName() ) @@ -36209,6 +37420,7 @@ namespace MainMenu { tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "world"; + soundActivate(); if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -36219,7 +37431,9 @@ namespace MainMenu { } compendiumPopulatePageRight(page_right); } + contents_activate_from_tab = true; compendiumPopulateContents(frame); + contents_activate_from_tab = false; } }); @@ -36229,6 +37443,16 @@ namespace MainMenu { tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexHi_00.png"); tab->setColor(makeColorRGB(255, 255, 255)); tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft(compendiumCategories[3].c_str()); + tab->setWidgetRight(compendiumCategories[5].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( compendium_current == button->getName() ) @@ -36266,6 +37490,7 @@ namespace MainMenu { tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { compendium_current = "codex"; + soundActivate(); if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -36276,9 +37501,109 @@ namespace MainMenu { } compendiumPopulatePageRight(page_right); } + contents_activate_from_tab = true; compendiumPopulateContents(frame); + contents_activate_from_tab = false; } }); + + tab = window->addButton(compendiumCategories[5].c_str()); + tab->setSize(SDL_Rect{ background->pos.x + 672 + 104, background->pos.y + background->pos.h - 30, 110, 66 }); + tab->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_Achievements_00.png"); + tab->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); + tab->setColor(makeColorRGB(255, 255, 255)); + tab->setHighlightColor(makeColorRGB(255, 255, 255)); + tab->setWidgetBack("back_button"); + tab->setWidgetLeft(compendiumCategories[4].c_str()); + tab->setWidgetUp("contents"); + tab->setWidgetDown("contents"); + tab->setWidgetSearchParent("compendium"); + tab->addWidgetAction("MenuPageLeft", "tab_left"); + tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + + tab->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactiveHi_00.png"); + } + }); + + { + Button* tab_right = window->addButton("tab_right"); + SDL_Rect pos = tab->getSize(); + pos.x += (pos.w + 8); + pos.w = 0; + pos.h = 0; + tab_right->setSize(pos); + tab_right->setCallback([](Button& button) { + for ( int i = 0; i < compendiumCategories.size(); ++i ) + { + if ( compendium_current == compendiumCategories[i] ) + { + if ( Frame* parent = static_cast(button.getParent()) ) + { + if ( i + 1 < compendiumCategories.size() ) + { + if ( auto tab = parent->findButton(compendiumCategories[i + 1].c_str()) ) + { + tab->getCallback()(*tab); + } + } + } + break; + } + } + }); + } + + tab_title = window->addField(tab->getName(), 32); + tab_title->setText(Language::get(6184)); + tab_title->setFont(smallfont_outline); + tab_title->setOntop(true); + tab_title->setColor(makeColorRGB(220, 178, 113)); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setVJustify(Field::justify_t::TOP); + tab_title->setHJustify(Field::justify_t::CENTER); + tab_title->setTickCallback([](Widget& widget) { + auto field = static_cast(&widget); + if ( compendium_current == field->getName() ) + { + field->setColor(tabTextColorActive); + } + else + { + field->setColor(tabTextColorInactive); + } + }); + + tab->getTickCallback()(*tab); + tab->setCallback([](Button& button) { + compendium_current = "achievements"; + soundActivate(); + if ( auto frame = static_cast(button.getParent()) ) + { + if ( auto page_right = frame->findFrame("page_right") ) + { + if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) + { + page_right_inner->removeSelf(); + } + //compendiumPopulatePageRight(page_right); + } + contents_activate_from_tab = true; + compendiumPopulateAchievements(frame); + compendiumPopulateContents(frame); + contents_activate_from_tab = false; + } + }); } auto navigation = window->addFrame("nav"); @@ -36325,8 +37650,83 @@ namespace MainMenu { { frame->setAllowScrollBinds(false); } + + if ( compendium_current == "achievements" ) + { + frame->addWidgetAction("MenuLeft", "page_prev"); + frame->addWidgetAction("MenuRight", "page_next"); + } + else + { + auto w = frame->getWidgetActions(); + if ( w.find("MenuLeft") != w.end() ) + { + w.erase("MenuLeft"); + } + if ( w.find("MenuRight") != w.end() ) + { + w.erase("MenuRight"); + } + } + + if ( !isMouseVisible() ) + { + auto& list = frame->getEntries(); + int selection = frame->getSelection(); + if ( selection >= 0 && selection < list.size() ) + { + int selection_y = selection * frame->getEntrySize(); + if ( selection_y < frame->getActualSize().y ) + { + // look ahead for a selectable option + for ( int i = selection; i < list.size(); ++i ) + { + int y = i * frame->getEntrySize(); + if ( !list[i]->navigable ) + { + continue; + } + if ( y >= frame->getActualSize().y ) + { + frame->setSelection(i); + break; + } + } + } + if ( selection_y + frame->getEntrySize() > frame->getActualSize().y + frame->getSize().h ) + { + // look behind for a selectable option + for ( int i = selection; i >= 0; --i ) + { + if ( !list[i]->navigable ) + { + continue; + } + int y = i * frame->getEntrySize(); + if ( y + frame->getEntrySize() <= frame->getActualSize().y + frame->getSize().h ) + { + frame->setSelection(i); + break; + } + } + } + } + } }); + contents->setListMenuCancelOverride(true); + contents->addWidgetAction("MenuListCancel", "back_button"); + contents->setWidgetSearchParent("compendium"); + contents->addWidgetAction("MenuCancel", "back_button"); + contents->addWidgetAction("MenuPageLeft", "tab_left"); + contents->addWidgetAction("MenuPageRight", "tab_right"); + contents->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + //contents->addWidgetAction("MenuAlt1", "delete_entry"); + //contents->addWidgetAction("MenuAlt2", "open_filters"); + //contents->addWidgetAction("MenuPageLeft", "tab_left"); + //contents->addWidgetAction("MenuPageRight", "tab_left"); + //contents->addWidgetAction("MenuPageRightAlt", "kills_toggle_right"); + { auto nav_slider = navigation->addSlider("nav_slider"); nav_slider->setRailSize(SDL_Rect{ @@ -36342,8 +37742,15 @@ namespace MainMenu { nav_slider->setOrientation(Slider::SLIDER_VERTICAL); nav_slider->setHideGlyphs(true); nav_slider->setHideKeyboardGlyphs(true); - nav_slider->setHideSelectors(true); + nav_slider->setHideSelectors(false); nav_slider->setMenuConfirmControlType(0); + + nav_slider->setWidgetSearchParent("compendium"); + nav_slider->addWidgetAction("MenuCancel", "back_button"); + nav_slider->addWidgetAction("MenuPageLeft", "tab_left"); + nav_slider->addWidgetAction("MenuPageRight", "tab_right"); + nav_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + nav_slider->setCallback([](Slider& slider) { if ( auto frame = static_cast(slider.getParent()) ) { @@ -36671,6 +38078,137 @@ namespace MainMenu { page_right->setSize(SDL_Rect{ background->pos.x + 480 + 36, background->pos.y + 30, 398, 484}); page_right->setInvisible(true); page_right->setInheritParentFrameOpacity(false); + page_right->setHideGlyphs(true); + page_right->setHideSelectors(true); + page_right->setHideKeyboardGlyphs(true); + page_right->setWidgetSearchParent("compendium"); + page_right->addWidgetAction("MenuPageLeft", "tab_left"); + page_right->addWidgetAction("MenuPageRight", "tab_right"); + page_right->setTickCallback([](Widget& widget) { + Frame* page_right = static_cast(&widget); + if ( !page_right ) { + return; + } + if ( isMouseVisible() ) + { + page_right->setWidgetBack("back_button"); + } + else + { + page_right->setWidgetBack("right_back_button"); + } + Frame* page_right_inner = page_right->findFrame("page_right_inner"); + if ( !page_right_inner || isMouseVisible() ) { + return; + } + if ( page_right->isSelected() ) + { + // rescue focus to either contents, or list entries + int index = -1; + for ( auto f : page_right_inner->getFrames() ) + { + if ( f->isToBeDeleted() ) + { + continue; + } + if ( auto txt = f->findField("item name") ) + { + ++index; + if ( compendium_contents_list_current[compendium_current][compendium_contents_current[compendium_current]] == index ) + { + f->select(); + return; + break; + } + } + } + if ( auto slider = page_right->findSlider("right_slider") ) + { + if ( !slider->isDisabled() && !slider->isInvisible() ) + { + slider->select(); + return; + } + } + if ( Frame* compendium = page_right->getParent() ) + { + if ( Frame* nav = compendium->findFrame("nav") ) + { + if ( Frame* contents = nav->findFrame("contents") ) + { + contents_activate_from_tab = true; + contents->activate(); + contents_activate_from_tab = false; + } + } + } + } + + if ( main_menu_frame ) + { + auto selectedWidget = main_menu_frame->findSelectedWidget(widget.getOwner()); + if ( !selectedWidget || !selectedWidget->isChildOf(widget) ) + { + Frame* compendium = page_right->getParent(); + if ( compendium ) + { + if ( auto back = compendium->findFrame("right_back") ) + { + back->removeSelf(); + } + if ( !selectedWidget ) + { + if ( Frame* nav = compendium->findFrame("nav") ) + { + if ( Frame* contents = nav->findFrame("contents") ) + { + contents_activate_from_tab = true; + contents->activate(); + contents_activate_from_tab = false; + } + } + } + } + } + else if ( selectedWidget->isChildOf(widget) ) + { + Frame* compendium = page_right->getParent(); + if ( compendium && !compendium->findFrame("right_back") ) + { + Button* right_back_button = createBackWidget(compendium, [](Button& button) { + Frame* back = static_cast(button.getParent()); + if ( !back ) + { + return; + } + Frame* page_right = back->getParent(); + if ( !page_right ) + { + return; + } + if ( Frame* compendium = page_right->getParent() ) + { + if ( Frame* nav = compendium->findFrame("nav") ) + { + if ( Frame* contents = nav->findFrame("contents") ) + { + contents_activate_from_tab = true; + contents->activate(); + contents_activate_from_tab = false; + soundCancel(); + } + } + } + Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuListCancel"); + back->removeSelf(); + }, SDL_Rect{page_right->getSize().x, page_right->getSize().y - 16, 0, 0}); + right_back_button->setName("right_back_button"); + auto right_back = static_cast(right_back_button->getParent()); + right_back->setName("right_back"); + } + } + } + }); auto page_right_unlock = window->addFrame("page_right_unlock"); page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 18, 376, 116 }); @@ -36687,6 +38225,11 @@ namespace MainMenu { page_right_unlock_btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); page_right_unlock_btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-press_00.png"); page_right_unlock_btn->setOntop(true); + page_right_unlock_btn->setWidgetSearchParent("compendium"); + page_right_unlock_btn->setWidgetBack("back_button"); + page_right_unlock_btn->addWidgetAction("MenuPageLeft", "tab_left"); + page_right_unlock_btn->addWidgetAction("MenuPageRight", "tab_right"); + page_right_unlock_btn->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); page_right_unlock_btn->setCallback([](Button& button) { compendiumRevealSection(&button); button.setInvisible(true); @@ -36704,9 +38247,11 @@ namespace MainMenu { auto page_right_inner = page_right->addFrame("page_right_inner"); page_right_inner->setSize(SDL_Rect{ 14, compendiumPageRightInnerY, 362, compendiumPageRightInnerHeight }); page_right_inner->setActualSize(SDL_Rect{ 0, 0, 362, compendiumPageRightInnerHeight }); - page_right_inner->setTickCallback([](Widget& widget) { - static_cast(&widget)->setAllowScrollBinds(true); - }); + page_right_inner->setScrollWithLeftControls(false); + page_right_inner->setHideGlyphs(true); + page_right_inner->setHideSelectors(true); + page_right_inner->setHideKeyboardGlyphs(true); + page_right_inner->setTickCallback(compendium_page_right_inner_fn); auto page_right_title = window->addField("page_right_title", 128); page_right_title->setFont("fonts/kongtext.ttf#16#0"); @@ -36740,8 +38285,15 @@ namespace MainMenu { right_slider->setOrientation(Slider::SLIDER_VERTICAL); right_slider->setHideGlyphs(true); right_slider->setHideKeyboardGlyphs(true); - right_slider->setHideSelectors(true); + right_slider->setHideSelectors(false); right_slider->setMenuConfirmControlType(0); + + right_slider->setWidgetSearchParent("compendium"); + right_slider->addWidgetAction("MenuCancel", "right_back_button"); + right_slider->addWidgetAction("MenuPageLeft", "tab_left"); + right_slider->addWidgetAction("MenuPageRight", "tab_right"); + right_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + right_slider->setCallback([](Slider& slider) { if ( auto frame = static_cast(slider.getParent()) ) { @@ -36762,6 +38314,14 @@ namespace MainMenu { } }); right_slider->setTickCallback([](Widget& widget) { + if ( isMouseVisible() ) + { + widget.addWidgetAction("MenuCancel", "back_button"); + } + else + { + widget.addWidgetAction("MenuCancel", "right_back_button"); + } auto slider = static_cast(&widget); if ( auto frame = static_cast(widget.getParent()) ) { @@ -36773,6 +38333,11 @@ namespace MainMenu { const int diff = frame->getActualSize().h - frame->getSize().h; slider->setValue(100.0 * frame->getActualSize().y / diff); + + if ( slider->isSelected() ) + { + frame->setAllowScrollBinds(true); + } } else { @@ -36782,26 +38347,35 @@ namespace MainMenu { if ( widget.isSelected() ) { widget.deselect(); + if ( Frame* page_right = frame->getParent() ) + { + page_right->select(); + } } } } } }); - /*auto page_right_number = window->addField("page_right_number", 32); + auto page_right_number = window->addField("page_right_number", 32); page_right_number->setFont(smallfont_no_outline); page_right_number->setText(""); page_right_number->setHJustify(Field::justify_t::CENTER); page_right_number->setVJustify(Field::justify_t::TOP); - page_right_number->setSize(SDL_Rect{ page_right->getSize().x + 156, page_right->getSize().y + 497 + 8, 72, 28 }); - page_right_number->setColor(makeColorRGB(77, 37, 16));*/ + page_right_number->setSize(SDL_Rect{ page_right->getSize().x + 156 + 4, page_right->getSize().y + 497 + 14, 72, 28 }); + page_right_number->setColor(makeColorRGB(77, 37, 16)); + page_right_number->setDisabled(true); + + auto page_right_number_flourish = window->addImage(SDL_Rect{ 768 + 130, 598, 186, 22 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Page_Flourish_00.png", "page_right_number_flourish"); + page_right_number_flourish->disabled = true; auto page_left_number = window->addField("page_left_number", 32); page_left_number->setFont(smallfont_no_outline); page_left_number->setText(""); page_left_number->setHJustify(Field::justify_t::CENTER); page_left_number->setVJustify(Field::justify_t::TOP); - page_left_number->setSize(SDL_Rect{ page_left->getSize().x + 156, page_right->getSize().y + 497 + 8, 72, 28 }); + page_left_number->setSize(SDL_Rect{ page_left->getSize().x + 156, page_right->getSize().y + 497 + 14, 72, 28 }); page_left_number->setColor(makeColorRGB(77, 37, 16)); auto page_right_next = window->addField("page_right_next", 128); @@ -36822,14 +38396,6 @@ namespace MainMenu { compendiumPopulatePageRight(page_right); compendiumPopulateContents(window); - - (void)createBackWidget(window, [](Button& button) { - soundCancel(); - auto frame = static_cast(button.getParent()); - frame = static_cast(frame->getParent()); - frame = static_cast(frame->getParent()); - frame->removeSelf(); - assert(main_menu_frame); - }/*, SDL_Rect{ -4, -4, 0, 0 }*/); + contents_activate_from_tab = false; } } From c46a8d71d0a0330bc62025de7b52d5a18f7efa97 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 31 Jul 2024 09:12:39 +1000 Subject: [PATCH 039/244] * lich death knell sfx only play once in splitscreen --- src/actmonster.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index a0f70125e..24b6b4654 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -2675,7 +2675,17 @@ void actMonster(Entity* my) my->monsterLichAllyUID = 0; for ( int c = 0; c < MAXPLAYERS; c++ ) { - playSoundPlayer(c, 392, 128); + if ( multiplayer == SINGLE ) + { + if ( c == clientnum ) + { + playSoundPlayer(c, 392, 128); + } + } + else + { + playSoundPlayer(c, 392, 128); + } messagePlayerColor(c, MESSAGE_WORLD, uint32ColorBaronyBlue, Language::get(2647)); } } @@ -2694,7 +2704,17 @@ void actMonster(Entity* my) my->monsterLichAllyUID = 0; for ( int c = 0; c < MAXPLAYERS; c++ ) { - playSoundPlayer(c, 391, 128); + if ( multiplayer == SINGLE ) + { + if ( c == clientnum ) + { + playSoundPlayer(c, 391, 128); + } + } + else + { + playSoundPlayer(c, 391, 128); + } messagePlayerColor(c, MESSAGE_WORLD, uint32ColorOrange, Language::get(2649)); } } From fdb6104d5e4e7483ac288dc8535ea479feddf816 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 31 Jul 2024 11:30:47 +1000 Subject: [PATCH 040/244] * bounty hat no longer targets bots * add empty noise sfx for using empty bottle on empty sink --- src/item_tool.cpp | 1 + src/scores.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/item_tool.cpp b/src/item_tool.cpp index d5b2f65c9..e8f10f8b0 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -696,6 +696,7 @@ void Item::applyEmptyPotion(int player, Entity& entity) else { messagePlayer(player, MESSAGE_INTERACTION, Language::get(580)); + playSoundEntity(&entity, 140 + local_rng.rand() % 2, 64); } } return; diff --git a/src/scores.cpp b/src/scores.cpp index 0dc5fc48d..09fb2b06f 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -4812,6 +4812,16 @@ void AchievementObserver::updateData() Entity* mapCreature = (Entity*)node->element; if ( mapCreature && mapCreature->behavior == &actMonster ) { + if ( auto stats = mapCreature->getStats() ) + { + if ( stats->type == SPELLBOT + || stats->type == SENTRYBOT + || stats->type == DUMMYBOT + || stats->type == GYROBOT ) + { + continue; + } + } monstersGeneratedOnLevel.push_back(mapCreature); } } From 060e21429e9a51876fb4668d864c14b1555e143b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 2 Aug 2024 00:45:22 +1000 Subject: [PATCH 041/244] * scroll/lorebook/merchant/summon trap events * add lore point counts to sections --- src/actsummontrap.cpp | 8 ++++ src/entity.cpp | 12 +++++ src/interface/bookgui.cpp | 13 ++++++ src/interface/consolecommand.cpp | 38 +++++++++++++++- src/item_tool.cpp | 3 ++ src/item_usage_funcs.cpp | 13 ++++++ src/mod_tools.cpp | 75 ++++++++++++++++++++++++++++---- src/mod_tools.hpp | 10 +++++ src/scrolls.hpp | 1 + src/shops.cpp | 19 +++++--- 10 files changed, 176 insertions(+), 16 deletions(-) diff --git a/src/actsummontrap.cpp b/src/actsummontrap.cpp index ef33300e9..20b5912a7 100644 --- a/src/actsummontrap.cpp +++ b/src/actsummontrap.cpp @@ -113,6 +113,14 @@ void actSummonTrap(Entity* my) if ( monster && monster->getStats() ) { monster->seedEntityRNG(rng.getU32()); + if ( !(gameModeManager.getMode() == gameModeManager.GAME_MODE_TUTORIAL + || gameModeManager.getMode() == gameModeManager.GAME_MODE_TUTORIAL_INIT) ) + { + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_TRAP_SUMMONED_MONSTERS, "summoning trap", 1); + } + } if ( useCustomMonsters ) { std::string variantName = "default"; diff --git a/src/entity.cpp b/src/entity.cpp index 2800bb314..06f17deda 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -17070,16 +17070,28 @@ bool Entity::monsterAddNearbyItemToInventory(Stat* myStats, int rangeToFind, int //messagePlayer(owner->skill[2], MESSAGE_WORLD, Language::get(3888), myStats->name); players[c]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_BROADCAST, Language::get(3888)); + if ( c == owner->skill[2] ) + { + Compendium_t::Events_t::eventUpdateMonster(owner->skill[2], Compendium_t::CPDM_MERCHANT_ORBS, this, 1); + } break; case ARTIFACT_ORB_BLUE: //messagePlayer(owner->skill[2], MESSAGE_WORLD, Language::get(3889), myStats->name); players[c]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_BROADCAST, Language::get(3889)); + if ( c == owner->skill[2] ) + { + Compendium_t::Events_t::eventUpdateMonster(owner->skill[2], Compendium_t::CPDM_MERCHANT_ORBS, this, 1); + } break; case ARTIFACT_ORB_RED: //messagePlayer(owner->skill[2], MESSAGE_WORLD, Language::get(3890), myStats->name); players[c]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_BROADCAST, Language::get(3890)); + if ( c == owner->skill[2] ) + { + Compendium_t::Events_t::eventUpdateMonster(owner->skill[2], Compendium_t::CPDM_MERCHANT_ORBS, this, 1); + } break; default: break; diff --git a/src/interface/bookgui.cpp b/src/interface/bookgui.cpp index 3b7dfc0ea..14204435e 100644 --- a/src/interface/bookgui.cpp +++ b/src/interface/bookgui.cpp @@ -560,6 +560,19 @@ void Player::BookGUI_t::openBook(int index, Item* item) node->deconstructor = &defaultDeconstructor; } + Compendium_t::Events_t::eventUpdate(player.playernum, Compendium_t::CPDM_LORE_READ, READABLE_BOOK, 1); + if ( numbooks > 0 ) + { + if ( index % numbooks >= 32 ) + { + Compendium_t::Events_t::eventUpdate(player.playernum, Compendium_t::CPDM_LORE_PERCENT_READ_2, READABLE_BOOK, (1 << ((index % numbooks) - 32))); + } + else + { + Compendium_t::Events_t::eventUpdate(player.playernum, Compendium_t::CPDM_LORE_PERCENT_READ, READABLE_BOOK, (1 << (index % numbooks))); + } + } + // activate the steam achievement if ( list_Size(&booksRead) >= numbooks ) { diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index c10a64836..9614af2d1 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -608,8 +608,42 @@ namespace ConsoleCommands { name.append(" "); name.append(argv[arg]); } - dropItem(newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook(name.c_str()), true, &stats[clientnum]->inventory), 0); - }); + + int i = 0; + for ( i = 0; i < numbooks; ++i ) + { + if ( strcmp(getBookDefaultNameFromIndex(i).c_str(), name.c_str()) == 0 ) + { + dropItem(newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook(getBookDefaultNameFromIndex(i)), true, &stats[clientnum]->inventory), 0); + break; + } + } + + if ( i == numbooks ) + { + for ( i = 0; i < numbooks; ++i ) + { + if ( strstr(getBookDefaultNameFromIndex(i).c_str(), name.c_str()) ) + { + dropItem(newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook(getBookDefaultNameFromIndex(i)), true, &stats[clientnum]->inventory), 0); + break; + } + } + } + }); + + static ConsoleCommand ccmd_spawnallbooks("/spawnallbooks", "spawn all readable books (cheat)", []CCMD{ + if ( !(svFlags & SV_FLAG_CHEATS) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); + return; + } + + for ( int i = 0; i < numbooks; ++i ) + { + dropItem(newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook(getBookDefaultNameFromIndex(i)), true, &stats[clientnum]->inventory), 0); + } + }); static ConsoleCommand ccmd_savemap("/savemap", "save the current level to disk", []CCMD{ if (argc > 1) diff --git a/src/item_tool.cpp b/src/item_tool.cpp index e8f10f8b0..c6ce3c4f4 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -643,16 +643,19 @@ void Item::applyOrb(int player, ItemType type, Entity& entity) //messagePlayer(player, MESSAGE_WORLD, Language::get(3888), entity.getStats()->name); players[player]->worldUI.worldTooltipDialogue.createDialogueTooltip(entity.getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(3888)); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_MERCHANT_ORBS, &entity, 1); break; case ARTIFACT_ORB_BLUE: //messagePlayer(player, MESSAGE_WORLD, Language::get(3889), entity.getStats()->name); players[player]->worldUI.worldTooltipDialogue.createDialogueTooltip(entity.getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(3889)); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_MERCHANT_ORBS, &entity, 1); break; case ARTIFACT_ORB_RED: //messagePlayer(player, MESSAGE_WORLD, Language::get(3890), entity.getStats()->name); players[player]->worldUI.worldTooltipDialogue.createDialogueTooltip(entity.getUID(), Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(3890)); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_MERCHANT_ORBS, &entity, 1); break; default: break; diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 793e8fdac..3dd902070 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -26,6 +26,7 @@ #include "scores.hpp" #include "prng.hpp" #include "mod_tools.hpp" +#include "scrolls.hpp" bool item_PotionWater(Item*& item, Entity* entity, Entity* usedBy) { @@ -2400,6 +2401,8 @@ void item_ScrollMail(Item* item, int player) conductIlliterate = false; } item->identified = true; + + int result = item->appearance % 25; switch ( item->appearance % 25 ) { case 0: @@ -2473,8 +2476,13 @@ void item_ScrollMail(Item* item, int player) break; default: messagePlayer(player, MESSAGE_WORLD | MESSAGE_INTERACTION, Language::get(846)); + result = 22; break; } + + result = result % NUM_SCROLL_MAIL_OPTIONS; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LORE_READ, SCROLL_MAIL, 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LORE_PERCENT_READ, SCROLL_MAIL, (1 << (result))); } void item_ScrollIdentify(Item* item, int player) @@ -5568,6 +5576,11 @@ void item_FoodAutomaton(Item*& item, int player) break; } + if ( item->type == SCROLL_MAIL || item->type == READABLE_BOOK ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LORE_BURNT, item->type, 1); + } + if ( itemCategory(item) == SCROLL ) { if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 156ba6b0b..88df4f278 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10838,6 +10838,10 @@ void Compendium_t::readItemsFromFile() obj.items_in_category.push_back(item); } } + if ( w.HasMember("lore_points") ) + { + obj.lorePoints = w["lore_points"].GetInt(); + } std::set alwaysTrackedEvents = { "APPRAISED", @@ -11045,6 +11049,11 @@ void Compendium_t::readMagicFromFile() } } + if ( w.HasMember("lore_points") ) + { + obj.lorePoints = w["lore_points"].GetInt(); + } + //std::list spellsToSort; //bool spells = name.find("spells") != std::string::npos; //bool spellbooks = name.find("spellbooks") != std::string::npos; @@ -11338,6 +11347,10 @@ void Compendium_t::readCodexFromFile() { jsonVecToVec(w["models"], obj.models); } + if ( w.HasMember("lore_points") ) + { + obj.lorePoints = w["lore_points"].GetInt(); + } Compendium_t::Events_t::eventCodexIDLookup[name] = obj.id; Compendium_t::Events_t::codexIDToString[obj.id + Compendium_t::Events_t::kEventCodexOffset] = name; @@ -11470,6 +11483,10 @@ void Compendium_t::readWorldFromFile() { jsonVecToVec(w["models"], obj.models); } + if ( w.HasMember("lore_points") ) + { + obj.lorePoints = w["lore_points"].GetInt(); + } Compendium_t::Events_t::eventWorldIDLookup[name] = obj.id; Compendium_t::Events_t::worldIDToString[obj.id + Compendium_t::Events_t::kEventWorldOffset] = name; @@ -11628,6 +11645,13 @@ void Compendium_t::readMonstersFromFile() Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); + + if ( pair.first == "mysterious shop" ) + { + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_MERCHANT_ORBS].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_SHOP_BOUGHT].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_SHOP_SPENT].insert(type); + } } } @@ -11660,6 +11684,10 @@ void Compendium_t::readMonstersFromFile() { monster.imagePath = m["img"].GetString(); } + if ( m.HasMember("lore_points") ) + { + monster.lorePoints = m["lore_points"].GetInt(); + } auto& stats = m["stats"]; jsonVecToVec(stats["hp"], monster.hp); jsonVecToVec(stats["ac"], monster.ac); @@ -12804,6 +12832,15 @@ void Compendium_t::readUnlocksSaveData() { CompendiumCodex_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } + /*CompendiumCodex_t::unlocks["class"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["classes list"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["races"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["stats metastats"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["melee"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["crits"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["flanking"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumCodex_t::unlocks["backstabs"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED;*/ + for ( auto& data : CompendiumEntries.items ) { for ( auto& entry : data.second.items_in_category ) @@ -14197,14 +14234,10 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag auto find = worldIDToString.find(worldID); if ( find != worldIDToString.end() ) { - auto& worldDefs = CompendiumEntries.worldObjects[find->second]; - if ( worldDefs.unlockTags.find(tag) != worldDefs.unlockTags.end() ) + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) { - auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks[find->second]; - if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) - { - unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; - } + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } } } @@ -14262,6 +14295,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag } int codexID = -1; + int baseCodexID = -1; if ( entryID >= 0 ) { codexID = entryID; @@ -14274,7 +14308,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag { for ( auto& pair : findClassTag->second ) { - if ( pair.second == (codexID < kEventCodexOffset) ? (codexID + kEventCodexOffset) : codexID ) + if ( pair.second == ((codexID < kEventCodexOffset) ? (codexID + kEventCodexOffset) : codexID) ) { foundCategory = true; break; @@ -14313,7 +14347,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag if ( find != eventCodexIDLookup.end() ) { codexID = find->second; - + baseCodexID = codexID; if ( def.attributes.find("class") != def.attributes.end() ) { auto findClassTag = eventClassIds.find(tag); @@ -14377,6 +14411,13 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag { codexID += kEventCodexOffset; // convert to offset } + if ( baseCodexID >= 0 ) + { + if ( baseCodexID < kEventCodexOffset || loadingValue ) + { + baseCodexID += kEventCodexOffset; // convert to offset + } + } auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; @@ -14433,6 +14474,22 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag } } } + + if ( playernum == clientnum ) + { + if ( baseCodexID >= 0 ) + { + auto find = codexIDToString.find(baseCodexID); + if ( find != codexIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumCodex_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + } } Uint8 Compendium_t::Events_t::clientSequence = 0; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 1410024d2..4c9f80d18 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3813,6 +3813,12 @@ struct Compendium_t CPDM_MINEHEAD_TOTAL_PLAYTIME, CPDM_MINEHEAD_ENTER_SPLIT_MP, CPDM_TOTAL_TIME_SPENT, + CPDM_TRAP_SUMMONED_MONSTERS, + CPDM_LORE_READ, + CPDM_LORE_BURNT, + CPDM_LORE_PERCENT_READ, + CPDM_LORE_PERCENT_READ_2, + CPDM_MERCHANT_ORBS, CPDM_EVENT_TAGS_MAX }; @@ -3835,6 +3841,7 @@ struct Compendium_t std::string imagePath = ""; std::vector models; std::set unlockAchievements; + int lorePoints = 0; }; static std::map>> contents; static std::map contentsMap; @@ -3874,6 +3881,7 @@ struct Compendium_t std::set unlockAchievements; std::set unlockTags; int id = -1; + int lorePoints = 0; }; static std::map>> contents; static std::map contentsMap; @@ -3895,6 +3903,7 @@ struct Compendium_t std::vector models; int id = -1; CompendiumView_t view; + int lorePoints = 0; }; static std::map>> contents; static std::map contentsMap; @@ -3921,6 +3930,7 @@ struct Compendium_t std::string imagePath = ""; std::vector blurb; std::vector items_in_category; + int lorePoints = 0; }; static std::map>> contents; static std::map contentsMap; diff --git a/src/scrolls.hpp b/src/scrolls.hpp index 3c7306f0c..ef563df3d 100644 --- a/src/scrolls.hpp +++ b/src/scrolls.hpp @@ -12,6 +12,7 @@ #pragma once #define NUMLABELS 25 +#define NUM_SCROLL_MAIL_OPTIONS 23 static char scroll_label[NUMLABELS][512] = { "ZELGO MER", diff --git a/src/shops.cpp b/src/shops.cpp index 9f2e3056e..fb391c156 100644 --- a/src/shops.cpp +++ b/src/shops.cpp @@ -229,8 +229,18 @@ bool buyItemFromShop(const int player, Item* item, bool& bOutConsumedEntireStack shopChangeGoldEvent(player, -item->buyValue(player)); stats[player]->GOLD -= item->buyValue(player); - Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_BOUGHT, "shop", 1); - Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_SPENT, "shop", item->buyValue(player)); + + Entity* entity = uidToEntity(shopkeeper[player]); + if ( shopIsMysteriousShopkeeper(entity) ) + { + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_SHOP_BOUGHT, entity, 1); + Compendium_t::Events_t::eventUpdateMonster(player, Compendium_t::CPDM_SHOP_SPENT, entity, item->buyValue(player)); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_BOUGHT, "shop", 1); + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SHOP_SPENT, "shop", item->buyValue(player)); + } if ( stats[player]->playerRace > 0 && players[player] && players[player]->entity->effectPolymorph > NUMMONSTERS ) { @@ -247,7 +257,6 @@ bool buyItemFromShop(const int player, Item* item, bool& bOutConsumedEntireStack item->count = ocount; if ( multiplayer != CLIENT ) { - Entity* entity = uidToEntity(shopkeeper[player]); if (entity) { Stat* shopstats = entity->getStats(); @@ -346,9 +355,9 @@ bool buyItemFromShop(const int player, Item* item, bool& bOutConsumedEntireStack net_packet->len = 30; sendPacketSafe(net_sock, -1, net_packet, 0); } - if ( shopIsMysteriousShopkeeper(uidToEntity(shopkeeper[player])) ) + if ( shopIsMysteriousShopkeeper(entity) ) { - buyItemFromMysteriousShopkeepConsumeOrb(player, *(uidToEntity(shopkeeper[player])), *item); + buyItemFromMysteriousShopkeepConsumeOrb(player, *entity, *item); } if ( itemTypeIsQuiver(item->type) ) { From 9c46e01da5b53262dcacf8771379bdbde00ba73b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 2 Aug 2024 03:59:48 +1000 Subject: [PATCH 042/244] * lang file update --- .../contents_achievements.json | 19 +++ lang/compendium_lang/contents_codex.json | 111 +++++++++++++ lang/compendium_lang/contents_items.json | 153 ++++++++++++++++++ lang/compendium_lang/contents_magic.json | 41 +++++ lang/compendium_lang/contents_monsters.json | 111 +++++++++++++ lang/compendium_lang/contents_world.json | 115 +++++++++++++ lang/en.txt | 9 +- 7 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 lang/compendium_lang/contents_achievements.json create mode 100644 lang/compendium_lang/contents_codex.json create mode 100644 lang/compendium_lang/contents_items.json create mode 100644 lang/compendium_lang/contents_magic.json create mode 100644 lang/compendium_lang/contents_monsters.json create mode 100644 lang/compendium_lang/contents_world.json diff --git a/lang/compendium_lang/contents_achievements.json b/lang/compendium_lang/contents_achievements.json new file mode 100644 index 000000000..d34d42887 --- /dev/null +++ b/lang/compendium_lang/contents_achievements.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "contents": [ + {" CATEGORIES": "-"}, + {"ADVENTURE": "adventure"}, + {"EXPERIENCE": "experience"}, + {"JOKER'S CHOICE": "joker"}, + {"TEAMWORK": "teamwork"}, + {"TECHNIQUE": "technique"} + ], + "contents_alphabetical": [ + {" CATEGORIES": "-"}, + {"ADVENTURE": "adventure"}, + {"EXPERIENCE": "experience"}, + {"JOKER'S CHOICE": "joker"}, + {"TEAMWORK": "teamwork"}, + {"TECHNIQUE": "technique"} + ] +} \ No newline at end of file diff --git a/lang/compendium_lang/contents_codex.json b/lang/compendium_lang/contents_codex.json new file mode 100644 index 000000000..25e201e75 --- /dev/null +++ b/lang/compendium_lang/contents_codex.json @@ -0,0 +1,111 @@ +{ + "version": 1, + "contents": [ + {" CHARACTER": "-"}, + {"CLASS OVERVIEW": "class"}, + {"CLASSES": "classes list"}, + {"RACE & ALIGNMENT": "races"}, + {"EXPERIENCE POINTS (XP)": "xp"}, + {"LEVELING UP": "leveling up"}, + {" STATS": "-"}, + {"STATS & METASTATS": "stats metastats"}, + {"STRENGTH (STR)": "str"}, + {"DEXTERITY (DEX)": "dex"}, + {"CONSTITUTION (CON)": "con"}, + {"INTELLIGENCE (INT)": "int"}, + {"PERCEPTION (PER)": "per"}, + {"CHARISMA (CHR)": "chr"}, + {"ARMOR CLASS (AC)": "ac"}, + {"HEALTH POINTS (HP)": "hp"}, + {"MAGIC POINTS (MP)": "mp"}, + {"MAGIC RESIST (RES)": "res"}, + {"MAGIC POWER (PWR)": "pwr"}, + {"REGENERATION (RGN)": "rgn"}, + {"WEIGHT (WGT)": "wgt"}, + {" MECHANICS": "-"}, + {"MELEE ATTACKS": "melee"}, + {"CRITICAL STRIKES": "crits"}, + {"FLANKING STRIKES": "flanking"}, + {"BACKSTABBING": "backstabs"}, + {"SNEAKING": "sneaking"}, + {"LEGENDARY STRIKES": "legendary strikes"}, + {"BLOCKING": "blocking"}, + {"STRAFE & BACKPEDAL": "strafing"}, + {"MEMORIZED CASTING": "memorized"}, + {"SPELLBOOK CASTING": "spellbook casting"}, + {"MISSILE ATTACKS": "missiles"}, + {"THROWN ATTACKS": "thrown"}, + {"ITEM DURABILITY": "equipment degradation"}, + {" SKILLS": "-"}, + {"SKILLS": "skills"}, + {"SWORD SKILL": "sword skill"}, + {"AXE SKILL": "axe skill"}, + {"POLEARM SKILL": "polearm skill"}, + {"MACE SKILL": "mace skill"}, + {"UNARMED SKILL": "unarmed skill"}, + {"STEALTH SKILL": "stealth skill"}, + {"BLOCKING SKILL": "blocking skill"}, + {"RANGED SKILL": "ranged skill"}, + {"MAGIC SKILL": "magic skill"}, + {"CASTING SKILL": "casting skill"}, + {"APPRAISAL SKILL": "appraisal skill"}, + {"SWIMMING SKILL": "swimming skill"}, + {"LEADERSHIP SKILL": "leadership skill"}, + {"TRADING SKILL": "trading skill"}, + {"WANTED STATUS": "wanted"}, + {"ALCHEMY SKILL": "alchemy skill"}, + {"TINKERING SKILL": "tinkering skill"} + ], + "contents_alphabetical": [ + {"ALCHEMY SKILL": "alchemy skill"}, + {"APPRAISAL SKILL": "appraisal skill"}, + {"ARMOR CLASS (AC)": "ac"}, + {"AXE SKILL": "axe skill"}, + {"BACKSTABBING": "backstabs"}, + {"BLOCKING SKILL": "blocking skill"}, + {"BLOCKING": "blocking"}, + {"CASTING MEMORIZED SPELLS": "memorized"}, + {"CASTING SKILL": "casting skill"}, + {"CHARISMA (CHR)": "chr"}, + {"CLASS": "class"}, + {"CLASSES LIST": "classes list"}, + {"CONSTITUTION (CON)": "con"}, + {"CRITICAL STRIKES": "crits"}, + {"DEXTERITY (DEX)": "dex"}, + {"EXPERIENCE POINTS (XP)": "xp"}, + {"FLANKING STRIKES": "flanking"}, + {"HEALTH POINTS (HP)": "hp"}, + {"INTELLIGENCE (INT)": "int"}, + {"ITEM DURABILITY": "equipment degradation"}, + {"LEADERSHIP SKILL": "leadership skill"}, + {"LEGENDARY STRIKES": "legendary strikes"}, + {"LEVELING UP": "leveling up"}, + {"MACE SKILL": "mace skill"}, + {"MAGIC POINTS (MP)": "mp"}, + {"MAGIC POWER (PWR)": "pwr"}, + {"MAGIC RESIST (RES)": "res"}, + {"MAGIC SKILL": "magic skill"}, + {"MELEE ATTACKS": "melee"}, + {"MISSILE ATTACKS": "missiles"}, + {"PERCEPTION (PER)": "per"}, + {"POLEARM SKILL": "polearm skill"}, + {"RACE & ALIGNMENT": "races"}, + {"RANGED SKILL": "ranged skill"}, + {"REGENERATION (RGN)": "rgn"}, + {"SKILLS": "skills"}, + {"SNEAKING": "sneaking"}, + {"SPELLBOOK CASTING": "spellbook casting"}, + {"STATS & METASTATS": "stats metastats"}, + {"STEALTH SKILL": "stealth skill"}, + {"STRAFE & BACKPEDAL": "strafing"}, + {"STRENGTH (STR)": "str"}, + {"SWIMMING SKILL": "swimming skill"}, + {"SWORD SKILL": "sword skill"}, + {"THROWN ATTACKS": "thrown"}, + {"TINKERING SKILL": "tinkering skill"}, + {"TRADING SKILL": "trading skill"}, + {"UNARMED SKILL": "unarmed skill"}, + {"WANTED STATUS": "wanted"}, + {"WEIGHT (WGT)": "wgt"} + ] +} \ No newline at end of file diff --git a/lang/compendium_lang/contents_items.json b/lang/compendium_lang/contents_items.json new file mode 100644 index 000000000..4c0712302 --- /dev/null +++ b/lang/compendium_lang/contents_items.json @@ -0,0 +1,153 @@ +{ + "version": 1, + "contents": [ + {" EQUIPMENT": "-"}, + {"TORCHES & LANTERNS": "torch and lantern"}, + {"SHIELDS": "shields"}, + {"CRYSTAL SHARD": "crystal shard"}, + {"MIRRORS": "mirrors"}, + {"HATS & HOODS": "hats & hoods"}, + {"CROWNS & HEADDRESSES": "crowns & headdresses"}, + {"FACE ACCESSORIES": "face accessories"}, + {"MASKS & VISORS": "masks & visors"}, + {"CLOAKS & CLOTHING": "cloaks & clothing"}, + {"BACKPACKS": "backpacks"}, + {"LEATHER ARMOR": "leather armor"}, + {"IRON ARMOR": "iron armor"}, + {"STEEL ARMOR": "steel armor"}, + {"CRYSTAL ARMOR": "crystal armor"}, + {"SPHINX'S CASQUE": "sphinxs casque"}, + {"ORACLE'S TREADS": "oracles treads"}, + {"DJINNI'S BRACE": "djinnis brace"}, + {"DRAGON'S MAIL": "dragons mail"}, + {"WRAITH'S GOWN": "wraiths gown"}, + {" MELEE WEAPONRY": "-"}, + {"SWORDS": "swords"}, + {"AXES": "axes"}, + {"MACES": "maces"}, + {"POLEARMS": "polearms"}, + {"DYRNWYN": "dyrnwyn"}, + {"PARASHU": "parashu"}, + {"SHARUR": "sharur"}, + {"GUNGNIR": "gungnir"}, + {"FIST WEAPONS": "fist weapons"}, + {"WHIP": "whip"}, + {" RANGED WEAPONRY": "-"}, + {"TOMAHAWKS & DAGGERS": "tomahawks & daggers"}, + {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, + {"BOOMERANG": "boomerang"}, + {"SLINGSHOT": "slingshot"}, + {"BOWS": "bows"}, + {"CROSSBOW": "crossbow"}, + {"ARBALEST": "arbalest"}, + {"KHRYSELAKATOS": "khryselakatos"}, + {"SWIFT & SPRINGSHOT AMMO": "swift and springshot ammo"}, + {"SILVER & PIERCING AMMO": "silver and piercing ammo"}, + {"FIRE & HUNTING AMMO": "fire and hunting ammo"}, + {"CRYSTAL AMMO": "crystal ammo"}, + {" GEMSTONES": "-"}, + {"ROCKS & GEMS": "rocks & gems"}, + {" FOOD": "-"}, + {"WATER": "water"}, + {"MORSELS": "cheese and apples"}, + {"CREAM PIE": "cream pie"}, + {"TOMALLEY": "tomalley"}, + {"MEALS": "meat, fish, and bread"}, + {"TIN & TIN OPENER": "tin and tin opener"}, + {"BLOOD VIALS": "blood vials"}, + {" POTIONS": "-"}, + {"BOOZE": "booze"}, + {"EMPTY BOTTLE": "empty bottle"}, + {"DEFENSIVE POTIONS": "defensive potions"}, + {"OFFENSIVE POTIONS": "offensive potions"}, + {"ALEMBIC": "alembic"}, + {" TOOLS": "-"}, + {"MINING PICK": "mining pick"}, + {"TINKERING KIT": "tinkering kit"}, + {"LOCKPICK": "lockpick"}, + {"SKELETON KEY": "skeleton key"}, + {"BEARTRAP": "beartrap"}, + {"NOISEMAKER": "noisemaker"}, + {"DUMMYBOT": "dummybot"}, + {"SENTRY BOT & SPELLBOT": "sentrybot and magic sentry"}, + {"GYROBOT": "gyrobot"}, + {"TINKERED TRAPS": "tinkered traps"}, + {"TELEPORTATION TRAP": "teleportation trap"}, + {" OTHER": "-"}, + {"TOWEL": "towel"}, + {"MAIL & LORE BOOKS": "mail & lore books"}, + {"MYSTIC ORB": "mystic orb"}, + {"DEATH BOX": "death box"} + ], + "contents_alphabetical": [ + {"ALEMBIC": "alembic"}, + {"ARBALEST": "arbalest"}, + {"AXES": "axes"}, + {"BACKPACKS": "backpacks"}, + {"BEARTRAP": "beartrap"}, + {"BLOOD VIALS": "blood vials"}, + {"BOOMERANG": "boomerang"}, + {"BOOZE": "booze"}, + {"BOWS": "bows"}, + {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, + {"CHEESE & APPLES": "cheese and apples"}, + {"CLOAKS & CLOTHING": "cloaks & clothing"}, + {"CREAM PIE": "cream pie"}, + {"CROSSBOW": "crossbow"}, + {"CROWNS & HEADDRESSES": "crowns & headdresses"}, + {"CRYSTAL AMMO": "crystal ammo"}, + {"CRYSTAL ARMOR": "crystal armor"}, + {"CRYSTAL SHARD": "crystal shard"}, + {"DEATH BOX": "death box"}, + {"DEFENSIVE POTIONS": "defensive potions"}, + {"DJINNI'S BRACE": "djinnis brace"}, + {"DRAGON'S MAIL": "dragons mail"}, + {"DUMMYBOT": "dummybot"}, + {"DYRNWYN": "dyrnwyn"}, + {"EMPTY BOTTLE": "empty bottle"}, + {"FACE ACCESSORIES": "face accessories"}, + {"FIRE & HUNTING AMMO": "fire and hunting ammo"}, + {"FIST WEAPONS": "fist weapons"}, + {"GUNGNIR": "gungnir"}, + {"GYROBOT": "gyrobot"}, + {"HATS & HOODS": "hats & hoods"}, + {"IRON ARMOR": "iron armor"}, + {"KHRYSELAKATOS": "khryselakatos"}, + {"LEATHER ARMOR": "leather armor"}, + {"LOCKPICK": "lockpick"}, + {"MACES": "maces"}, + {"MAIL & LORE BOOKS": "mail & lore books"}, + {"MASKS & VISORS": "masks & visors"}, + {"MEAT, FISH, & BREAD": "meat, fish, and bread"}, + {"MINING PICK": "mining pick"}, + {"MIRRORS": "mirrors"}, + {"MYSTIC ORB": "mystic orb"}, + {"NOISEMAKER": "noisemaker"}, + {"OFFENSIVE POTIONS": "offensive potions"}, + {"ORACLE'S TREADS": "oracles treads"}, + {"PARASHU": "parashu"}, + {"POLEARMS": "polearms"}, + {"ROCKS & GEMS": "rocks & gems"}, + {"SENTRY BOT & SPELLBOT": "sentrybot and magic sentry"}, + {"SHARUR": "sharur"}, + {"SHIELDS": "shields"}, + {"SILVER & PIERCING AMMO": "silver and piercing ammo"}, + {"SKELETON KEY": "skeleton key"}, + {"SLINGSHOT": "slingshot"}, + {"SPHINX'S CASQUE": "sphinxs casque"}, + {"STEEL ARMOR": "steel armor"}, + {"SWIFT & SPRINGSHOT AMMO": "swift and springshot ammo"}, + {"SWORDS": "swords"}, + {"TELEPORTATION TRAP": "teleportation trap"}, + {"TIN & TIN OPENER": "tin and tin opener"}, + {"TINKERED TRAPS": "tinkered traps"}, + {"TINKERING KIT": "tinkering kit"}, + {"TOMAHAWKS & DAGGERS": "tomahawks & daggers"}, + {"TOMALLEY": "tomalley"}, + {"TORCHES & LANTERNS": "torch and lantern"}, + {"TOWEL": "towel"}, + {"WATER": "water"}, + {"WHIP": "whip"}, + {"WRAITH'S GOWN": "wraiths gown"} + ] +} \ No newline at end of file diff --git a/lang/compendium_lang/contents_magic.json b/lang/compendium_lang/contents_magic.json new file mode 100644 index 000000000..23e33d686 --- /dev/null +++ b/lang/compendium_lang/contents_magic.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "contents": [ + {"ENCHANTED RINGS": "enchanted rings"}, + {"ENCHANTED AMULETS": "enchanted amulets"}, + {"MAGICSTAFFS": "magicstaffs"}, + {"SCROLLS": "scrolls"}, + {"ENCHANTED FEATHER": "enchanted feather"}, + {"EVOCATION": "evocation"}, + {"CONTRAVENTION": "contravention"}, + {"KINESIS": "kinesis"}, + {"TEMULTURGY": "temulturgy"}, + {"BLESSINGS": "blessings"}, + {"DIVINATIONS": "divinations"}, + {"MIRACULOUS FEATS": "miraculous feats"}, + {"MEDITATIONS": "meditations"}, + {"SARKOMANCY": "sarkomancy"}, + {"PSIANIMUS": "psianimus"}, + {"DAIMONIA": "daimonia"}, + {"IMPETURIA": "impeturia"} + ], + "contents_alphabetical": [ + {"BLESSINGS": "blessings"}, + {"CONTRAVENTION": "contravention"}, + {"DAIMONIA": "daimonia"}, + {"DIVINATIONS": "divinations"}, + {"ENCHANTED AMULETS": "enchanted amulets"}, + {"ENCHANTED FEATHER": "enchanted feather"}, + {"ENCHANTED RINGS": "enchanted rings"}, + {"EVOCATION": "evocation"}, + {"IMPETURIA": "impeturia"}, + {"KINESIS": "kinesis"}, + {"MAGICSTAFFS": "magicstaffs"}, + {"MEDITATIONS": "meditations"}, + {"MIRACULOUS FEATS": "miraculous feats"}, + {"PSIANIMUS": "psianimus"}, + {"SARKOMANCY": "sarkomancy"}, + {"SCROLLS": "scrolls"}, + {"TEMULTURGY": "temulturgy"} + ] +} \ No newline at end of file diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json new file mode 100644 index 000000000..e163155d9 --- /dev/null +++ b/lang/compendium_lang/contents_monsters.json @@ -0,0 +1,111 @@ +{ + "version": 1, + "contents": [ + {"HUMANS": "human"}, + {"RAT": "rat"}, + {"ALGERNON": "algernon"}, + {"SKELETON": "skeleton"}, + {"FUNNY BONES": "funny bones"}, + {"SPIDER": "spider"}, + {"SHELOB": "shelob"}, + {"TROLL": "troll"}, + {"THUMPUS": "thumpus"}, + {"SUCCUBUS": "succubus"}, + {"LILITH": "lilith"}, + {"SUCCUBUS CONSORTS": "bram succubi"}, + {"SLIMES": "slime"}, + {"GHOULS": "ghoul"}, + {"CORAL GRIMES": "coral grimes"}, + {"ENSLAVED GHOULS": "enslaved ghoul"}, + {"GNOME": "gnome"}, + {"GOBLIN": "goblin"}, + {"THE POTATO KING": "potato king"}, + {"SCORPION": "scorpion"}, + {"SKRABBLAG": "skrabblag"}, + {"INSECTOID": "insectoid"}, + {"SCARAB": "scarab"}, + {"XYGGI": "xyggi"}, + {"AUTOMATON": "automaton"}, + {"DEMON": "demon"}, + {"DEU DE'BREAU": "deudebreau"}, + {"INCUBUS": "incubus"}, + {"IMP": "imp"}, + {"VAMPIRE": "vampire"}, + {"BRAM KINDLY": "bram kindly"}, + {"KOBOLD": "kobold"}, + {"CRYSTAL GOLEM": "crystalgolem"}, + {"COCKATRICE": "cockatrice"}, + {"GOATMAN": "goatman"}, + {"GHARBAD": "gharbad"}, + {"SHADOW": "shadow"}, + {"ARTEMISIA": "artemisia"}, + {"BARATHEON": "baratheon"}, + {"SHOPKEEPER": "shopkeeper"}, + {"MYSTERIOUS MERCHANT": "mysterious shop"}, + {"MINOTAUR": "minotaur"}, + {"BARON HERX": "lich"}, + {"BAPHOMET": "devil"}, + {"ERUDYCE": "lichice"}, + {"ORPHEUS": "lichfire"}, + {"MIMIC": "mimic"}, + {"SENTRYBOT": "sentrybot"}, + {"SPELLBOT": "spellbot"}, + {"GYROBOT": "gyrobot"}, + {"DUMMYBOT": "dummybot"}, + {"GHOST": "ghost"} + ], + "contents_alphabetical": [ + {"ALGERNON": "algernon"}, + {"ARTEMISIA": "artemisia"}, + {"AUTOMATON": "automaton"}, + {"BAPHOMET": "devil"}, + {"BARATHEON": "baratheon"}, + {"BARON HERX": "lich"}, + {"BRAM KINDLY": "bram kindly"}, + {"COCKATRICE": "cockatrice"}, + {"CORAL GRIMES": "coral grimes"}, + {"CRYSTAL GOLEM": "crystalgolem"}, + {"DEMON": "demon"}, + {"DEU DE'BREAU": "deudebreau"}, + {"DUMMYBOT": "dummybot"}, + {"ENSLAVED GHOULS": "enslaved ghoul"}, + {"ERUDYCE": "lichice"}, + {"FUNNY BONES": "funny bones"}, + {"GHARBAD": "gharbad"}, + {"GHOST": "ghost"}, + {"GHOULS": "ghoul"}, + {"GNOME": "gnome"}, + {"GOATMAN": "goatman"}, + {"GOBLIN": "goblin"}, + {"GYROBOT": "gyrobot"}, + {"HUMANS": "human"}, + {"IMP": "imp"}, + {"INCUBUS": "incubus"}, + {"INSECTOID": "insectoid"}, + {"KOBOLD": "kobold"}, + {"LILITH": "lilith"}, + {"MIMIC": "mimic"}, + {"MINOTAUR": "minotaur"}, + {"MYSTERIOUS MERCHANT": "mysterious shop"}, + {"ORPHEUS": "lichfire"}, + {"RAT": "rat"}, + {"SCARAB": "scarab"}, + {"SCORPION": "scorpion"}, + {"SENTRYBOT": "sentrybot"}, + {"SHADOW": "shadow"}, + {"SHELOB": "shelob"}, + {"SHOPKEEPER": "shopkeeper"}, + {"SKELETON": "skeleton"}, + {"SKRABBLAG": "skrabblag"}, + {"SLIMES": "slime"}, + {"SPELLBOT": "spellbot"}, + {"SPIDER": "spider"}, + {"SUCCUBUS CONSORTS": "bram succubi"}, + {"SUCCUBUS": "succubus"}, + {"THE POTATO KING": "potato king"}, + {"THUMPUS": "thumpus"}, + {"TROLL": "troll"}, + {"VAMPIRE": "vampire"}, + {"XYGGI": "xyggi"} + ] +} \ No newline at end of file diff --git a/lang/compendium_lang/contents_world.json b/lang/compendium_lang/contents_world.json new file mode 100644 index 000000000..40e287e3d --- /dev/null +++ b/lang/compendium_lang/contents_world.json @@ -0,0 +1,115 @@ +{ + "version": 1, + "contents": [ + {" AREAS": "-"}, + {"MINEHEAD": "minehead"}, + {"HALL OF TRIALS": "hall of trials"}, + {"MINES": "mines"}, + {"SHOP": "shop"}, + {"GNOMISH MINES": "gnomish mines"}, + {"MINETOWN": "minetown"}, + {" DUNGEONS": "-"}, + {" SECRET AREAS": "-"}, + {"TRANSITION FLOOR": "transition floor"}, + {"SWAMPS": "swamps"}, + {"TEMPLE": "temple"}, + {"HAUNTED CASTLE": "haunted castle"}, + {"LABYRINTH": "labyrinth"}, + {"SOKOBAN": "sokoban"}, + {"MINOTAUR MAZE": "minotaur maze"}, + {"RUINS": "ruins"}, + {"MYSTIC LIBRARY": "mystic library"}, + {"LICH'S BASTION": "herx lair"}, + {"UNDERWORLD": "underworld"}, + {"THE MOLTEN THRONE": "molten throne"}, + {"HAMLET": "hamlet"}, + {"HELL": "hell"}, + {"CRYSTAL CAVES": "crystal caves"}, + {"COCKATRICE LAIR": "cockatrice lair"}, + {"ARCANE CITADEL": "arcane citadel"}, + {"BRAM'S CASTLE": "brams castle"}, + {"CITADEL SANCTUM": "citadel sanctum"}, + + {" ENVIRONMENT": "-"}, + + {"BOULDER TRAP": "boulder trap"}, + {"ARROW TRAP": "arrow trap"}, + {"SPIKE TRAP": "spike trap"}, + {"MAGIC TRAP": "magic trap"}, + {"SUMMONING TRAP": "summoning trap"}, + {"BRIMSTONE BOULDER": "brimstone boulder"}, + {"CEILING TRAP": "ceiling trap"}, + {"MURKY WATER": "murky water"}, + {"LAVA": "lava"}, + {"PITS": "pits"}, + {"BREAKABLE BARRIERS": "breakable barriers"}, + + {" OBJECTS": "-"}, + {"GATE": "portcullis"}, + {"LEVER": "lever"}, + {"DOOR": "door"}, + {"SINK": "sink"}, + {"FOUNTAIN": "fountain"}, + {"CHEST": "chest"}, + {"GRAVESTONE": "gravestone"}, + {"OBELISK": "obelisk"}, + + {" GUILDS": "-"}, + {"MERCHANTS' GUILD": "merchants guild"}, + {"MAGICIANS' GUILD": "magicians guild"}, + {"HUNTERS' GUILD": "hunters guild"}, + {"THE CHURCH": "the church"}, + {"MASONS' GUILD": "masons guild"} + ], + "contents_alphabetical": [ + {"ARCANE CITADEL": "arcane citadel"}, + {"ARROW TRAP": "arrow trap"}, + {"BOULDER TRAP": "boulder trap"}, + {"BRAM'S CASTLE": "brams castle"}, + {"BREAKABLE BARRIERS": "breakable barriers"}, + {"BRIMSTONE BOULDER": "brimstone boulder"}, + {"CEILING TRAP": "ceiling trap"}, + {"CHEST": "chest"}, + {"CITADEL SANCTUM": "citadel sanctum"}, + {"COCKATRICE LAIR": "cockatrice lair"}, + {"CRYSTAL CAVES": "crystal caves"}, + {"DOOR": "door"}, + {"FOUNTAIN": "fountain"}, + {"GATE": "portcullis"}, + {"GNOMISH MINES": "gnomish mines"}, + {"GRAVESTONE": "gravestone"}, + {"HALL OF TRIALS": "hall of trials"}, + {"HAMLET": "hamlet"}, + {"HAUNTED CASTLE": "haunted castle"}, + {"HELL": "hell"}, + {"HUNTERS' GUILD": "hunters guild"}, + {"LABYRINTH": "labyrinth"}, + {"LAVA": "lava"}, + {"LEVER": "lever"}, + {"LICH'S BASTION": "herx lair"}, + {"MAGIC TRAP": "magic trap"}, + {"MAGICIANS' GUILD": "magicians guild"}, + {"MASONS' GUILD": "masons guild"}, + {"MERCHANTS' GUILD": "merchants guild"}, + {"MINEHEAD": "minehead"}, + {"MINES": "mines"}, + {"MINETOWN": "minetown"}, + {"MINOTAUR MAZE": "minotaur maze"}, + {"MURKY WATER": "murky water"}, + {"MYSTIC LIBRARY": "mystic library"}, + {"OBELISK": "obelisk"}, + {"PITS": "pits"}, + {"RUINS": "ruins"}, + {"SHOP": "shop"}, + {"SINK": "sink"}, + {"SOKOBAN": "sokoban"}, + {"SPIKE TRAP": "spike trap"}, + {"SUMMONING TRAP": "summoning trap"}, + {"SWAMPS": "swamps"}, + {"TEMPLE": "temple"}, + {"THE CHURCH": "the church"}, + {"THE MOLTEN THRONE": "molten throne"}, + {"TRANSITION FLOOR": "transition floor"}, + {"UNDERWORLD": "underworld"} + ] +} \ No newline at end of file diff --git a/lang/en.txt b/lang/en.txt index d5d805006..c98eebdc2 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6397,12 +6397,19 @@ to participate.# 6171 Unlock the Achievement: "%s" to play this class combination.# -6172 Magic Required: %d (%s)# +6172 +Magic Required: %d (%s)# 6173 Dungeon Floor:# 6174 Denizens# 6175 Items# 6176 Magic# 6177 World# 6178 Codex# +6179 PREV ENTRY: # +6180 NEXT ENTRY: # +6181 %d of %d# +6182 CONTENTS# +6183 New Entry!# +6184 Achievements# 6200 END# From 6d5375cbe18479f1870baf46393e836d42e2bc5d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 9 Aug 2024 21:56:03 +1000 Subject: [PATCH 043/244] * gitignore update debug folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e60e55eda..a1b8c921e 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ steam_appid.txt xcode/Barony/Barony.xcodeproj/project.xcworkspace/* xcode/Barony/Barony.xcodeproj/xcuserdata/* *.ps1 -!LICENSE.nativefiledialog.txt \ No newline at end of file +!LICENSE.nativefiledialog.txt +/VS.2015/Barony/x64/Debug From a522028e7e848a62b193321dfe844ff7fd09a937 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 9 Aug 2024 22:26:50 +1000 Subject: [PATCH 044/244] * much compendium events for magic/tinkering and misc * actMagicMissile denote if from spellbook * add equip noise/break to sexchange ammy --- src/actarrow.cpp | 25 ++++ src/actbeartrap.cpp | 45 ++++++++ src/actboulder.cpp | 1 + src/actplayer.cpp | 57 ++++++++++ src/actthrown.cpp | 5 + src/entity.cpp | 187 +++++++++++++++++++----------- src/entity.hpp | 1 + src/entity_editor.cpp | 1 + src/game.cpp | 8 ++ src/init_game.cpp | 82 +++++++++++--- src/interface/interface.cpp | 67 ++++++++++- src/item_tool.cpp | 44 +++++++- src/item_usage_funcs.cpp | 31 ++++- src/items.cpp | 3 +- src/magic/actmagic.cpp | 220 ++++++++++++++++++++++++++++++++---- src/magic/castSpell.cpp | 67 +++++++++-- src/magic/magic.cpp | 15 ++- src/magic/magic.hpp | 2 +- src/magic/spell.cpp | 4 + src/monster_sentrybot.cpp | 24 +++- src/net.cpp | 2 +- src/scores.cpp | 2 +- src/stat.cpp | 10 ++ src/steam.cpp | 10 +- src/ui/GameUI.cpp | 12 +- 25 files changed, 783 insertions(+), 142 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index c70a1b953..eb80c3c9f 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -692,6 +692,14 @@ void actArrow(Entity* my) { if ( oldHP > hitstats->HP ) { + if ( itemTypeIsQuiver((ItemType)my->arrowQuiverType) ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_RANGED_DMG_TOTAL, (ItemType)my->arrowQuiverType, oldHP - hitstats->HP); + } + if ( isRangedWeapon((ItemType)my->arrowShotByWeapon) ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_RANGED_DMG_TOTAL, (ItemType)my->arrowShotByWeapon, oldHP - hitstats->HP); + } Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RANGED_DMG_TOTAL, "missiles", oldHP - hitstats->HP); Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RANGED_HITS, "missiles", 1); Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_CLASS_RANGED_HITS_RUN, "missiles", 1); @@ -712,6 +720,23 @@ void actArrow(Entity* my) Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_0, (ItemType)my->arrowShotByWeapon, 1); } } + else if ( parent->behavior == &actMonster ) + { + if ( oldHP > hitstats->HP ) + { + if ( Stat* parentStats = parent->getStats() ) + { + if ( parentStats->type == SENTRYBOT ) + { + if ( auto leader = parent->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdate(leader->skill[2], + Compendium_t::CPDM_SENTRY_DEPLOY_DMG, TOOL_SENTRYBOT, oldHP - hitstats->HP); + } + } + } + } + } if ( hit.entity->behavior == &actMonster && parent->behavior == &actPlayer ) { diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index 56f00807c..9dcafae7c 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -24,6 +24,7 @@ #include "monster.hpp" #include "prng.hpp" #include "paths.hpp" +#include "mod_tools.hpp" /*------------------------------------------------------------------------------- @@ -160,6 +161,16 @@ void actBeartrap(Entity* my) // entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH); // } //} + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_BEARTRAP_TRAPPED, TOOL_BEARTRAP, 1); + if ( stat->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_BEARTRAP_DMG, TOOL_BEARTRAP, oldHP - stat->HP); + } + } + // set obituary entity->updateEntityOnHit(parent, true); entity->setObituary(Language::get(1504)); @@ -378,6 +389,20 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa } } + if ( parent && parent->behavior == &actPlayer ) + { + if ( triggered && (triggered == parent || parent->checkFriend(triggered)) ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_BOMB_DETONATED_ALLY, (ItemType)BOMB_ITEMTYPE, 1); + } + else + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_BOMB_DETONATED, (ItemType)BOMB_ITEMTYPE, 1); + } + } + if ( doSpell == SPELL_TELEPORTATION ) { if ( triggered->isBossMonster() ) @@ -529,6 +554,15 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa triggered->updateEntityOnHit(parent, true); stat->killer = KilledBy::TRAP_BOMB; + if ( stat->HP < oldHP ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_BOMB_DMG, (ItemType)BOMB_ITEMTYPE, oldHP - stat->HP); + } + } + if ( stat->HP <= 0 && oldHP > 0 ) { if ( parent ) @@ -1180,6 +1214,7 @@ void actDecoyBox(Entity* my) entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange); bool message = false; bool detected = false; + int lured = 0; for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) { list_t* currentList = *it; @@ -1247,6 +1282,7 @@ void actDecoyBox(Entity* my) } spawnFloatingSpriteMisc(134, entity->x + (-4 + local_rng.rand() % 9) + cos(entity->yaw) * 2, entity->y + (-4 + local_rng.rand() % 9) + sin(entity->yaw) * 2, entity->z + local_rng.rand() % 4); + ++lured; } } break; @@ -1265,6 +1301,7 @@ void actDecoyBox(Entity* my) entity->monsterState = MONSTER_STATE_HUNT; // hunt state serverUpdateEntitySkill(entity, 0); detected = true; + ++lured; if ( entityDist(entity, my) < TOUCHRANGE && !myStats->EFFECTS[EFF_DISORIENTED] @@ -1326,6 +1363,14 @@ void actDecoyBox(Entity* my) } } } + if ( detected && lured > 0 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_NOISEMAKER_LURED, TOOL_DECOY, lured); + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_NOISEMAKER_MOST_LURED, TOOL_DECOY, lured); + } + } if ( !message && detected ) { if ( parent && parent->behavior == &actPlayer ) diff --git a/src/actboulder.cpp b/src/actboulder.cpp index f991bd265..d2f6e45e4 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -532,6 +532,7 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit } if ( stats->type == GYROBOT ) { + Compendium_t::Events_t::eventUpdate(entity->monsterAllyIndex, Compendium_t::CPDM_GYROBOT_BOULDERS, TOOL_GYROBOT, 1); real_t tangent = atan2(leader->y - entity->y, leader->x - entity->x); Entity* ohitentity = hit.entity; lineTraceTarget(entity, entity->x, entity->y, tangent, 1024, 0, false, leader); diff --git a/src/actplayer.cpp b/src/actplayer.cpp index c7eb06af6..031f9484a 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -4544,6 +4544,8 @@ void actPlayer(Entity* my) players[PLAYER_NUM]->compendiumProgress.playerAliveTimeStopped = 0; players[PLAYER_NUM]->compendiumProgress.playerAliveTimeTotal = 0; players[PLAYER_NUM]->compendiumProgress.playerGameTimeTotal = 0; + players[PLAYER_NUM]->compendiumProgress.playerEquipSlotTime.clear(); + players[PLAYER_NUM]->compendiumProgress.allyTimeSpent.clear(); Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = my->x; @@ -8106,6 +8108,38 @@ void actPlayer(Entity* my) { players[PLAYER_NUM]->compendiumProgress.playerSneakTime++; } + Item* itemslots[10] = { + stats[PLAYER_NUM]->helmet, + stats[PLAYER_NUM]->breastplate, + stats[PLAYER_NUM]->gloves, + stats[PLAYER_NUM]->shoes, + stats[PLAYER_NUM]->shield, + stats[PLAYER_NUM]->weapon, + stats[PLAYER_NUM]->cloak, + stats[PLAYER_NUM]->amulet, + stats[PLAYER_NUM]->ring, + stats[PLAYER_NUM]->mask + }; + for ( auto slot = 0; slot < 10; ++slot ) + { + if ( itemslots[slot] ) + { + players[PLAYER_NUM]->compendiumProgress.playerEquipSlotTime[(int)(itemslots[slot]->type)]++; + } + } + for ( node_t* node = stats[PLAYER_NUM]->FOLLOWERS.first; node != nullptr; node = node->next, ++i ) + { + Entity* follower = nullptr; + if ( (Uint32*)node->element ) + { + follower = uidToEntity(*((Uint32*)node->element)); + if ( follower ) + { + int type = follower->getMonsterTypeFromSprite(); + players[PLAYER_NUM]->compendiumProgress.allyTimeSpent[type]++; + } + } + } if ( ticks % (TICKS_PER_SECOND * 5) == 0 ) { if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_TUTORIAL ) @@ -8124,6 +8158,29 @@ void actPlayer(Entity* my) Compendium_t::Events_t::eventUpdateCodex(PLAYER_NUM, Compendium_t::CPDM_CLASS_SNEAK_TIME, "sneaking", players[PLAYER_NUM]->compendiumProgress.playerSneakTime); players[PLAYER_NUM]->compendiumProgress.playerSneakTime = 0; + + for ( auto& pair : players[PLAYER_NUM]->compendiumProgress.playerEquipSlotTime ) + { + if ( pair.second > 0 ) + { + if ( items[pair.first].item_slot == EQUIPPABLE_IN_SLOT_RING ) + { + Compendium_t::Events_t::eventUpdate(PLAYER_NUM, Compendium_t::CPDM_TIME_WORN, (ItemType)(pair.first), pair.second); + } + } + pair.second = 0; + } + for ( auto& pair : players[PLAYER_NUM]->compendiumProgress.allyTimeSpent ) + { + if ( pair.second > 0 ) + { + if ( pair.first == GYROBOT ) + { + Compendium_t::Events_t::eventUpdate(PLAYER_NUM, Compendium_t::CPDM_GYROBOT_TIME_SPENT, TOOL_GYROBOT, pair.second); + } + } + pair.second = 0; + } } } diff --git a/src/actthrown.cpp b/src/actthrown.cpp index ca96c365c..422de1b56 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -824,6 +824,11 @@ void actThrown(Entity* my) { Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_DMG_MAX, item->type, damage); + if ( oldHP > hitstats->HP ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], + Compendium_t::CPDM_THROWN_DMG_TOTAL, item->type, oldHP - hitstats->HP); + } if ( cat == THROWN ) { if ( oldHP > hitstats->HP ) diff --git a/src/entity.cpp b/src/entity.cpp index 06f17deda..37b4b336e 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -334,6 +334,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicCastByTinkerTrap(skill[22]), actmagicTinkerTrapFriendlyFire(skill[23]), actmagicReflectionCount(skill[25]), + actmagicFromSpellbook(skill[26]), goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), @@ -558,7 +559,7 @@ Entity::~Entity() // alert clients of the entity's deletion if ( multiplayer == SERVER && !loading ) { - if ( mynode->list == map.entities && uid != 0 && flags[NOUPDATE] == false ) + if ( mynode && mynode->list == map.entities && uid != 0 && flags[NOUPDATE] == false ) { for ( i = 1; i < MAXPLAYERS; ++i ) { @@ -4725,7 +4726,8 @@ void Entity::handleEffects(Stat* myStats) else { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(646), myStats->cloak->getName()); // "Your %s burns to ash!" - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CLOAK_BURNED, myStats->cloak->type, 1); + //Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_CLOAK_BURNED, myStats->cloak->type, 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, myStats->cloak->type, 1); } if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -4971,6 +4973,7 @@ void Entity::handleEffects(Stat* myStats) } myStats->amulet->count = 1; myStats->amulet->status = BROKEN; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, myStats->amulet->type, 1); playSoundEntity(this, 76, 64); if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -5079,6 +5082,7 @@ void Entity::handleEffects(Stat* myStats) messagePlayer(player, MESSAGE_STATUS | MESSAGE_OBITUARY, Language::get(657)); } myStats->amulet->status = BROKEN; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, myStats->amulet->type, 1); playSoundEntity(this, 76, 64); if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) { @@ -7789,6 +7793,12 @@ void Entity::attack(int pose, int charge, Entity* target) playSoundEntity(hit.entity, 67, 128); list_RemoveNode(hit.entity->mynode); messagePlayer(player, MESSAGE_COMBAT, Language::get(663)); + + if ( myStats->weapon && myStats->weapon->type == TOOL_PICKAXE && !shapeshifted && pose != PLAYER_POSE_GOLEM_SMASH ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_PICKAXE_BOULDERS_DUG, myStats->weapon->type, 1); + } + if ( myStats->weapon && local_rng.rand() % 2 && pose != PLAYER_POSE_GOLEM_SMASH ) { myStats->weapon->status = static_cast(myStats->weapon->status - 1); @@ -8684,41 +8694,37 @@ void Entity::attack(int pose, int charge, Entity* target) } } - Sint32 oldHP = hitstats->HP; - hit.entity->modHP(-damage); // do the damage - - if ( playerhit >= 0 ) - { - Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", oldHP - hitstats->HP); - } - if ( player >= 0 ) + Item** weaponToBreak = nullptr; + ItemType weaponType = static_cast(WOODEN_SHIELD); + bool hasMeleeGloves = false; + if ( myStats->gloves && !shapeshifted ) { - if ( hitstats->HP < oldHP ) + switch ( myStats->gloves->type ) { - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_DMG_TOTAL, "melee", oldHP - hitstats->HP); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_HITS, "melee", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_MELEE_HITS_RUN, "melee", 1); - if ( chargeMult > 1 ) - { - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRITS_DMG_TOTAL, "crits", oldHP - hitstats->HP); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRIT_HITS, "crits", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_CRITS_HITS_RUN, "crits", 1); - } - if ( flanking ) - { - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_DMG, "flanking", oldHP - hitstats->HP); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_HITS, "flanking", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_HITS_RUN, "flanking", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_DMG_RUN, "flanking", oldHP - hitstats->HP); - } - else if ( backstab ) - { - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_BACKSTAB_HITS, "backstabs", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_HITS_RUN, "backstabs", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_DMG_RUN, "backstabs", oldHP - hitstats->HP); - } + case BRASS_KNUCKLES: + case IRON_KNUCKLES: + case SPIKED_GAUNTLETS: + hasMeleeGloves = true; + break; + default: + break; } } + if ( myStats->weapon && !shapeshifted ) + { + weaponToBreak = &myStats->weapon; + } + else if ( hasMeleeGloves ) + { + weaponToBreak = &myStats->gloves; + } + if ( weaponToBreak != nullptr && !shapeshifted ) + { + weaponType = (*weaponToBreak)->type; + } + + Sint32 oldHP = hitstats->HP; + hit.entity->modHP(-damage); // do the damage bool skillIncreased = false; // skill increase @@ -8848,42 +8854,21 @@ void Entity::attack(int pose, int charge, Entity* target) bool isWeakWeapon = false; bool artifactWeapon = false; bool degradeWeapon = false; - ItemType weaponType = static_cast(WOODEN_SHIELD); - bool hasMeleeGloves = false; - if ( myStats->gloves && !shapeshifted ) - { - switch ( myStats->gloves->type ) - { - case BRASS_KNUCKLES: - case IRON_KNUCKLES: - case SPIKED_GAUNTLETS: - hasMeleeGloves = true; - break; - default: - break; - } - } - - Item** weaponToBreak = nullptr; - - if ( myStats->weapon ) - { - weaponToBreak = &myStats->weapon; - } - else if ( hasMeleeGloves ) - { - weaponToBreak = &myStats->gloves; - } - if ( weaponToBreak != nullptr && !shapeshifted ) + if ( weaponToBreak != nullptr && weaponType != WOODEN_SHIELD ) { - weaponType = (*weaponToBreak)->type; - if ( behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_ATTACKS, weaponType, 1); - Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_DMG_MAX, weaponType, damage); + if ( pose == PLAYER_POSE_GOLEM_SMASH ) + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_SPELL_DMG, SPELL_ITEM, damage, false, SPELL_STRIKE); + } + else + { + Compendium_t::Events_t::eventUpdate(skill[2], Compendium_t::CPDM_DMG_MAX, weaponType, damage); + } } if ( weaponType == ARTIFACT_AXE || weaponType == ARTIFACT_MACE || weaponType == ARTIFACT_SPEAR || weaponType == ARTIFACT_SWORD ) @@ -10529,6 +10514,43 @@ void Entity::attack(int pose, int charge, Entity* target) } } + if ( playerhit >= 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(playerhit, Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", oldHP - hitstats->HP); + } + if ( player >= 0 ) + { + if ( hitstats->HP < oldHP ) + { + if ( weaponType != WOODEN_SHIELD ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_MELEE_DMG_TOTAL, weaponType, oldHP - hitstats->HP); + } + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_DMG_TOTAL, "melee", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_MELEE_HITS, "melee", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_MELEE_HITS_RUN, "melee", 1); + if ( chargeMult > 1 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRITS_DMG_TOTAL, "crits", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CRIT_HITS, "crits", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_CRITS_HITS_RUN, "crits", 1); + } + if ( flanking ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_DMG, "flanking", oldHP - hitstats->HP); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_FLANK_HITS, "flanking", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_HITS_RUN, "flanking", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_FLANK_DMG_RUN, "flanking", oldHP - hitstats->HP); + } + else if ( backstab ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_BACKSTAB_HITS, "backstabs", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_HITS_RUN, "backstabs", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_BACKSTAB_DMG_RUN, "backstabs", oldHP - hitstats->HP); + } + } + } + if ( !strncmp(hitstats->name, "inner demon", strlen("inner demon")) ) { hit.entity->modHP(damage); // undo melee damage. @@ -12175,6 +12197,31 @@ void Entity::awardXP(Entity* src, bool share, bool root) && (src->monsterAllySummonRank != 0 || src->monsterIsTinkeringCreation()) ) { + if ( root && behavior == &actPlayer ) + { + if ( multiplayer == SINGLE ) + { + if ( splitscreen ) + { + Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + } + else + { + Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_SOLO, src, 1); + } + } + else + { + Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } + } + } + } return; // summoned monster, no XP! } if ( srcStats->type == INCUBUS && !strncmp(srcStats->name, "inner demon", strlen("inner demon")) ) @@ -12390,6 +12437,8 @@ void Entity::awardXP(Entity* src, bool share, bool root) Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_MAX_IN_FLOOR, "xp", gain, false, -1, true); Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_MAX_INSTANCE, "xp", gain); Compendium_t::Events_t::eventUpdateCodex(skill[2], Compendium_t::CPDM_XP_KILLS, "xp", gain); + + Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILL_XP, src, gain); } destStats->EXP += gain; } @@ -12695,6 +12744,18 @@ void Entity::awardXP(Entity* src, bool share, bool root) { steamAchievementClient(leader->skill[2], "BARONY_ACH_TIME_TO_PLAN"); } + + if ( root ) + { + if ( destStats && (destStats->type == SENTRYBOT || destStats->type == SPELLBOT) ) + { + if ( leader->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(leader->skill[2], + Compendium_t::CPDM_SENTRY_DEPLOY_KILLS, destStats->type == SENTRYBOT ? TOOL_SENTRYBOT : TOOL_SPELLBOT, 1); + } + } + } } else { diff --git a/src/entity.hpp b/src/entity.hpp index 9dcf98ca3..9efe0fdf1 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -511,6 +511,7 @@ class Entity Sint32& actmagicCastByTinkerTrap; // skill[22] Sint32& actmagicTinkerTrapFriendlyFire; // skill[23] Sint32& actmagicReflectionCount; // skill[25] + Sint32& actmagicFromSpellbook; // skill[26] //--PUBLIC GOLD SKILLS-- Sint32& goldAmount; //skill[0] diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index fa15a29b3..ae2f45c23 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -304,6 +304,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicCastByTinkerTrap(skill[22]), actmagicTinkerTrapFriendlyFire(skill[23]), actmagicReflectionCount(skill[25]), + actmagicFromSpellbook(skill[26]), goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), diff --git a/src/game.cpp b/src/game.cpp index ae1cf46fe..dd0c4e8fb 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2765,6 +2765,10 @@ void gameLogic(void) else { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + if ( items[item->type].item_slot != ItemEquippableSlot::NO_EQUIP ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BLESSED_MAX, item->type, item->beatitude); + } } } } @@ -3422,6 +3426,10 @@ void gameLogic(void) else { Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_RUNS_COLLECTED, item->type, 1); + if ( items[item->type].item_slot != ItemEquippableSlot::NO_EQUIP ) + { + Compendium_t::Events_t::eventUpdate(clientnum, Compendium_t::CPDM_BLESSED_MAX, item->type, item->beatitude); + } } } } diff --git a/src/init_game.cpp b/src/init_game.cpp index 26142d924..7623f0b13 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -105,6 +105,7 @@ void initGameDatafiles(bool moddedReload) Compendium_t::Events_t::loadItemsSaveData(); CompendiumEntries.readModelLimbsFromFile("monster"); CompendiumEntries.readModelLimbsFromFile("world"); + CompendiumEntries.readModelLimbsFromFile("codex"); } void initGameDatafilesAsync(bool moddedReload) @@ -925,7 +926,11 @@ void loadAchievementData(const char* path) { { if ( steamStatAchStringsAndMaxVals[statNum].first != "BARONY_ACH_NONE" ) { - Compendium_t::achievements[steamStatAchStringsAndMaxVals[statNum].first].achievementProgress = statNum; + auto find = Compendium_t::achievements.find(steamStatAchStringsAndMaxVals[statNum].first); + if ( find != Compendium_t::achievements.end() ) + { + find->second.achievementProgress = statNum; + } } } @@ -970,37 +975,78 @@ void sortAchievementsForDisplay() { auto& achData1 = Compendium_t::achievements[lhs.first]; auto& achData2 = Compendium_t::achievements[rhs.first]; + bool ach1 = achData1.unlocked; bool ach2 = achData2.unlocked; bool lhsAchIsHidden = achData1.hidden; bool rhsAchIsHidden = achData2.hidden; - if ( ach1 && !ach2 ) + if ( !Compendium_t::AchievementData_t::sortAlphabetical ) { - return true; - } - else if ( !ach1 && ach2 ) - { - return false; - } - else if ( !ach1 && !ach2 && (lhsAchIsHidden || rhsAchIsHidden) ) - { - if ( lhsAchIsHidden && rhsAchIsHidden ) - { - return lhs.second < rhs.second; - } - if ( !lhsAchIsHidden ) + if ( ach1 && !ach2 ) { return true; } - if ( !rhsAchIsHidden ) + else if ( !ach1 && ach2 ) { return false; } - return lhs.second < rhs.second; + else if ( !ach1 && !ach2 && (lhsAchIsHidden || rhsAchIsHidden) ) + { + if ( lhsAchIsHidden && rhsAchIsHidden ) + { + return lhs.second < rhs.second; + } + if ( !lhsAchIsHidden ) + { + return true; + } + if ( !rhsAchIsHidden ) + { + return false; + } + return lhs.second < rhs.second; + } + else + { + if ( !ach1 && !ach2 ) + { + return lhs.second < rhs.second; + } + else + { + if ( achData1.unlockTime == achData2.unlockTime ) + { + return lhs.second < rhs.second; + } + else + { + return achData1.unlockTime > achData2.unlockTime; + } + } + } } else { - return lhs.second < rhs.second; + if ( !ach1 && !ach2 && (lhsAchIsHidden || rhsAchIsHidden) ) + { + if ( lhsAchIsHidden && rhsAchIsHidden ) + { + return lhs.second < rhs.second; + } + if ( !lhsAchIsHidden ) + { + return true; + } + if ( !rhsAchIsHidden ) + { + return false; + } + return lhs.second < rhs.second; + } + else + { + return lhs.second < rhs.second; + } } }; diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index fd86bafb8..fbc219a60 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -6743,7 +6743,10 @@ bool GenericGUIMenu::executeOnItemClick(Item* item) { if ( tinkeringIsItemRepairable(item, gui_player) ) { - tinkeringRepairItem(item); + if ( tinkeringRepairItem(item) ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_TINKERKIT_REPAIRS, TOOL_TINKERING_KIT, 1); + } } } else if ( tinkeringFilter == TINKER_FILTER_SALVAGEABLE ) @@ -7860,6 +7863,8 @@ void GenericGUIMenu::alchemyCombinePotions() if ( explodeSelf && players[gui_player] && players[gui_player]->entity ) { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_EXPLOSIONS, TOOL_ALEMBIC, 1); + // hurt. alchemyAddRecipe(gui_player, basePotionType, secondaryPotionType, TOOL_BOMB, true); if ( multiplayer == CLIENT ) @@ -7943,6 +7948,7 @@ void GenericGUIMenu::alchemyCombinePotions() if ( result == POTION_WATER && !duplicateSucceed ) { messagePlayer(gui_player, MESSAGE_MISC, Language::get(3356)); + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_DUPLICATION_FAIL, TOOL_ALEMBIC, 1); } else if ( newPotion ) { @@ -7954,12 +7960,24 @@ void GenericGUIMenu::alchemyCombinePotions() newPotion->status = duplicatedPotion->status; } messagePlayer(gui_player, MESSAGE_MISC, Language::get(3352), newPotion->description()); + if ( duplicateSucceed ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_DUPLICATED, TOOL_ALEMBIC, 1); + } } } else { messagePlayer(gui_player, MESSAGE_MISC, Language::get(3352), newPotion->description()); steamStatisticUpdate(STEAM_STAT_IN_THE_MIX, STEAM_STAT_INT, 1); + if ( samePotion ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_DECANTED, TOOL_ALEMBIC, 1); + } + else + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_BREWED, TOOL_ALEMBIC, 1); + } } if ( newPotion ) @@ -8028,6 +8046,7 @@ void GenericGUIMenu::alchemyCombinePotions() Item* emptyBottle = newItem(POTION_EMPTY, SERVICABLE, 0, 1, 0, true, nullptr); itemPickup(gui_player, emptyBottle); messagePlayer(gui_player, MESSAGE_MISC, Language::get(3351), items[POTION_EMPTY].getIdentifiedName()); + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_BOTTLE_FROM_BREWING, POTION_EMPTY, 1); free(emptyBottle); } if ( raiseSkill && local_rng.rand() % 2 == 0 ) @@ -8407,6 +8426,8 @@ bool GenericGUIMenu::tinkeringCraftItem(Item* item) { Item* pickedUp = itemPickup(gui_player, crafted); messagePlayer(gui_player, MESSAGE_MISC, Language::get(3668), crafted->description()); + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_TINKERKIT_CRAFTS, TOOL_TINKERING_KIT, 1); + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_GADGET_CRAFTED, crafted->type, 1); free(crafted); return true; } @@ -8520,10 +8541,26 @@ bool GenericGUIMenu::tinkeringSalvageItem(Item* item, bool outsideInventory, int { Uint32 color = makeColorRGB(0, 255, 0); messagePlayerColor(player, MESSAGE_INVENTORY, color, Language::get(3665), metal + tinkeringBulkSalvageMetalScrap, items[pickedUp->type].getIdentifiedName()); + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TINKERKIT_METAL_SCRAPPED, TOOL_TINKERING_KIT, metal + tinkeringBulkSalvageMetalScrap); + if ( item && item->type == TOOL_DETONATOR_CHARGE ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DETONATOR_SCRAPPED_METAL, TOOL_DETONATOR_CHARGE, metal + tinkeringBulkSalvageMetalScrap); + } + } } else { messagePlayer(player, MESSAGE_MISC, Language::get(3665), metal + tinkeringBulkSalvageMetalScrap, items[pickedUp->type].getIdentifiedName()); + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TINKERKIT_METAL_SCRAPPED, TOOL_TINKERING_KIT, metal + tinkeringBulkSalvageMetalScrap); + if ( item && item->type == TOOL_DETONATOR_CHARGE ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DETONATOR_SCRAPPED_METAL, TOOL_DETONATOR_CHARGE, metal + tinkeringBulkSalvageMetalScrap); + } + } } } else @@ -8546,10 +8583,26 @@ bool GenericGUIMenu::tinkeringSalvageItem(Item* item, bool outsideInventory, int { Uint32 color = makeColorRGB(0, 255, 0); messagePlayerColor(player, MESSAGE_INVENTORY, color, Language::get(3665), magic + tinkeringBulkSalvageMagicScrap, items[pickedUp->type].getIdentifiedName()); + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TINKERKIT_MAGIC_SCRAPPED, TOOL_TINKERING_KIT, magic + tinkeringBulkSalvageMagicScrap); + if ( item && item->type == TOOL_DETONATOR_CHARGE ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DETONATOR_SCRAPPED_MAGIC, TOOL_DETONATOR_CHARGE, magic + tinkeringBulkSalvageMagicScrap); + } + } } else { messagePlayer(player, MESSAGE_MISC, Language::get(3665), magic + tinkeringBulkSalvageMagicScrap, items[pickedUp->type].getIdentifiedName()); + if ( players[player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TINKERKIT_MAGIC_SCRAPPED, TOOL_TINKERING_KIT, magic + tinkeringBulkSalvageMagicScrap); + if ( item && item->type == TOOL_DETONATOR_CHARGE ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DETONATOR_SCRAPPED_MAGIC, TOOL_DETONATOR_CHARGE, magic + tinkeringBulkSalvageMagicScrap); + } + } } } else @@ -8662,6 +8715,14 @@ bool GenericGUIMenu::tinkeringSalvageItem(Item* item, bool outsideInventory, int } } + if ( players[player]->isLocalPlayer() && didCraft ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_TINKERKIT_SALVAGED, TOOL_TINKERING_KIT, 1); + if ( item && item->type == TOOL_DETONATOR_CHARGE ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_DETONATOR_SCRAPPED, TOOL_DETONATOR_CHARGE, 1); + } + } if ( !outsideInventory && didCraft ) { consumeItem(item, player); @@ -14333,7 +14394,7 @@ void GenericGUIMenu::AlchemyGUI_t::updateAlchemyMenu() } alchFrame->setSize(alchFramePos); - if ( keystatus[SDLK_j] && enableDebugKeys ) + /*if ( keystatus[SDLK_j] && enableDebugKeys ) { if ( keystatus[SDLK_LSHIFT] ) { @@ -14377,7 +14438,7 @@ void GenericGUIMenu::AlchemyGUI_t::updateAlchemyMenu() } } keystatus[SDLK_j] = 0; - } + }*/ /*if ( keystatus[SDLK_H] && enableDebugKeys ) { keystatus[SDLK_H] = 0; diff --git a/src/item_tool.cpp b/src/item_tool.cpp index c6ce3c4f4..6ad113cae 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -33,11 +33,13 @@ void Item::applySkeletonKey(int player, Entity& entity) messagePlayer(player, MESSAGE_INTERACTION, Language::get(1097)); entity.unlockChest(); Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_CHESTS_UNLOCK, TOOL_SKELETONKEY, 1); } else { messagePlayer(player, MESSAGE_INTERACTION, Language::get(1098)); entity.lockChest(); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_CHESTS_LOCK, TOOL_SKELETONKEY, 1); } } else if ( entity.behavior == &actDoor ) @@ -55,12 +57,14 @@ void Item::applySkeletonKey(int player, Entity& entity) messagePlayer(player, MESSAGE_INTERACTION, Language::get(1099)); entity.doorLocked = 0; Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_DOOR_UNLOCK, TOOL_SKELETONKEY, 1); } } else { messagePlayer(player, MESSAGE_INTERACTION, Language::get(1100)); entity.doorLocked = 1; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_DOOR_LOCK, TOOL_SKELETONKEY, 1); } } else if ( entity.behavior == &actMonster && entity.getMonsterTypeFromSprite() == MIMIC ) @@ -102,7 +106,7 @@ void Item::applySkeletonKey(int player, Entity& entity) playSoundEntity(&entity, 91, 64); messagePlayer(player, MESSAGE_INTERACTION, Language::get(1098)); entity.setEffect(EFF_MIMIC_LOCKED, true, TICKS_PER_SECOND * 5, false); - + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_MIMICS_LOCKED, TOOL_SKELETONKEY, 1); entity.monsterHitTime = HITRATE - 2; if ( players[player] ) { @@ -147,6 +151,10 @@ void Item::applyLockpick(int player, Entity& entity) { entity.skill[22] = (entity.skill[22] == BOMB_TRIGGER_ENEMIES) ? BOMB_TRIGGER_ALL : BOMB_TRIGGER_ENEMIES; } + if ( !gyrobotUsing ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_TINKERTRAPS, TOOL_LOCKPICK, 1); + } if ( entity.skill[22] == BOMB_TRIGGER_ENEMIES ) { if ( gyrobotUsing ) @@ -259,6 +267,7 @@ void Item::applyLockpick(int player, Entity& entity) } entity.unlockChest(); Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_CHESTS_UNLOCK, TOOL_LOCKPICK, 1); } else { @@ -353,6 +362,7 @@ void Item::applyLockpick(int player, Entity& entity) playSoundEntity(&entity, 91, 64); messagePlayer(player, MESSAGE_INTERACTION, Language::get(1099)); Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_DOOR_UNLOCK, TOOL_LOCKPICK, 1); entity.doorLocked = 0; if ( !entity.doorPreventLockpickExploit ) { @@ -858,6 +868,14 @@ void Item::applyEmptyPotion(int player, Entity& entity) } free(item); steamStatisticUpdateClient(player, STEAM_STAT_FREE_REFILLS, STEAM_STAT_INT, 1); + if ( entity.behavior == &actSink ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SINKS_TAPPED, POTION_EMPTY, 1); + } + else if ( entity.behavior == &actFountain ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_FOUNTAINS_TAPPED, POTION_EMPTY, 1); + } } if ( entity.behavior == &actSink ) @@ -1079,6 +1097,11 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, entity->skill[16] = placement; entity->skill[20] = dir; entity->skill[21] = type; + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, type, 1); + } } } else if ( placement == BOMB_WALL ) @@ -1202,6 +1225,11 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, entity->skill[16] = placement; entity->skill[20] = dir; entity->skill[21] = type; + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, type, 1); + } } } else if ( placement == BOMB_CHEST || placement == BOMB_DOOR || placement == BOMB_COLLIDER ) @@ -1393,6 +1421,11 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, } entity->skill[20] = dir; entity->skill[21] = type; + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, type, 1); + } } } } @@ -1426,6 +1459,11 @@ void Item::applyTinkeringCreation(Entity* parent, Entity* thrown) entity->skill[13] = 1; entity->skill[14] = this->appearance; entity->skill[15] = 1; + + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, this->type, 1); + } } else if ( type == TOOL_DUMMYBOT || type == TOOL_GYROBOT || type == TOOL_SENTRYBOT || type == TOOL_SPELLBOT ) { @@ -1455,6 +1493,10 @@ void Item::applyTinkeringCreation(Entity* parent, Entity* thrown) Stat* summonedStats = summon->getStats(); if ( parent && parent->behavior == &actPlayer && summonedStats ) { + if ( parent && parent->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, this->type, 1); + } if ( summonedStats->type == GYROBOT ) { summon->yaw = thrown->yaw; diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 3dd902070..46ffc9599 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -4340,6 +4340,11 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) entity->skill[17] = players[player]->entity->skill[2]; messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(906)); consumeItem(item, player); + + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BEARTRAP_DEPLOYED, TOOL_BEARTRAP, 1); + } return; } @@ -4870,10 +4875,18 @@ void item_FoodTin(Item*& item, int player) if (svFlags & SV_FLAG_HUNGER) { stats[player]->HUNGER += 600 * foodMult; - stats[player]->EFFECTS[EFF_HP_REGEN] = hpBuff; - stats[player]->EFFECTS[EFF_MP_REGEN] = mpBuff; - stats[player]->EFFECTS_TIMERS[EFF_HP_REGEN] = buffDuration; - stats[player]->EFFECTS_TIMERS[EFF_MP_REGEN] = buffDuration; + if ( hpBuff ) + { + stats[player]->EFFECTS[EFF_HP_REGEN] = hpBuff; + stats[player]->EFFECTS_TIMERS[EFF_HP_REGEN] = buffDuration; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TIN_REGEN_HP, FOOD_TIN, 1); + } + if ( mpBuff ) + { + stats[player]->EFFECTS[EFF_MP_REGEN] = mpBuff; + stats[player]->EFFECTS_TIMERS[EFF_MP_REGEN] = buffDuration; + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TIN_REGEN_MP, FOOD_TIN, 1); + } } else { @@ -4899,6 +4912,7 @@ void item_FoodTin(Item*& item, int player) if ( players[player]->entity->setEffect(EFF_GREASY, true, TICKS_PER_SECOND * (60 + local_rng.rand() % 60), true) ) { messagePlayer(player, MESSAGE_STATUS | MESSAGE_HINT, Language::get(966)); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_TIN_GREASY, FOOD_TIN, 1); } else { @@ -4953,6 +4967,12 @@ void item_AmuletSexChange(Item* item, int player) } } + if ( multiplayer != CLIENT ) + { + playSoundEntity(players[player]->entity, 33 + local_rng.rand() % 2, 64); + playSoundEntity(players[player]->entity, 76, 64); + } + if ( players[player] && players[player]->isLocalPlayer() ) { messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1094)); @@ -4963,12 +4983,12 @@ void item_AmuletSexChange(Item* item, int player) serverUpdateSexChange(player); - if ( !players[player]->isLocalPlayer() ) { return; } + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BROKEN, item->type, 1); consumeItem(item, player); // find out what creature we are... @@ -5338,6 +5358,7 @@ void item_Spellbook(Item*& item, int player) if ( learned ) { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_LEARNT, item->type, 1); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CAST_DEGRADES, item->type, 1); if ( item->type >= SPELLBOOK_RAT_FORM && item->type <= SPELLBOOK_IMP_FORM ) { ItemType originalSpellbook = item->type; diff --git a/src/items.cpp b/src/items.cpp index 35dd8d341..ac43a3032 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -2830,9 +2830,9 @@ void useItem(Item* item, const int player, Entity* usedBy, bool unequipForDroppi if ( tryEmptyBottle && local_rng.rand() % 100 < std::min(80, (60 + skillLVL * 10)) ) // 60 - 80% chance { Item* emptyBottle = newItem(POTION_EMPTY, SERVICABLE, 0, 1, 0, true, nullptr); - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BOTTLES_FROM_CONSUME, POTION_EMPTY, 1); itemPickup(player, emptyBottle); messagePlayer(player, MESSAGE_INTERACTION, Language::get(3351), items[POTION_EMPTY].getIdentifiedName()); + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BOTTLES_FROM_CONSUME, POTION_EMPTY, 1); free(emptyBottle); } } @@ -4983,6 +4983,7 @@ void Item::applyLockpickToWall(const int player, const int x, const int y) const playSoundEntity(entity, 176, 128); entity->skill[4] = player + 1; // disabled flag and spit out items. serverUpdateEntitySkill(entity, 4); // update clients. + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_LOCKPICK_ARROWTRAPS, stats[player]->weapon->type, 1); } // degrade lockpick. diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index f5d658bde..38a86fdbe 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -583,29 +583,181 @@ void spawnBloodVialOnMonsterDeath(Entity* entity, Stat* hitstats, Entity* killer } static ConsoleVariable cvar_magic_fx_use_vismap("/magic_fx_use_vismap", true); -void magicOnPlayerHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID) +void magicOnEntityHit(Entity* parent, Entity* particle, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID) { - if ( !hitentity || !hitstats ) { return; } + if ( !hitentity ) { return; } - if ( hitentity->behavior != &actPlayer ) + if ( hitentity->behavior == &actPlayer ) { - return; - } - - Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_SPELLS_HIT, "res", 1); + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_SPELLS_HIT, "res", 1); - Sint32 damageTaken = oldHP - hitstats->HP; - if ( damageTaken > 0 ) + if ( hitstats ) + { + Sint32 damageTaken = oldHP - hitstats->HP; + if ( damageTaken > 0 ) + { + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_TAKEN, "res", damageTaken); + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", damageTaken); + if ( preResistanceDamage > damage ) + { + Sint32 noResistDmgTaken = oldHP - std::max(0, oldHP - preResistanceDamage); + if ( noResistDmgTaken > damageTaken ) + { + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED, "res", noResistDmgTaken - damageTaken); + Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED_RUN, "res", noResistDmgTaken - damageTaken); + } + } + } + } + } + if ( parent && parent->behavior == &actMonster && spellID > SPELL_NONE ) + { + if ( hitstats ) + { + Sint32 damageTaken = oldHP - hitstats->HP; + if ( damageTaken > 0 ) + { + if ( Stat* stats = parent->getStats() ) + { + if ( stats->type == SPELLBOT ) + { + if ( Entity* leader = parent->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdate(leader->skill[2], + Compendium_t::CPDM_SENTRY_DEPLOY_DMG, TOOL_SPELLBOT, damageTaken); + } + } + } + } + } + } + else if ( parent && parent->behavior == &actPlayer && spellID > SPELL_NONE ) { - Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_TAKEN, "res", damageTaken); - Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_HP_MOST_DMG_LOST_ONE_HIT, "hp", damageTaken); - if ( preResistanceDamage > damage ) + Sint32 damageTaken = 0; + if ( hitstats ) + { + damageTaken = oldHP - hitstats->HP; + } + if ( !particle || (particle && particle->behavior != &actMagicMissile)) { return; } + if ( particle->actmagicCastByMagicstaff != 0 ) + { + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() ) + { + if ( damageTaken > 0 ) + { + if ( find->second.magicstaffId >= 0 && find->second.magicstaffId < NUMITEMS && items[find->second.magicstaffId].category == MAGICSTAFF ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_DMG, (ItemType)find->second.magicstaffId, damageTaken); + } + } + else if ( damage == 0 && oldHP == 0 ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_TRACK_HITS) != find->second.spellTags.end() ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_TARGETS, + (ItemType)find->second.magicstaffId, 1); + } + } + } + } + else if ( particle->actmagicFromSpellbook != 0 ) + { + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() ) + { + if ( find->second.spellbookId >= 0 && find->second.spellbookId < NUMITEMS && items[find->second.spellbookId].category == SPELLBOOK ) + { + if ( damageTaken > 0 ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_HEALING) != find->second.spellTags.end() ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_HEAL, (ItemType)find->second.spellbookId, damageTaken); + } + else + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_DMG, (ItemType)find->second.spellbookId, damageTaken); + } + } + else if ( damage == 0 && oldHP == 0 ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_TRACK_HITS) != find->second.spellTags.end() ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_TARGETS, + (ItemType)find->second.spellbookId, 1); + } + } + } + } + } + else if ( particle->actmagicCastByTinkerTrap == 0 ) { - Sint32 noResistDmgTaken = oldHP - std::max(0, oldHP - preResistanceDamage); - if ( noResistDmgTaken > damageTaken ) + if ( particle->actmagicIsOrbiting == 2 && particle->actmagicOrbitCastFromSpell == 0 ) { - Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED, "res", noResistDmgTaken - damageTaken); - Compendium_t::Events_t::eventUpdateCodex(hitentity->skill[2], Compendium_t::CPDM_RES_DMG_RESISTED_RUN, "res", noResistDmgTaken - damageTaken); + // cast by firestorm potion etc + if ( damageTaken > 0 ) + { + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() ) + { + if ( find->second.id > SPELL_NONE && find->second.id < NUM_SPELLS ) + { + ItemType type = WOODEN_SHIELD; + if ( find->second.id == SPELL_FIREBALL ) + { + type = POTION_FIRESTORM; + } + else if ( find->second.id == SPELL_COLD ) + { + type = POTION_ICESTORM; + } + else if ( find->second.id == SPELL_LIGHTNING ) + { + type = POTION_THUNDERSTORM; + } + if ( type != WOODEN_SHIELD ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_THROWN_DMG_TOTAL, type, damageTaken); + } + } + } + } + } + else + { + // normal spellcasts + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() ) + { + if ( find->second.id > SPELL_NONE && find->second.id < NUM_SPELLS ) + { + if ( damageTaken > 0 ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_DMG, SPELL_ITEM, damageTaken, false, find->second.id); + } + else if ( damage == 0 && oldHP == 0 ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_TRACK_HITS) != find->second.spellTags.end() ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_SPELL_TARGETS, SPELL_ITEM, 1, false, find->second.id); + } + } + } + } + } + } + else if ( particle->actmagicCastByTinkerTrap == 1 ) + { + if ( damageTaken > 0 ) + { + if ( spellID == SPELL_FIREBALL ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_BOMB_DMG, TOOL_BOMB, damageTaken); + } + else if ( spellID == SPELL_COLD ) + { + Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_BOMB_DMG, TOOL_FREEZE_BOMB, damageTaken); + } } } } @@ -1486,7 +1638,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage /= (1 + (int)resistance); Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { @@ -1665,7 +1817,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); for (i = 0; i < damage; i += 2) //Spawn a gib for every two points of damage. { Entity* gib = spawnGib(hit.entity); @@ -1951,7 +2103,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage *= fireMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); //for (i = 0; i < damage; i += 2) { //Spawn a gib for every two points of damage. Entity* gib = spawnGib(hit.entity); @@ -2202,6 +2354,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( duration > 0 && hit.entity->setEffect(EFF_CONFUSED, true, duration, false) ) { + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); playSoundEntity(hit.entity, 174, 64); if ( hit.entity->behavior == &actMonster ) @@ -2323,7 +2476,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( damage > 0 ) { hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); Entity* gib = spawnGib(hit.entity); serverSpawnGibForClient(gib); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); @@ -2404,6 +2557,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast(element->base_mana)) * element->overload_multiplier)); hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); // If the Entity hit is a Player, update their status to be Slowed @@ -2480,6 +2634,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { if ( hit.entity->setEffect(EFF_ASLEEP, true, effectDuration, false) ) { + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, 0, spell ? spell->ID : SPELL_NONE); playSoundEntity(hit.entity, 174, 64); hitstats->OLDHP = hitstats->HP; @@ -2583,7 +2738,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. damage *= damageMultiplier; damage /= (1 + (int)resistance); hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); // write the obituary @@ -2857,6 +3012,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent->behavior == &actPlayer ) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(399)); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -2881,6 +3037,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent->behavior == &actPlayer ) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(400)); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -2914,6 +3071,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(6083)); } + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } if ( !hitstats->EFFECTS[EFF_MIMIC_LOCKED] ) { @@ -2969,6 +3127,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_DOOR_UNLOCKED, "door", 1); } } + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); hit.entity->doorLocked = 0; // Unlocks the Door hit.entity->doorPreventLockpickExploit = 1; @@ -3022,6 +3181,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(403)); // "The spell opens the gate!" Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_GATE_OPENED_SPELL, "portcullis", 1); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -3051,6 +3211,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(404)); // "The spell unlocks the chest!" Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_CHESTS_UNLOCKED, "chest", 1); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -3076,6 +3237,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent->behavior == &actPlayer ) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(2358)); + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -3100,6 +3262,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } } + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } else { @@ -3123,6 +3286,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. hit.entity->monsterHitTime = std::max(hit.entity->monsterHitTime, HITRATE / 2); } } + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -3199,6 +3363,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int oldDuration = !hitstats->EFFECTS[EFF_PARALYZED] ? 0 : hitstats->EFFECTS_TIMERS[EFF_PARALYZED]; if ( hit.entity->setEffect(EFF_PARALYZED, true, effectDuration, false) ) { + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); if ( hit.entity->behavior == &actPlayer ) { serverUpdateEffects(hit.entity->skill[2]); @@ -3267,7 +3432,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); // write the obituary @@ -3506,6 +3671,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( spellEffectDominate(*my, *element, *caster, parent) ) { //Success + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); } } } @@ -3562,7 +3728,10 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); if ( caster ) { - spellEffectTeleportPull(my, *element, parent, hit.entity, resistance); + if ( spellEffectTeleportPull(my, *element, parent, hit.entity, resistance) ) + { + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); + } } } else if ( !strcmp(element->element_internal_name, spellElement_shadowTag.element_internal_name) ) @@ -3578,7 +3747,10 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); if ( caster ) { - spellEffectDemonIllusion(*my, *element, parent, hit.entity, resistance); + if ( spellEffectDemonIllusion(*my, *element, parent, hit.entity, resistance) ) + { + magicOnEntityHit(parent, my, hit.entity, hitstats, 0, 0, 0, spell ? spell->ID : SPELL_NONE); + } } } diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 8d7560765..90489145b 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -1029,6 +1029,20 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3438)); } } + else + { + if ( !using_magicstaff && !trap ) + { + if ( usingSpellbook ) + { + Compendium_t::Events_t::eventUpdate(caster->skill[2], Compendium_t::CPDM_SPELL_TARGETS, SPELLBOOK_FEAR, foundTarget); + } + else + { + Compendium_t::Events_t::eventUpdate(caster->skill[2], Compendium_t::CPDM_SPELL_TARGETS, SPELL_ITEM, foundTarget, false, SPELL_FEAR); + } + } + } } else if ( caster->behavior == &actMonster ) { @@ -1551,6 +1565,27 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool if ( totalHeal > 0 ) { serverUpdatePlayerGameplayStats(i, STATISTICS_HEAL_BOT, totalHeal); + if ( spell && spell->ID > SPELL_NONE ) + { + if ( !using_magicstaff && !trap ) + { + if ( usingSpellbook ) + { + auto find = ItemTooltips.spellItems.find(spell->ID); + if ( find != ItemTooltips.spellItems.end() ) + { + if ( find->second.spellbookId >= 0 && find->second.spellbookId < NUMITEMS && items[find->second.spellbookId].category == SPELLBOOK ) + { + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_SPELL_HEAL, (ItemType)find->second.spellbookId, totalHeal); + } + } + } + else + { + Compendium_t::Events_t::eventUpdate(i, Compendium_t::CPDM_SPELL_HEAL, SPELL_ITEM, totalHeal, false, spell->ID); + } + } + } } break; } @@ -2037,9 +2072,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { missileEntity->actmagicCastByMagicstaff = 1; } - else if ( usingSpellbook && spellBookBonusPercent > 0 ) + else if ( usingSpellbook ) { - missileEntity->actmagicSpellbookBonus = spellBookBonusPercent; + missileEntity->actmagicFromSpellbook = 1; + if ( spellBookBonusPercent > 0 ) + { + missileEntity->actmagicSpellbookBonus = spellBookBonusPercent; + } } Stat* casterStats = caster->getStats(); if ( !trap && !using_magicstaff && casterStats && casterStats->EFFECTS[EFF_MAGICAMPLIFY] ) @@ -2165,9 +2204,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { missileEntity->actmagicCastByMagicstaff = 1; } - else if ( usingSpellbook && spellBookBonusPercent > 0 ) + else if ( usingSpellbook ) { - missileEntity->actmagicSpellbookBonus = spellBookBonusPercent; + missileEntity->actmagicFromSpellbook = 1; + if ( spellBookBonusPercent > 0 ) + { + missileEntity->actmagicSpellbookBonus = spellBookBonusPercent; + } } node = list_AddNodeFirst(&missileEntity->children); node->element = copySpell(spell); @@ -2202,9 +2245,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { entity1->actmagicCastByMagicstaff = 1; } - else if ( usingSpellbook && spellBookBonusPercent > 0 ) + else if ( usingSpellbook ) { - entity1->actmagicSpellbookBonus = spellBookBonusPercent; + entity1->actmagicFromSpellbook = 1; + if ( spellBookBonusPercent > 0 ) + { + entity1->actmagicSpellbookBonus = spellBookBonusPercent; + } } node = list_AddNodeFirst(&entity1->children); node->element = copySpell(spell); @@ -2235,9 +2282,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { entity2->actmagicCastByMagicstaff = 1; } - else if ( usingSpellbook && spellBookBonusPercent > 0 ) + else if ( usingSpellbook ) { - entity2->actmagicSpellbookBonus = spellBookBonusPercent; + entity2->actmagicFromSpellbook = 1; + if ( spellBookBonusPercent > 0 ) + { + entity2->actmagicSpellbookBonus = spellBookBonusPercent; + } } node = list_AddNodeFirst(&entity2->children); node->element = copySpell(spell); diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 01119211e..5fd7c6953 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -306,11 +306,11 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re if ( !hasgoggles ) { hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_ACID_SPRAY); + magicOnEntityHit(parent, &my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_ACID_SPRAY); } else { - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, 0, oldHP, SPELL_ACID_SPRAY); + magicOnEntityHit(parent, &my, hit.entity, hitstats, preResistanceDamage, 0, oldHP, SPELL_ACID_SPRAY); } // write the obituary @@ -499,7 +499,7 @@ void spellEffectPoison(Entity& my, spellElement_t& element, Entity* parent, int Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_POISON); + magicOnEntityHit(parent, &my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_POISON); // write the obituary if ( parent ) @@ -709,6 +709,7 @@ void spellEffectSprayWeb(Entity& my, spellElement_t& element, Entity* parent, in duration /= (1 + resistance); if ( hit.entity->setEffect(EFF_WEBBED, true, 400, true) ) // 8 seconds. { + magicOnEntityHit(parent, &my, hit.entity, hitstats, 0, 0, 0, SPELL_SPRAY_WEB); if ( abs(duration - previousDuration) > 10 ) { playSoundEntity(hit.entity, 396 + local_rng.rand() % 3, 64); // play sound only if not recently webbed. (triple shot makes many noise) @@ -850,6 +851,9 @@ void spellEffectStealWeapon(Entity& my, spellElement_t& element, Entity* parent, spellEntity->skill[14] = hitstats->weapon->appearance; spellEntity->skill[15] = hitstats->weapon->identified; spellEntity->itemOriginalOwner = hit.entity->getUID(); + + magicOnEntityHit(parent, &my, hit.entity, hitstats, 0, 0, 0, SPELL_STEAL_WEAPON); + // hit messages if ( player >= 0 ) { @@ -1003,7 +1007,7 @@ void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, i Sint32 oldHP = hitstats->HP; hit.entity->modHP(-damage); - magicOnPlayerHit(parent, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_DRAIN_SOUL); + magicOnEntityHit(parent, &my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, SPELL_DRAIN_SOUL); if ( damage > hitstats->MP ) { @@ -1575,6 +1579,7 @@ void spellEffectCharmMonster(Entity& my, spellElement_t& element, Entity* parent } hit.entity->setEffect(EFF_CONFUSED, false, 0, false); + magicOnEntityHit(parent, &my, hit.entity, hitstats, 0, 0, 0, SPELL_CHARM_MONSTER); // change the color of the hit entity. hit.entity->flags[USERFLAG2] = true; @@ -1633,6 +1638,7 @@ void spellEffectCharmMonster(Entity& my, spellElement_t& element, Entity* parent } if ( hit.entity->setEffect(EFF_PACIFY, true, duration, true) ) { + magicOnEntityHit(parent, &my, hit.entity, hitstats, 0, 0, 0, SPELL_CHARM_MONSTER); playSoundEntity(hit.entity, 168, 128); // Healing.ogg if ( player >= 0 ) { @@ -2636,6 +2642,7 @@ void spellEffectShadowTag(Entity& my, spellElement_t& element, Entity* parent, i { hit.entity->setEffect(EFF_SHADOW_TAGGED, true, 10 * TICKS_PER_SECOND, true); } + magicOnEntityHit(parent, &my, hit.entity, hitstats, 0, 0, 0, SPELL_SHADOW_TAG); parent->creatureShadowTaggedThisUid = hit.entity->getUID(); serverUpdateEntitySkill(parent, 54); if ( !sameAsPrevious ) diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index afd99c342..127450920 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -508,7 +508,7 @@ bool isSpellcasterBeginnerFromSpellbook(int player, Entity* caster, Stat* stat, int getSpellbookBonusPercent(Entity* caster, Stat* stat, Item* spellbookItem); real_t getBonusFromCasterOfSpellElement(Entity* caster, Stat* casterStats, spellElement_t* spellElement, int spellID); real_t getSpellBonusFromCasterINT(Entity* caster, Stat* casterStats); -void magicOnPlayerHit(Entity* parent, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID); +void magicOnEntityHit(Entity* parent, Entity* particle, Entity* hitentity, Stat* hitstats, Sint32 preResistanceDamage, Sint32 damage, Sint32 oldHP, int spellID); #endif bool isSpellcasterBeginner(int player, Entity* caster); void actMagicTrap(Entity* my); diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index dfff78767..fd54aa80d 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -1330,6 +1330,10 @@ spell_t* getSpellFromItem(const int player, Item* item, bool usePlayerInventory) { return nullptr; } + if ( item->type != SPELL_ITEM ) + { + return nullptr; + } Uint32 appearance = item->appearance; if ( item->type == SPELL_ITEM && item->appearance >= 1000 ) diff --git a/src/monster_sentrybot.cpp b/src/monster_sentrybot.cpp index f01f0acfa..4ce92802c 100644 --- a/src/monster_sentrybot.cpp +++ b/src/monster_sentrybot.cpp @@ -23,6 +23,7 @@ #include "magic/magic.hpp" #include "interface/interface.hpp" #include "prng.hpp" +#include "mod_tools.hpp" std::unordered_map gyroBotDetectedUids; @@ -1255,6 +1256,11 @@ void gyroBotAnimate(Entity* my, Stat* myStats, double dist) { my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr); my->monsterSpecialTimer = TICKS_PER_SECOND * 8; + + if ( auto leader = my->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdateMonster(leader->skill[2], Compendium_t::CPDM_GYROBOT_FLIPS, my, 1); + } } } @@ -1878,12 +1884,22 @@ void dummyBotAnimate(Entity* my, Stat* myStats, double dist) head = entity; if ( multiplayer != CLIENT && entity->skill[0] == 2 ) { - if ( entity->skill[3] > 0 && myStats->HP < entity->skill[3] ) + if ( myStats ) { - // on hit, bounce a bit. - my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr); + if ( entity->skill[3] > 0 && myStats->HP < entity->skill[3] ) + { + // on hit, bounce a bit. + my->attack(MONSTER_POSE_RANGED_WINDUP1, 0, nullptr); + if ( Entity* leader = my->monsterAllyGetPlayerLeader() ) + { + Compendium_t::Events_t::eventUpdate(leader->skill[2], + Compendium_t::CPDM_DUMMY_HITS_TAKEN, TOOL_DUMMYBOT, 1); + Compendium_t::Events_t::eventUpdate(leader->skill[2], + Compendium_t::CPDM_DUMMY_DMG_TAKEN, TOOL_DUMMYBOT, std::max(0, entity->skill[3] - myStats->HP)); + } + } + entity->skill[3] = myStats->HP; } - entity->skill[3] = myStats->HP; } if ( my->monsterSpecialState == DUMMYBOT_RETURN_FORM ) diff --git a/src/net.cpp b/src/net.cpp index 144429e68..54d277003 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -5125,7 +5125,7 @@ static std::unordered_map clientPacketHandlers = { if ( itemType >= Compendium_t::Events_t::kEventCodexOffset && itemType <= Compendium_t::Events_t::kEventCodexOffsetMax ) { Compendium_t::Events_t::eventUpdateCodex(0, (Compendium_t::EventTags)id, nullptr, value, false, - itemType - Compendium_t::Events_t::kEventCodexOffset); + itemType); continue; } if ( itemType < 0 || (itemType >= NUMITEMS && itemType < Compendium_t::Events_t::kEventSpellOffset) ) diff --git a/src/scores.cpp b/src/scores.cpp index 09fb2b06f..e6a09b585 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -5357,7 +5357,7 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev break; } #ifdef DEBUG_ACHIEVEMENTS - messagePlayer(player, MESSAGE_DEBUG, "[DEBUG]: Processed achievement %d, event: %d", achievement, achEvent); + //messagePlayer(player, MESSAGE_DEBUG, "[DEBUG]: Processed achievement %d, event: %d", achievement, achEvent); #endif } diff --git a/src/stat.cpp b/src/stat.cpp index 57b47a43a..99f416f17 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -19,6 +19,7 @@ #include "net.hpp" #include "player.hpp" #include "prng.hpp" +#include "mod_tools.hpp" Stat* stats[MAXPLAYERS]; @@ -1581,6 +1582,15 @@ bool Stat::emptyLootingBag(const int player, Uint32 key) } stats[i]->player_lootbags[key].looted = true; } + int owner = (key & 0xF); + if ( owner == player ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEATHBOX_OPEN_OWN, TOOL_PLAYER_LOOT_BAG, 1); + } + else if ( owner != player ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_DEATHBOX_OPEN_OTHERS, TOOL_PLAYER_LOOT_BAG, 1); + } } stats[i]->player_lootbags.erase(key); return true; diff --git a/src/steam.cpp b/src/steam.cpp index 99473e15a..99f9127fa 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -861,9 +861,12 @@ void steamAchievement(const char* achName) #endif #endif - Compendium_t::achievements[achName].unlocked = true; - Compendium_t::achievements[achName].unlockTime = getTime(); - auto& unlockStatus = Compendium_t::AchievementData_t::unlocks[Compendium_t::achievements[achName].category]; + auto find = Compendium_t::achievements.find(achName); + if ( find != Compendium_t::achievements.end() ) + { + find->second.unlocked = true; + find->second.unlockTime = getTime(); + auto& unlockStatus = Compendium_t::AchievementData_t::unlocks[find->second.category]; if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) { unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; @@ -880,6 +883,7 @@ void steamAchievement(const char* achName) Compendium_t::AchievementData_t::achievementsNeedResort = true; } } +} void steamUnsetAchievement(const char* achName) { diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 64a24de51..b26480781 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -20795,16 +20795,18 @@ void createInventoryTooltipFrame(const int player, if ( parentFrame ) { tooltipContainerFrame = parentFrame->addFrame(name); + tooltipContainerFrame->setSize( + SDL_Rect{ 0, 0, parentFrame->getSize().w, parentFrame->getSize().h }); } else { tooltipContainerFrame = gameUIFrame[player]->addFrame(name); + tooltipContainerFrame->setSize( + SDL_Rect{ players[player]->camera_virtualx1(), + players[player]->camera_virtualy1(), + players[player]->camera_virtualWidth(), + players[player]->camera_virtualHeight() }); } - tooltipContainerFrame->setSize( - SDL_Rect{ players[player]->camera_virtualx1(), - players[player]->camera_virtualy1(), - players[player]->camera_virtualWidth(), - players[player]->camera_virtualHeight() }); tooltipContainerFrame->setHollow(true); tooltipContainerFrame->setDisabled(false); tooltipContainerFrame->setInheritParentFrameOpacity(false); From 46275d36144beba78fe35c901eb42f5b77e736c7 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 9 Aug 2024 22:28:37 +1000 Subject: [PATCH 045/244] * lang update * compendium section update --- lang/compendium_lang/contents_codex.json | 34 ++++----- lang/compendium_lang/contents_items.json | 49 ++++++------- lang/compendium_lang/contents_magic.json | 7 +- lang/compendium_lang/contents_monsters.json | 65 +++++++++-------- lang/compendium_lang/contents_world.json | 77 ++++++++++----------- lang/en.txt | 1 + 6 files changed, 121 insertions(+), 112 deletions(-) diff --git a/lang/compendium_lang/contents_codex.json b/lang/compendium_lang/contents_codex.json index 25e201e75..9fd03269d 100644 --- a/lang/compendium_lang/contents_codex.json +++ b/lang/compendium_lang/contents_codex.json @@ -7,8 +7,24 @@ {"RACE & ALIGNMENT": "races"}, {"EXPERIENCE POINTS (XP)": "xp"}, {"LEVELING UP": "leveling up"}, - {" STATS": "-"}, {"STATS & METASTATS": "stats metastats"}, + {"SKILLS": "skills"}, + {" ACTIONS": "-"}, + {"MELEE ATTACKS": "melee"}, + {"CRITICAL STRIKES": "crits"}, + {"FLANKING STRIKES": "flanking"}, + {"BACKSTABBING": "backstabs"}, + {"SNEAKING": "sneaking"}, + {"LEGENDARY STRIKES": "legendary strikes"}, + {"BLOCKING": "blocking"}, + {"STRAFE & BACKPEDAL": "strafing"}, + {"MEMORIZED CASTING": "memorized"}, + {"SPELLBOOK CASTING": "spellbook casting"}, + {"MISSILE ATTACKS": "missiles"}, + {"THROWN ATTACKS": "thrown"}, + {"ITEM DEGRADATION": "equipment degradation"}, + {"WANTED STATUS": "wanted"}, + {" STATS & METASTATS": "-"}, {"STRENGTH (STR)": "str"}, {"DEXTERITY (DEX)": "dex"}, {"CONSTITUTION (CON)": "con"}, @@ -22,22 +38,7 @@ {"MAGIC POWER (PWR)": "pwr"}, {"REGENERATION (RGN)": "rgn"}, {"WEIGHT (WGT)": "wgt"}, - {" MECHANICS": "-"}, - {"MELEE ATTACKS": "melee"}, - {"CRITICAL STRIKES": "crits"}, - {"FLANKING STRIKES": "flanking"}, - {"BACKSTABBING": "backstabs"}, - {"SNEAKING": "sneaking"}, - {"LEGENDARY STRIKES": "legendary strikes"}, - {"BLOCKING": "blocking"}, - {"STRAFE & BACKPEDAL": "strafing"}, - {"MEMORIZED CASTING": "memorized"}, - {"SPELLBOOK CASTING": "spellbook casting"}, - {"MISSILE ATTACKS": "missiles"}, - {"THROWN ATTACKS": "thrown"}, - {"ITEM DURABILITY": "equipment degradation"}, {" SKILLS": "-"}, - {"SKILLS": "skills"}, {"SWORD SKILL": "sword skill"}, {"AXE SKILL": "axe skill"}, {"POLEARM SKILL": "polearm skill"}, @@ -52,7 +53,6 @@ {"SWIMMING SKILL": "swimming skill"}, {"LEADERSHIP SKILL": "leadership skill"}, {"TRADING SKILL": "trading skill"}, - {"WANTED STATUS": "wanted"}, {"ALCHEMY SKILL": "alchemy skill"}, {"TINKERING SKILL": "tinkering skill"} ], diff --git a/lang/compendium_lang/contents_items.json b/lang/compendium_lang/contents_items.json index 4c0712302..a57a57296 100644 --- a/lang/compendium_lang/contents_items.json +++ b/lang/compendium_lang/contents_items.json @@ -1,17 +1,8 @@ { "version": 1, "contents": [ - {" EQUIPMENT": "-"}, - {"TORCHES & LANTERNS": "torch and lantern"}, - {"SHIELDS": "shields"}, - {"CRYSTAL SHARD": "crystal shard"}, - {"MIRRORS": "mirrors"}, - {"HATS & HOODS": "hats & hoods"}, - {"CROWNS & HEADDRESSES": "crowns & headdresses"}, - {"FACE ACCESSORIES": "face accessories"}, - {"MASKS & VISORS": "masks & visors"}, - {"CLOAKS & CLOTHING": "cloaks & clothing"}, - {"BACKPACKS": "backpacks"}, + {" ARMOR": "-"}, + {"SHIELDS": "shields"}, {"LEATHER ARMOR": "leather armor"}, {"IRON ARMOR": "iron armor"}, {"STEEL ARMOR": "steel armor"}, @@ -21,7 +12,14 @@ {"DJINNI'S BRACE": "djinnis brace"}, {"DRAGON'S MAIL": "dragons mail"}, {"WRAITH'S GOWN": "wraiths gown"}, - {" MELEE WEAPONRY": "-"}, + {" CLOTHING": "-"}, + {"HATS & HOODS": "hats & hoods"}, + {"CROWNS & HEADDRESSES": "crowns & headdresses"}, + {"FACE ACCESSORIES": "face accessories"}, + {"MASKS & VISORS": "masks & visors"}, + {"CLOAKS & SHIRTS": "cloaks & clothing"}, + {"BACKPACKS": "backpacks"}, + {" MELEE WEAPONS": "-"}, {"SWORDS": "swords"}, {"AXES": "axes"}, {"MACES": "maces"}, @@ -32,10 +30,7 @@ {"GUNGNIR": "gungnir"}, {"FIST WEAPONS": "fist weapons"}, {"WHIP": "whip"}, - {" RANGED WEAPONRY": "-"}, - {"TOMAHAWKS & DAGGERS": "tomahawks & daggers"}, - {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, - {"BOOMERANG": "boomerang"}, + {" MISSILE WEAPONS": "-"}, {"SLINGSHOT": "slingshot"}, {"BOWS": "bows"}, {"CROSSBOW": "crossbow"}, @@ -45,27 +40,36 @@ {"SILVER & PIERCING AMMO": "silver and piercing ammo"}, {"FIRE & HUNTING AMMO": "fire and hunting ammo"}, {"CRYSTAL AMMO": "crystal ammo"}, - {" GEMSTONES": "-"}, + {" THROWING WEAPONS": "-"}, + {"TOMAHAWKS & DAGGERS": "tomahawks & daggers"}, + {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, + {"BOOMERANG": "boomerang"}, + {" CRYSTALS": "-"}, {"ROCKS & GEMS": "rocks & gems"}, + {"CRYSTAL SHARD": "crystal shard"}, + {"MYSTIC ORB": "mystic orb"}, {" FOOD": "-"}, - {"WATER": "water"}, {"MORSELS": "cheese and apples"}, {"CREAM PIE": "cream pie"}, {"TOMALLEY": "tomalley"}, {"MEALS": "meat, fish, and bread"}, {"TIN & TIN OPENER": "tin and tin opener"}, {"BLOOD VIALS": "blood vials"}, - {" POTIONS": "-"}, - {"BOOZE": "booze"}, + {" POTIONS & DRINKS": "-"}, + {"WATER": "water"}, {"EMPTY BOTTLE": "empty bottle"}, {"DEFENSIVE POTIONS": "defensive potions"}, {"OFFENSIVE POTIONS": "offensive potions"}, {"ALEMBIC": "alembic"}, {" TOOLS": "-"}, + {"TORCHES & LANTERNS": "torch and lantern"}, + {"MIRRORS": "mirrors"}, + {"TOWEL": "towel"}, {"MINING PICK": "mining pick"}, {"TINKERING KIT": "tinkering kit"}, {"LOCKPICK": "lockpick"}, {"SKELETON KEY": "skeleton key"}, + {" GADGETS": "-"}, {"BEARTRAP": "beartrap"}, {"NOISEMAKER": "noisemaker"}, {"DUMMYBOT": "dummybot"}, @@ -74,9 +78,7 @@ {"TINKERED TRAPS": "tinkered traps"}, {"TELEPORTATION TRAP": "teleportation trap"}, {" OTHER": "-"}, - {"TOWEL": "towel"}, {"MAIL & LORE BOOKS": "mail & lore books"}, - {"MYSTIC ORB": "mystic orb"}, {"DEATH BOX": "death box"} ], "contents_alphabetical": [ @@ -87,11 +89,10 @@ {"BEARTRAP": "beartrap"}, {"BLOOD VIALS": "blood vials"}, {"BOOMERANG": "boomerang"}, - {"BOOZE": "booze"}, {"BOWS": "bows"}, {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, {"CHEESE & APPLES": "cheese and apples"}, - {"CLOAKS & CLOTHING": "cloaks & clothing"}, + {"CLOAKS & SHIRTS": "cloaks & clothing"}, {"CREAM PIE": "cream pie"}, {"CROSSBOW": "crossbow"}, {"CROWNS & HEADDRESSES": "crowns & headdresses"}, diff --git a/lang/compendium_lang/contents_magic.json b/lang/compendium_lang/contents_magic.json index 23e33d686..16219383f 100644 --- a/lang/compendium_lang/contents_magic.json +++ b/lang/compendium_lang/contents_magic.json @@ -1,19 +1,24 @@ { "version": 1, "contents": [ + {" MAGIC ITEMS": "-"}, + {"MAGICSTAFFS": "magicstaffs"}, {"ENCHANTED RINGS": "enchanted rings"}, {"ENCHANTED AMULETS": "enchanted amulets"}, - {"MAGICSTAFFS": "magicstaffs"}, + {" ARCANA": "-"}, {"SCROLLS": "scrolls"}, {"ENCHANTED FEATHER": "enchanted feather"}, + {" SORCERY": "-"}, {"EVOCATION": "evocation"}, {"CONTRAVENTION": "contravention"}, {"KINESIS": "kinesis"}, {"TEMULTURGY": "temulturgy"}, + {" THAUMATURGY": "-"}, {"BLESSINGS": "blessings"}, {"DIVINATIONS": "divinations"}, {"MIRACULOUS FEATS": "miraculous feats"}, {"MEDITATIONS": "meditations"}, + {" MYSTICISM": "-"}, {"SARKOMANCY": "sarkomancy"}, {"PSIANIMUS": "psianimus"}, {"DAIMONIA": "daimonia"}, diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json index e163155d9..397e7366e 100644 --- a/lang/compendium_lang/contents_monsters.json +++ b/lang/compendium_lang/contents_monsters.json @@ -1,58 +1,64 @@ { "version": 1, "contents": [ + {" HUMANOIDS": "-"}, {"HUMANS": "human"}, + {"GNOME": "gnome"}, + {"GOBLIN": "goblin"}, + {"THE POTATO KING": "potato king"}, + {"SHOPKEEPER": "shopkeeper"}, + {"MYSTERIOUS MERCHANT": "mysterious shop"}, + {" BEASTS": "-"}, {"RAT": "rat"}, - {"ALGERNON": "algernon"}, - {"SKELETON": "skeleton"}, - {"FUNNY BONES": "funny bones"}, {"SPIDER": "spider"}, {"SHELOB": "shelob"}, {"TROLL": "troll"}, {"THUMPUS": "thumpus"}, - {"SUCCUBUS": "succubus"}, - {"LILITH": "lilith"}, - {"SUCCUBUS CONSORTS": "bram succubi"}, - {"SLIMES": "slime"}, - {"GHOULS": "ghoul"}, - {"CORAL GRIMES": "coral grimes"}, - {"ENSLAVED GHOULS": "enslaved ghoul"}, - {"GNOME": "gnome"}, - {"GOBLIN": "goblin"}, - {"THE POTATO KING": "potato king"}, + {"ALGERNON": "algernon"}, {"SCORPION": "scorpion"}, {"SKRABBLAG": "skrabblag"}, - {"INSECTOID": "insectoid"}, {"SCARAB": "scarab"}, {"XYGGI": "xyggi"}, - {"AUTOMATON": "automaton"}, - {"DEMON": "demon"}, - {"DEU DE'BREAU": "deudebreau"}, - {"INCUBUS": "incubus"}, - {"IMP": "imp"}, - {"VAMPIRE": "vampire"}, - {"BRAM KINDLY": "bram kindly"}, - {"KOBOLD": "kobold"}, - {"CRYSTAL GOLEM": "crystalgolem"}, {"COCKATRICE": "cockatrice"}, + {"MINOTAUR": "minotaur"}, + {" BEASTFOLK": "-"}, + {"INSECTOID": "insectoid"}, + {"KOBOLD": "kobold"}, {"GOATMAN": "goatman"}, {"GHARBAD": "gharbad"}, + {" UNDEAD": "-"}, + {"GHOST": "ghost"}, + {"SKELETON": "skeleton"}, + {"FUNNY BONES": "funny bones"}, + {"GHOULS": "ghoul"}, + {"CORAL GRIMES": "coral grimes"}, + {"ENSLAVED GHOULS": "enslaved ghoul"}, + {"VAMPIRE": "vampire"}, + {"BRAM KINDLY": "bram kindly"}, {"SHADOW": "shadow"}, {"ARTEMISIA": "artemisia"}, {"BARATHEON": "baratheon"}, - {"SHOPKEEPER": "shopkeeper"}, - {"MYSTERIOUS MERCHANT": "mysterious shop"}, - {"MINOTAUR": "minotaur"}, {"BARON HERX": "lich"}, {"BAPHOMET": "devil"}, {"ERUDYCE": "lichice"}, {"ORPHEUS": "lichfire"}, - {"MIMIC": "mimic"}, + {" DEMONOIDS": "-"}, + {"SUCCUBUS": "succubus"}, + {"LILITH": "lilith"}, + {"SUCCUBUS CONSORTS": "bram succubi"}, + {"INCUBUS": "incubus"}, + {"IMP": "imp"}, + {"DEMON": "demon"}, + {"DEU DE'BREAU": "deudebreau"}, + {" CONSTRUCTS": "-"}, + {"AUTOMATON": "automaton"}, {"SENTRYBOT": "sentrybot"}, {"SPELLBOT": "spellbot"}, - {"GYROBOT": "gyrobot"}, {"DUMMYBOT": "dummybot"}, - {"GHOST": "ghost"} + {" ELEMENTALS": "-"}, + {"SLIMES": "slime"}, + {"CRYSTAL GOLEM": "crystalgolem"}, + {"MIMIC": "mimic"} ], "contents_alphabetical": [ {"ALGERNON": "algernon"}, @@ -77,7 +83,6 @@ {"GNOME": "gnome"}, {"GOATMAN": "goatman"}, {"GOBLIN": "goblin"}, - {"GYROBOT": "gyrobot"}, {"HUMANS": "human"}, {"IMP": "imp"}, {"INCUBUS": "incubus"}, diff --git a/lang/compendium_lang/contents_world.json b/lang/compendium_lang/contents_world.json index 40e287e3d..25d031842 100644 --- a/lang/compendium_lang/contents_world.json +++ b/lang/compendium_lang/contents_world.json @@ -1,37 +1,39 @@ { "version": 1, "contents": [ - {" AREAS": "-"}, + {" FURNISHINGS": "-"}, + {"GATE": "portcullis"}, + {"LEVER": "lever"}, + {"DOOR": "door"}, + {"MURKY WATER": "murky water"}, + {"LAVA": "lava"}, + {"PITS": "pits"}, + {"BREAKABLE BARRIERS": "breakable barriers"}, + {" LOCATIONS": "-"}, {"MINEHEAD": "minehead"}, {"HALL OF TRIALS": "hall of trials"}, - {"MINES": "mines"}, - {"SHOP": "shop"}, - {"GNOMISH MINES": "gnomish mines"}, - {"MINETOWN": "minetown"}, - {" DUNGEONS": "-"}, - {" SECRET AREAS": "-"}, + {"LICH'S BASTION": "herx lair"}, + {"THE MOLTEN THRONE": "molten throne"}, + {"HAMLET": "hamlet"}, {"TRANSITION FLOOR": "transition floor"}, + {"CITADEL SANCTUM": "citadel sanctum"}, + {" DUNGEONS": "-"}, + {"MINES": "mines"}, {"SWAMPS": "swamps"}, - {"TEMPLE": "temple"}, - {"HAUNTED CASTLE": "haunted castle"}, {"LABYRINTH": "labyrinth"}, - {"SOKOBAN": "sokoban"}, - {"MINOTAUR MAZE": "minotaur maze"}, - {"RUINS": "ruins"}, - {"MYSTIC LIBRARY": "mystic library"}, - {"LICH'S BASTION": "herx lair"}, + {"RUINS": "ruins"}, {"UNDERWORLD": "underworld"}, - {"THE MOLTEN THRONE": "molten throne"}, - {"HAMLET": "hamlet"}, - {"HELL": "hell"}, + {"HELL": "hell"}, {"CRYSTAL CAVES": "crystal caves"}, - {"COCKATRICE LAIR": "cockatrice lair"}, - {"ARCANE CITADEL": "arcane citadel"}, - {"BRAM'S CASTLE": "brams castle"}, - {"CITADEL SANCTUM": "citadel sanctum"}, - - {" ENVIRONMENT": "-"}, - + {"ARCANE CITADEL": "arcane citadel"}, + {" STATIONS": "-"}, + {"SHOP": "shop"}, + {"SINK": "sink"}, + {"FOUNTAIN": "fountain"}, + {"CHEST": "chest"}, + {"GRAVESTONE": "gravestone"}, + {"OBELISK": "obelisk"}, + {" TRAPS": "-"}, {"BOULDER TRAP": "boulder trap"}, {"ARROW TRAP": "arrow trap"}, {"SPIKE TRAP": "spike trap"}, @@ -39,22 +41,17 @@ {"SUMMONING TRAP": "summoning trap"}, {"BRIMSTONE BOULDER": "brimstone boulder"}, {"CEILING TRAP": "ceiling trap"}, - {"MURKY WATER": "murky water"}, - {"LAVA": "lava"}, - {"PITS": "pits"}, - {"BREAKABLE BARRIERS": "breakable barriers"}, - - {" OBJECTS": "-"}, - {"GATE": "portcullis"}, - {"LEVER": "lever"}, - {"DOOR": "door"}, - {"SINK": "sink"}, - {"FOUNTAIN": "fountain"}, - {"CHEST": "chest"}, - {"GRAVESTONE": "gravestone"}, - {"OBELISK": "obelisk"}, - - {" GUILDS": "-"}, + {" SECRET PLACES": "-"}, + {"GNOMISH MINES": "gnomish mines"}, + {"MINETOWN": "minetown"}, + {"TEMPLE": "temple"}, + {"HAUNTED CASTLE": "haunted castle"}, + {"SOKOBAN": "sokoban"}, + {"MINOTAUR MAZE": "minotaur maze"}, + {"MYSTIC LIBRARY": "mystic library"}, + {"COCKATRICE LAIR": "cockatrice lair"}, + {"BRAM'S CASTLE": "brams castle"}, + {" GUILD LORE": "-"}, {"MERCHANTS' GUILD": "merchants guild"}, {"MAGICIANS' GUILD": "magicians guild"}, {"HUNTERS' GUILD": "hunters guild"}, diff --git a/lang/en.txt b/lang/en.txt index c98eebdc2..1c5230b07 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6411,5 +6411,6 @@ Magic Required: %d (%s)# 6182 CONTENTS# 6183 New Entry!# 6184 Achievements# +6185 RECORDS# 6200 END# From 725bade2813d091ea08cfdd2c7eb39cdc65d3930 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 9 Aug 2024 22:28:56 +1000 Subject: [PATCH 046/244] * fit splitscreen compendium tooltip --- src/interface/playerinventory.cpp | 46 +++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 814e026b4..24a152fb8 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -4112,25 +4112,32 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int } } + bool compendiumTooltip = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + } if ( tooltipContainerFrame ) { - tooltipContainerFrame->setSize( - SDL_Rect{ this->player.camera_virtualx1(), - this->player.camera_virtualy1(), - this->player.camera_virtualWidth(), - this->player.camera_virtualHeight()}); + if ( compendiumTooltip ) + { + tooltipContainerFrame->setSize(SDL_Rect{ 0, 0, parentFrame->getSize().w, parentFrame->getSize().h }); + } + else + { + tooltipContainerFrame->setSize( + SDL_Rect{ this->player.camera_virtualx1(), + this->player.camera_virtualy1(), + this->player.camera_virtualWidth(), + this->player.camera_virtualHeight()}); + } } players[player]->inventoryUI.miscTooltipOpacitySetpoint = 0; players[player]->inventoryUI.miscTooltipOpacityAnimate = 0.0; - bool compendiumTooltip = false; - if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) - { - compendiumTooltip = true; - } auto& tooltipDisplayedSettings = compendiumTooltip ? this->player.inventoryUI.compendiumItemTooltipDisplay : this->player.inventoryUI.itemTooltipDisplay; @@ -6011,8 +6018,11 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int static ConsoleVariable cvar_item_tooltip_max_height("/item_tooltip_max_height", 348); static ConsoleVariable cvar_item_tooltip_max_height_compact("/item_tooltip_max_height_compact", 264); int maxHeight = !players[player]->bUseCompactGUIHeight() ? *cvar_item_tooltip_max_height : *cvar_item_tooltip_max_height_compact; - - if ( players[player]->GUI.activeModule == Player::GUI_t::MODULE_HOTBAR && players[player]->bUseCompactGUIHeight() ) + if ( compendiumTooltip ) + { + maxHeight = *cvar_item_tooltip_max_height; + } + else if ( players[player]->GUI.activeModule == Player::GUI_t::MODULE_HOTBAR && players[player]->bUseCompactGUIHeight() ) { maxHeight -= 68; } @@ -6790,8 +6800,16 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i { totalHeight += frameTooltipPrompt->getSize().h - 2; } - const int lowestY = players[player]->camera_virtualHeight() - - (players[player]->bUseCompactGUIHeight() ? *cvar_item_tooltip_lowest_y_compact : *cvar_item_tooltip_lowest_y); + int lowestY = 0; + if ( compendiumTooltip ) + { + lowestY = tooltipContainerFrame->getSize().h - *cvar_item_tooltip_lowest_y; + } + else + { + lowestY = players[player]->camera_virtualHeight() - + (players[player]->bUseCompactGUIHeight() ? *cvar_item_tooltip_lowest_y_compact : *cvar_item_tooltip_lowest_y); + } if ( totalHeight > lowestY ) { mainPos.y -= (totalHeight - lowestY); From 6129ca2c2180b4ef61f9a867451b8aac7009ac90 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 9 Aug 2024 22:31:47 +1000 Subject: [PATCH 047/244] * lots of compendium ui --- src/mod_tools.cpp | 607 +++++++++++++-- src/mod_tools.hpp | 60 +- src/player.hpp | 2 + src/ui/MainMenu.cpp | 1764 +++++++++++++++++++++++++++++++++++++++---- 4 files changed, 2262 insertions(+), 171 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 88df4f278..6439dfff5 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -1160,6 +1160,10 @@ void ItemTooltips_t::readItemsFromFile() { t.spellTags.insert(SPELL_TAG_BASIC_HIT_MESSAGE); } + else if ( t.spellTagsStr[t.spellTagsStr.size() - 1] == "TRACK_SPELL_HITS" ) + { + t.spellTags.insert(SPELL_TAG_TRACK_HITS); + } } t.spellbookInternalName = spell_itr->value["spellbook_internal_name"].GetString(); @@ -8140,6 +8144,7 @@ LocalAchievements_t LocalAchievements; void LocalAchievements_t::readFromFile() { LocalAchievements.init(); + Compendium_t::AchievementData_t::achievementsNeedFirstData = false; char path[PATH_MAX] = ""; completePath(path, "savegames/achievements.json", outputdir); @@ -8172,11 +8177,16 @@ void LocalAchievements_t::readFromFile() ach.unlocked = achievement->value["unlocked"].GetBool(); ach.unlockTime = achievement->value["unlock_time"].GetInt64(); - auto& achData = Compendium_t::achievements[achievement->name.GetString()]; - achData.unlockTime = ach.unlockTime; - if ( ach.unlocked ) + auto find = Compendium_t::achievements.find(achievement->name.GetString()); + if ( find != Compendium_t::achievements.end() ) { - Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(ach.name); + auto& achData = find->second; + achData.unlocked = ach.unlocked; + achData.unlockTime = ach.unlockTime; + if ( ach.unlocked ) + { + Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(ach.name); + } } } @@ -8192,6 +8202,7 @@ void LocalAchievements_t::readFromFile() { g_SteamStats[statNum].m_iValue = LocalAchievements.statistics[statNum].value; } + sortAchievementsForDisplay(); } void LocalAchievements_t::writeToFile() @@ -10659,6 +10670,12 @@ std::map Compendium_t::CompendiumMagic_t::contentsMap; std::map>> Compendium_t::AchievementData_t::contents; std::map Compendium_t::AchievementData_t::unlocks; std::map Compendium_t::AchievementData_t::contentsMap; +int Compendium_t::CompendiumMonsters_t::completionPercent = 0; +int Compendium_t::CompendiumCodex_t::completionPercent = 0; +int Compendium_t::CompendiumItems_t::completionPercent = 0; +int Compendium_t::CompendiumMagic_t::completionPercent = 0; +int Compendium_t::CompendiumWorld_t::completionPercent = 0; +int Compendium_t::AchievementData_t::completionPercent = 0; std::map Compendium_t::Events_t::monsterIDToString; std::map Compendium_t::Events_t::codexIDToString; @@ -10767,11 +10784,11 @@ void Compendium_t::updateTooltip() auto compendiumFrame = MainMenu::main_menu_frame->findFrame("compendium"); if ( !compendiumFrame ) { return; } - players[0]->inventoryUI.updateInventoryItemTooltip(compendiumFrame); + players[MainMenu::getMenuOwner()]->inventoryUI.updateInventoryItemTooltip(compendiumFrame); if ( update ) { - players[0]->hud.updateFrameTooltip(&compendiumItem, tooltipPos.x, tooltipPos.y, Player::PANEL_JUSTIFY_RIGHT, compendiumFrame); + players[MainMenu::getMenuOwner()]->hud.updateFrameTooltip(&compendiumItem, tooltipPos.x, tooltipPos.y, Player::PANEL_JUSTIFY_RIGHT, compendiumFrame); } } } @@ -10861,6 +10878,9 @@ void Compendium_t::readItemsFromFile() alwaysTrackedEvents.insert("DEGRADED"); alwaysTrackedEvents.insert("REPAIRS"); } + + Compendium_t::Events_t::itemDisplayedEventsList.erase(itemType); + Compendium_t::Events_t::itemDisplayedCustomEventsList.erase(itemType); } } @@ -10909,6 +10929,8 @@ void Compendium_t::readItemsFromFile() } } } + + std::set itemsInList; if ( w.HasMember("events_display") ) { for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) @@ -10927,16 +10949,52 @@ void Compendium_t::readItemsFromFile() { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[itemType]; if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) - == vec.end() ) + == vec.end() || find2->second.id == EventTags::CPDM_CUSTOM_TAG ) { vec.push_back((Compendium_t::EventTags)find2->second.id); } + itemsInList.insert(itemType); } } } } } } + + if ( w.HasMember("custom_events_display") ) + { + std::vector customEvents; + for ( auto itr = w["custom_events_display"].Begin(); itr != w["custom_events_display"].End(); ++itr ) + { + customEvents.push_back(itr->GetString()); + } + + for ( auto item : itemsInList ) + { + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[item]; + int index = -1; + for ( auto& v : vec ) + { + ++index; + auto& vec2 = Compendium_t::Events_t::itemDisplayedCustomEventsList[item]; + if ( v == EventTags::CPDM_CUSTOM_TAG ) + { + if ( index < customEvents.size() ) + { + vec2.push_back(customEvents[index]); + } + else + { + vec2.push_back(""); + } + } + else + { + vec2.push_back(""); + } + } + } + } } } @@ -11175,10 +11233,14 @@ void Compendium_t::readMagicFromFile() if ( !isSpell ) { Compendium_t::Events_t::itemIDToString[itemType] = name; + Compendium_t::Events_t::itemDisplayedEventsList.erase(itemType); + Compendium_t::Events_t::itemDisplayedCustomEventsList.erase(itemType); } else { Compendium_t::Events_t::itemIDToString[Compendium_t::Events_t::kEventSpellOffset + item.spellID] = name; + Compendium_t::Events_t::itemDisplayedEventsList.erase(Compendium_t::Events_t::kEventSpellOffset + item.spellID); + Compendium_t::Events_t::itemDisplayedCustomEventsList.erase(Compendium_t::Events_t::kEventSpellOffset + item.spellID); } if ( itemType != SPELL_ITEM && ::items[itemType].item_slot != NO_EQUIP ) { @@ -11246,6 +11308,8 @@ void Compendium_t::readMagicFromFile() } } } + + std::set itemsInList; if ( w.HasMember("events_display") ) { for ( auto itr = w["events_display"].Begin(); itr != w["events_display"].End(); ++itr ) @@ -11265,25 +11329,62 @@ void Compendium_t::readMagicFromFile() { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[Compendium_t::Events_t::kEventSpellOffset + item.spellID]; if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) - == vec.end() ) + == vec.end() || find2->second.id == EventTags::CPDM_CUSTOM_TAG ) { vec.push_back((Compendium_t::EventTags)find2->second.id); } + itemsInList.insert(Compendium_t::Events_t::kEventSpellOffset + item.spellID); } else if ( itemType >= WOODEN_SHIELD && itemType < NUMITEMS ) { auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[itemType]; if ( std::find(vec.begin(), vec.end(), (Compendium_t::EventTags)find2->second.id) - == vec.end() ) + == vec.end() || find2->second.id == EventTags::CPDM_CUSTOM_TAG ) { vec.push_back((Compendium_t::EventTags)find2->second.id); } + itemsInList.insert(itemType); } } } } } } + + if ( w.HasMember("custom_events_display") ) + { + std::vector customEvents; + for ( auto itr = w["custom_events_display"].Begin(); itr != w["custom_events_display"].End(); ++itr ) + { + customEvents.push_back(itr->GetString()); + } + + for ( auto item : itemsInList ) + { + auto& vec = Compendium_t::Events_t::itemDisplayedEventsList[item]; + int index = -1; + for ( auto& v : vec ) + { + ++index; + auto& vec2 = Compendium_t::Events_t::itemDisplayedCustomEventsList[item]; + if ( v == EventTags::CPDM_CUSTOM_TAG ) + { + if ( index < customEvents.size() ) + { + vec2.push_back(customEvents[index]); + } + else + { + vec2.push_back(""); + } + } + else + { + vec2.push_back(""); + } + } + } + } } } @@ -11624,6 +11725,11 @@ void Compendium_t::readMonstersFromFile() Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILL_XP].insert(type); + if ( i == GYROBOT ) + { + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_GYROBOT_FLIPS].insert(type); + } Compendium_t::Events_t::monsterIDToString[type] = monstertypename[i]; } @@ -11645,6 +11751,7 @@ void Compendium_t::readMonstersFromFile() Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_MULTIPLAYER].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILLED_BY].insert(type); Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_RECRUITED].insert(type); + Compendium_t::Events_t::eventMonsterLookup[EventTags::CPDM_KILL_XP].insert(type); if ( pair.first == "mysterious shop" ) { @@ -11808,6 +11915,24 @@ std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const ch resultsFormatting = langMap[fmt]; } } + else if ( formatType && itemIDToString.find(formatVal) != itemIDToString.end() + && itemIDToString.at(formatVal) == formatType ) + { + std::string fmt = "format_"; + fmt += formatType; + if ( langMap.find(fmt) != langMap.end() ) + { + resultsFormatting = langMap[fmt]; + } + else if ( langMap.find("format") != langMap.end() ) + { + resultsFormatting = langMap["format"]; + } + else + { + return std::to_string(value); + } + } else if ( langMap.find("format") != langMap.end() ) { resultsFormatting = langMap["format"]; @@ -11894,6 +12019,14 @@ std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const ch { output += std::to_string(value); } + else if ( resultsFormatting[c + 1] == 'p' ) + { + if ( value >= 0 ) + { + output += '+'; + } + output += std::to_string(value); + } else if ( resultsFormatting[c + 1] == 'h' ) { real_t regen = value; @@ -12024,10 +12157,19 @@ std::vector> Compendium_t::Events_t::getCustomEve } } - if ( valueType != "class_sum" ) + int specificItemId = -1; + if ( compendiumSection == "items" || compendiumSection == "magic" ) { + specificItemId = specificClass; specificClass = -1; } + else + { + if ( valueType != "class_sum" ) + { + specificClass = -1; + } + } std::map mapValueTotals; std::string formatType = ""; @@ -12043,9 +12185,17 @@ std::vector> Compendium_t::Events_t::getCustomEve { std::string name = (*itr)["name"].GetString(); std::string cat = (*itr)["category"].GetString(); - if ( cat == "" ) + + if ( compendiumSection == "items" || compendiumSection == "magic" ) { - cat = compendiumContentsSelected; + // items dont use the category header by default, either specific item or current viewed item + } + else + { + if ( cat == "" ) + { + cat = compendiumContentsSelected; + } } if ( valueType == "sum_items" ) @@ -12120,9 +12270,35 @@ std::vector> Compendium_t::Events_t::getCustomEve continue; } - auto& eventSectionIDLookup = compendiumSection == "codex" ? eventCodexIDLookup : eventWorldIDLookup; - auto findCat = eventSectionIDLookup.find(cat); - if ( findCat != eventSectionIDLookup.end() ) + int foundId = -1; + if ( compendiumSection == "items" || compendiumSection == "magic" ) + { + if ( cat == "" ) + { + if ( itemIDToString.find(specificItemId) != itemIDToString.end() ) + { + foundId = specificItemId; + } + } + else + { + auto find = ItemTooltips.itemNameStringToItemID.find(cat); + if ( find != ItemTooltips.itemNameStringToItemID.end() ) + { + foundId = find->second; + } + } + } + else + { + auto& eventSectionIDLookup = compendiumSection == "codex" ? eventCodexIDLookup : eventWorldIDLookup; + auto findCat = eventSectionIDLookup.find(cat); + if ( findCat != eventSectionIDLookup.end() ) + { + foundId = findCat->second; + } + } + if ( foundId >= 0 ) { auto findTag = eventIdLookup.find(name); if ( findTag != eventIdLookup.end() ) @@ -12139,11 +12315,26 @@ std::vector> Compendium_t::Events_t::getCustomEve } auto& playerTags = playerEvents[tag]; std::vector> codexIDs; - codexIDs.push_back(std::make_pair(-1, findCat->second)); + codexIDs.push_back(std::make_pair(-1, foundId)); - auto& eventSectionLookup = compendiumSection == "codex" ? eventCodexLookup : eventWorldLookup; + bool foundLookup = false; + if ( compendiumSection == "items" || compendiumSection == "magic" ) + { + if ( eventItemLookup[tag].find(foundId) != eventItemLookup[tag].end() ) + { + foundLookup = true; + } + } + else + { + auto& eventSectionLookup = compendiumSection == "codex" ? eventCodexLookup : eventWorldLookup; + if ( eventSectionLookup[tag].find(cat) != eventSectionLookup[tag].end() ) + { + foundLookup = true; + } + } - if ( eventSectionLookup[tag].find(cat) != eventSectionLookup[tag].end() ) + if ( foundLookup ) { auto& def = events[tag]; if ( def.attributes.find("stats") != def.attributes.end() && valueType != "max_class" ) @@ -12255,7 +12446,7 @@ std::vector> Compendium_t::Events_t::getCustomEve codexID += kEventCodexOffset; // convert to offset } } - else + else if ( compendiumSection == "world" ) { if ( codexID < kEventWorldOffset ) { @@ -12275,7 +12466,7 @@ std::vector> Compendium_t::Events_t::getCustomEve int numFormats = 0; if ( valueType == "sum_category_max" || valueType == "sum_category_min" ) { - int categoryValue = findCat->second; + int categoryValue = foundId; if ( formatType == "skills" ) { for ( int i = 0; i < NUMPROFICIENCIES; ++i ) @@ -12721,7 +12912,7 @@ void Compendium_t::Events_t::loadItemsSaveData() } if ( itemType >= kEventCodexOffset && itemType <= kEventCodexOffsetMax ) { - eventUpdateCodex(0, id, nullptr, value, true, itemType - kEventCodexOffset); + eventUpdateCodex(0, id, nullptr, value, true, itemType); continue; } if ( itemType < 0 || (itemType >= NUMITEMS && itemType < kEventSpellOffset) ) @@ -12840,11 +13031,26 @@ void Compendium_t::readUnlocksSaveData() CompendiumCodex_t::unlocks["crits"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; CompendiumCodex_t::unlocks["flanking"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; CompendiumCodex_t::unlocks["backstabs"] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED;*/ + // debug stuff + /*for ( auto& data : CompendiumEntries.worldObjects ) + { + CompendiumWorld_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + + for ( auto& data : CompendiumEntries.monsters ) + { + CompendiumMonsters_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + }*/ for ( auto& data : CompendiumEntries.items ) { for ( auto& entry : data.second.items_in_category ) { + // debug stuff + /*CompendiumItems_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumItems_t::itemUnlocks[entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED;*/ if ( entry.itemID == TOOL_TORCH || entry.itemID == BRONZE_SWORD || entry.itemID == WOODEN_SHIELD @@ -12868,6 +13074,11 @@ void Compendium_t::readUnlocksSaveData() { for ( auto& entry : data.second.items_in_category ) { + // debug stuff + /*CompendiumItems_t::unlocks[data.first] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + CompendiumItems_t::itemUnlocks[entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID] = CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED;*/ if ( entry.itemID == TOOL_TORCH || entry.itemID == BRONZE_SWORD || entry.itemID == WOODEN_SHIELD @@ -13010,7 +13221,6 @@ void Compendium_t::readUnlocksSaveData() void Compendium_t::writeUnlocksSaveData() { - return; char path[PATH_MAX] = ""; completePath(path, "savegames/compendium_progress.json", outputdir); @@ -13125,7 +13335,7 @@ void Compendium_t::Events_t::writeItemsSaveData() return; } rapidjson::StringBuffer os; - rapidjson::PrettyWriter writer(os); + rapidjson::Writer writer(os); exportDocument.Accept(writer); fp->write(os.GetString(), sizeof(char), os.GetSize()); FileIO::close(fp); @@ -13136,6 +13346,8 @@ void Compendium_t::Events_t::writeItemsSaveData() bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) { + bool first = firstValue; + firstValue = false; if ( type == SUM ) { value += val; @@ -13150,6 +13362,11 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) } else if ( type == MAX ) { + if ( first ) + { + value = val; + return true; + } if ( value == val ) { return false; @@ -13159,6 +13376,11 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) } else if ( type == MIN ) { + if ( first ) + { + value = val; + return true; + } if ( value == val ) { return false; @@ -13336,6 +13558,7 @@ void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) if ( ticks % (5 * TICKS_PER_SECOND) == 25 ) { int weight = 0; + int numDeathBoxes = 0; for ( node_t* node = stats[playernum]->inventory.first; node != NULL; node = node->next ) { Item* item = (Item*)node->element; @@ -13347,6 +13570,10 @@ void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) { continue; } + if ( item->type == TOOL_PLAYER_LOOT_BAG ) + { + ++numDeathBoxes; + } if ( ::items[item->type].item_slot != NO_EQUIP && itemIsEquipped(item, playernum) ) { @@ -13354,6 +13581,10 @@ void Compendium_t::Events_t::updateEventsInMainLoop(const int playernum) } } Compendium_t::Events_t::eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_WGT_EQUIPPED_MAX, "wgt", weight); + if ( numDeathBoxes > 0 ) + { + eventUpdate(playernum, CPDM_DEATHBOX_MOST_CARRIED, TOOL_PLAYER_LOOT_BAG, numDeathBoxes); + } } } @@ -13594,6 +13825,27 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p onCompendiumLevelExit(playernum, currentWorldString, true); } + if ( stats[playernum] ) + { + int numDeathBoxes = 0; + for ( node_t* node = stats[playernum]->inventory.first; node; node = node->next ) + { + Item* item = (Item*)node->element; + if ( !item ) + { + continue; + } + if ( item->type == TOOL_PLAYER_LOOT_BAG ) + { + ++numDeathBoxes; + } + } + if ( numDeathBoxes > 0 ) + { + eventUpdate(playernum, CPDM_DEATHBOX_TO_EXIT, TOOL_PLAYER_LOOT_BAG, numDeathBoxes); + } + } + const char* prevWorldString = compendiumCurrentLevelToWorldString(prevlevel, prevsecretfloor); if ( !prevsecretfloor ) { @@ -13799,6 +14051,7 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con if ( playernum == 0 ) { playernum = clientnum; // when a client receives an update from the server + clientReceiveUpdateFromServer = true; } } } @@ -13837,30 +14090,39 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con if ( loadingValue ) { val.value = value; // reading from savefile + val.firstValue = false; } else { - if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) + if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL + || gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL_INIT ) { - if ( clientReceiveUpdateFromServer ) - { - // server is tracking the total for us, so don't add - players[playernum]->compendiumProgress.itemEvents[def.name][itemType] = value; - } - else + // don't update in tutorial + } + else + { + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN ) { - players[playernum]->compendiumProgress.itemEvents[def.name][itemType] += value; + if ( clientReceiveUpdateFromServer ) + { + // server is tracking the total for us, so don't add + players[playernum]->compendiumProgress.itemEvents[def.name][itemType] = value; + } + else + { + players[playernum]->compendiumProgress.itemEvents[def.name][itemType] += value; + } + value = players[playernum]->compendiumProgress.itemEvents[def.name][itemType]; } - value = players[playernum]->compendiumProgress.itemEvents[def.name][itemType]; - } - if ( val.applyValue(value) ) - { - if ( *cvar_compendiumDebugSave ) + if ( val.applyValue(value) ) { - if ( playernum == clientnum ) + if ( *cvar_compendiumDebugSave ) { - writeItemsSaveData(); + if ( playernum == clientnum ) + { + writeItemsSaveData(); + } } } } @@ -13870,6 +14132,39 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con { if ( tag == CPDM_RUNS_COLLECTED ) { + { + Monster monsterUnlock = NOTHING; + if ( type == TOOL_SENTRYBOT ) + { + monsterUnlock = SENTRYBOT; + } + else if ( type == TOOL_SPELLBOT ) + { + monsterUnlock = SPELLBOT; + } + else if ( type == TOOL_GYROBOT ) + { + monsterUnlock = GYROBOT; + } + else if ( type == TOOL_DUMMYBOT ) + { + monsterUnlock = DUMMYBOT; + } + if ( monsterUnlock != NOTHING ) + { + int monsterId = Compendium_t::Events_t::kEventMonsterOffset + monsterUnlock; + auto find = Compendium_t::Events_t::monsterIDToString.find(monsterId); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + } + bool itemUnlocked = false; { auto& unlockStatus = Compendium_t::CompendiumItems_t::itemUnlocks[itemType]; @@ -13887,13 +14182,13 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con { unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } - else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) + /*else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) { if ( itemUnlocked ) { unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } - } + }*/ else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) { if ( itemUnlocked ) @@ -14027,6 +14322,7 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t if ( loadingValue ) { val.value = value; // reading from savefile + val.firstValue = false; } else { @@ -14189,6 +14485,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag } auto& val = e[worldID]; val.value = value; // reading from savefile + val.firstValue = false; } else { @@ -14239,6 +14536,20 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag { unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } + if ( category && !strcmp(category, "shop") ) + { + // buying items triggers shopkeep stuff + auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + SHOPKEEPER); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + + } } } } @@ -14407,13 +14718,13 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag return; } - if ( codexID < kEventCodexOffset || loadingValue ) + if ( codexID < kEventCodexOffset ) { codexID += kEventCodexOffset; // convert to offset } if ( baseCodexID >= 0 ) { - if ( baseCodexID < kEventCodexOffset || loadingValue ) + if ( baseCodexID < kEventCodexOffset ) { baseCodexID += kEventCodexOffset; // convert to offset } @@ -14441,6 +14752,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag if ( loadingValue ) { val.value = value; // reading from savefile + val.firstValue = false; } else { @@ -14934,12 +15246,221 @@ void Compendium_t::exportCurrentMonster(Entity* monster) return; } + +void Compendium_t::updateLorePointCounts() +{ + lorePointsFromAchievements = 0; + lorePointsSpent = 0; + lorePointsAchievementsTotal = 0; + int completed = 0; + int total = achievements.size(); + for ( auto& achData : achievements ) + { + if ( achData.second.unlocked ) + { + lorePointsFromAchievements += achData.second.lorePoints; + ++completed; + } + lorePointsAchievementsTotal += achData.second.lorePoints; + } + + AchievementData_t::completionPercent = 100.0 * (completed / (real_t)total); + + completed = 0; + total = 0; + for ( auto& pair : CompendiumItems_t::contents["default"] ) + { + if ( pair.first != "-" ) + { + total += 2; + } + auto unlockStatus = CompendiumItems_t::unlocks.find(pair.first); + if ( unlockStatus != CompendiumItems_t::unlocks.end() ) + { + if ( unlockStatus->second != LOCKED_UNKNOWN ) + { + if ( unlockStatus->second == UNLOCKED_UNVISITED || unlockStatus->second == UNLOCKED_VISITED ) + { + auto find = CompendiumEntries.items.find(pair.first); + if ( find != CompendiumEntries.items.end() ) + { + lorePointsSpent += find->second.lorePoints; + ++completed; // 2x completion + } + } + ++completed; + } + } + } + + for ( auto& item : CompendiumEntries.items ) + { + total += item.second.items_in_category.size(); + for ( auto& entry : item.second.items_in_category ) + { + int type = entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID; + auto find = Compendium_t::CompendiumItems_t::itemUnlocks.find(type); + if ( find != Compendium_t::CompendiumItems_t::itemUnlocks.end() ) + { + if ( find->second != LOCKED_UNKNOWN ) + { + completed++; + } + } + } + } + + CompendiumItems_t::completionPercent = 100.0 * (completed / (real_t)total); + + completed = 0; + total = 0; + for ( auto& pair : CompendiumMagic_t::contents["default"] ) + { + if ( pair.first != "-" ) + { + total += 2; + } + auto unlockStatus = CompendiumItems_t::unlocks.find(pair.first); + if ( unlockStatus != CompendiumItems_t::unlocks.end() ) + { + if ( unlockStatus->second != LOCKED_UNKNOWN ) + { + if ( unlockStatus->second == UNLOCKED_UNVISITED || unlockStatus->second == UNLOCKED_VISITED ) + { + auto find = CompendiumEntries.magic.find(pair.first); + if ( find != CompendiumEntries.magic.end() ) + { + lorePointsSpent += find->second.lorePoints; + ++completed; // 2x completion + } + } + ++completed; + } + } + } + + for ( auto& item : CompendiumEntries.magic ) + { + total += item.second.items_in_category.size(); + for ( auto& entry : item.second.items_in_category ) + { + int type = entry.itemID == SPELL_ITEM + ? entry.spellID + Compendium_t::Events_t::kEventSpellOffset : + entry.itemID; + auto find = Compendium_t::CompendiumItems_t::itemUnlocks.find(type); + if ( find != Compendium_t::CompendiumItems_t::itemUnlocks.end() ) + { + if ( find->second != LOCKED_UNKNOWN ) + { + completed++; + } + } + } + } + + CompendiumMagic_t::completionPercent = 100.0 * (completed / (real_t)total); + + completed = 0; + total = 0; + for ( auto& pair : CompendiumWorld_t::contents["default"] ) + { + if ( pair.first != "-" ) + { + total += 2; + } + auto unlockStatus = CompendiumWorld_t::unlocks.find(pair.first); + if ( unlockStatus != CompendiumWorld_t::unlocks.end() ) + { + if ( unlockStatus->second != LOCKED_UNKNOWN ) + { + if ( unlockStatus->second == UNLOCKED_UNVISITED || unlockStatus->second == UNLOCKED_VISITED ) + { + auto find = CompendiumEntries.worldObjects.find(pair.first); + if ( find != CompendiumEntries.worldObjects.end() ) + { + lorePointsSpent += find->second.lorePoints; + ++completed; // 2x completion + } + } + ++completed; + } + } + } + + CompendiumWorld_t::completionPercent = 100.0 * (completed / (real_t)total); + + completed = 0; + total = 0; + for ( auto& pair : CompendiumCodex_t::contents["default"] ) + { + if ( pair.first != "-" ) + { + total += 2; + } + auto unlockStatus = CompendiumCodex_t::unlocks.find(pair.first); + if ( unlockStatus != CompendiumCodex_t::unlocks.end() ) + { + if ( unlockStatus->second != LOCKED_UNKNOWN ) + { + if ( unlockStatus->second == UNLOCKED_UNVISITED || unlockStatus->second == UNLOCKED_VISITED ) + { + auto find = CompendiumEntries.codex.find(pair.first); + if ( find != CompendiumEntries.codex.end() ) + { + lorePointsSpent += find->second.lorePoints; + ++completed; // 2x completion + } + } + ++completed; + } + } + } + + CompendiumCodex_t::completionPercent = std::min(100.0, 100.0 * (completed / (real_t)total)); + + completed = 0; + total = 0; + for ( auto& pair : CompendiumMonsters_t::contents["default"] ) + { + if ( pair.first != "-" ) + { + total += 2; + } + auto unlockStatus = CompendiumMonsters_t::unlocks.find(pair.first); + if ( unlockStatus != CompendiumMonsters_t::unlocks.end() ) + { + if ( unlockStatus->second != LOCKED_UNKNOWN ) + { + if ( unlockStatus->second == UNLOCKED_UNVISITED || unlockStatus->second == UNLOCKED_VISITED ) + { + auto find = CompendiumEntries.monsters.find(pair.first); + if ( find != CompendiumEntries.monsters.end() ) + { + lorePointsSpent += find->second.lorePoints; + ++completed; // 2x completion + } + } + ++completed; + } + } + } + + CompendiumMonsters_t::completionPercent = 100.0 * (completed / (real_t)total); +} #endif std::unordered_map Compendium_t::achievements; bool Compendium_t::AchievementData_t::achievementsNeedResort = true; bool Compendium_t::AchievementData_t::achievementsNeedFirstData = true; +int Compendium_t::lorePointsFromAchievements = 0; +int Compendium_t::lorePointsAchievementsTotal = 0; +int Compendium_t::lorePointsSpent = 0; std::set, Compendium_t::AchievementData_t::Comparator> Compendium_t::AchievementData_t::achievementNamesSorted; std::map>> Compendium_t::AchievementData_t::achievementCategories; std::map Compendium_t::AchievementData_t::achievementsBookDisplay; -std::unordered_set Compendium_t::AchievementData_t::achievementUnlockedLookup; \ No newline at end of file +std::unordered_set Compendium_t::AchievementData_t::achievementUnlockedLookup; +bool Compendium_t::AchievementData_t::sortAlphabetical = false; +std::string Compendium_t::compendium_sorting = "default"; +bool Compendium_t::compendium_sorting_hide_undiscovered = false; \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 4c9f80d18..7b774f2b4 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2769,7 +2769,8 @@ class ItemTooltips_t SPELL_TAG_STATUS_EFFECT, SPELL_TAG_HEALING, SPELL_TAG_CURE, - SPELL_TAG_BASIC_HIT_MESSAGE + SPELL_TAG_BASIC_HIT_MESSAGE, + SPELL_TAG_TRACK_HITS }; private: struct spellItem_t @@ -3539,6 +3540,7 @@ struct Compendium_t static std::map>> contents; static std::map contentsMap; static std::map unlocks; + static int completionPercent; static void readContentsLang(); struct CompendiumAchievementsDisplay @@ -3548,8 +3550,11 @@ struct Compendium_t int numHidden = 0; }; static std::map achievementsBookDisplay; + static bool sortAlphabetical; }; static std::unordered_map achievements; + static std::string compendium_sorting; + static bool compendium_sorting_hide_undiscovered; enum EventTags { @@ -3819,6 +3824,48 @@ struct Compendium_t CPDM_LORE_PERCENT_READ, CPDM_LORE_PERCENT_READ_2, CPDM_MERCHANT_ORBS, + CPDM_SPELL_DMG, + CPDM_SPELL_HEAL, + CPDM_TIME_WORN, + CPDM_LOCKPICK_DOOR_UNLOCK, + CPDM_LOCKPICK_DOOR_LOCK, + CPDM_LOCKPICK_ARROWTRAPS, + CPDM_LOCKPICK_TINKERTRAPS, + CPDM_LOCKPICK_CHESTS_UNLOCK, + CPDM_LOCKPICK_MIMICS_LOCKED, + CPDM_LOCKPICK_CHESTS_LOCK, + CPDM_ALEMBIC_DUPLICATION_FAIL, + CPDM_ALEMBIC_EXPLOSIONS, + CPDM_BEARTRAP_DEPLOYED, + CPDM_BEARTRAP_TRAPPED, + CPDM_BEARTRAP_DMG, + CPDM_GADGET_CRAFTED, + CPDM_GADGET_DEPLOYED, + CPDM_NOISEMAKER_LURED, + CPDM_NOISEMAKER_MOST_LURED, + CPDM_DUMMY_HITS_TAKEN, + CPDM_DUMMY_DMG_TAKEN, + CPDM_SENTRY_DEPLOY_KILLS, + CPDM_SENTRY_DEPLOY_DMG, + CPDM_GYROBOT_BOULDERS, + CPDM_GYROBOT_TIME_SPENT, + CPDM_BOMB_DMG, + CPDM_BOMB_DETONATED, + CPDM_BOMB_DETONATED_ALLY, + CPDM_DETONATOR_SCRAPPED, + CPDM_DETONATOR_SCRAPPED_METAL, + CPDM_DETONATOR_SCRAPPED_MAGIC, + CPDM_DEATHBOX_OPEN_OWN, + CPDM_DEATHBOX_OPEN_OTHERS, + CPDM_DEATHBOX_TO_EXIT, + CPDM_DEATHBOX_MOST_CARRIED, + CPDM_PICKAXE_BOULDERS_DUG, + CPDM_TIN_GREASY, + CPDM_TIN_REGEN_HP, + CPDM_TIN_REGEN_MP, + CPDM_SPELL_TARGETS, + CPDM_GYROBOT_FLIPS, + CPDM_KILL_XP, CPDM_EVENT_TAGS_MAX }; @@ -3847,6 +3894,7 @@ struct Compendium_t static std::map contentsMap; static void readContentsLang(); static std::map unlocks; + static int completionPercent; }; std::map monsters; void readMonstersFromFile(); @@ -3887,6 +3935,7 @@ struct Compendium_t static std::map contentsMap; static void readContentsLang(); static std::map unlocks; + static int completionPercent; }; std::map worldObjects; void readWorldFromFile(); @@ -3909,6 +3958,7 @@ struct Compendium_t static std::map contentsMap; static void readContentsLang(); static std::map unlocks; + static int completionPercent; }; std::map codex; void readCodexFromFile(); @@ -3937,6 +3987,7 @@ struct Compendium_t static void readContentsLang(); static std::map unlocks; static std::map itemUnlocks; + static int completionPercent; }; std::map items; void readItemsFromFile(); @@ -3946,6 +3997,7 @@ struct Compendium_t static std::map>> contents; static std::map contentsMap; static void readContentsLang(); + static int completionPercent; }; std::map magic; void readMagicFromFile(); @@ -3955,6 +4007,10 @@ struct Compendium_t static SDL_Rect tooltipPos; static Entity compendiumItemModel; static Uint32 lastTickUpdate; + static int lorePointsFromAchievements; + static int lorePointsAchievementsTotal; + static int lorePointsSpent; + static void updateLorePointCounts(); static void writeUnlocksSaveData(); static void readUnlocksSaveData(); @@ -4038,6 +4094,7 @@ struct Compendium_t Type type = SUM; int id = CPDM_EVENT_TAGS_MAX; Sint32 value = 0; + bool firstValue = true; bool applyValue(const Sint32 val); EventVal_t() = default; EventVal_t(EventTags tag) @@ -4046,6 +4103,7 @@ struct Compendium_t type = def.type; id = def.id; value = 0; + firstValue = true; } }; static std::map events; diff --git a/src/player.hpp b/src/player.hpp index 7efd5407a..7d03fd033 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2277,6 +2277,8 @@ class Player Uint32 playerAliveTimeStopped = 0; Uint32 playerAliveTimeTotal = 0; Uint32 playerGameTimeTotal = 0; + std::map playerEquipSlotTime; + std::map allyTimeSpent; CompendiumProgress_t(Player& p) : player(p) {}; ~CompendiumProgress_t() {}; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 1b19c0886..f497f30ce 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -23,6 +23,8 @@ #include "../interface/ui.hpp" #include "../eos.hpp" #include "../colors.hpp" +#include "../book.hpp" +#include "../scrolls.hpp" #include #include @@ -32639,8 +32641,11 @@ namespace MainMenu { static const int compendiumPageRightInnerY = 32; static const int compendiumPageRightInnerHeight = 412 - 114 + 22; static const int compendiumPageRightInnerHeightExpanded = 412; + constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); + constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); + constexpr auto compendiumContentsDivColor = makeColorRGB(42, 22, 18); - static void refreshCompendiumEntryWorld(std::string name, Frame* parent) + static void refreshCompendiumEntryWorld(std::string name, Frame* parent, const bool gamepadClick) { if ( CompendiumEntries.worldObjects.find(name) == CompendiumEntries.worldObjects.end() ) { @@ -32695,8 +32700,43 @@ namespace MainMenu { } } + if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) + { + if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); + if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) + { + unlock_lore_cost->setColor(compendiumContentsSelectedColor); + } + else + { + unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + } + } + else + { + unlock_lore_cost->setText("-"); + } + } + } + if ( Frame* page_right = parent->findFrame("page_right") ) { + if ( auto page_right_unlock_cost = page_right->findField("lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + page_right_unlock_cost->setText(std::to_string(entry.lorePoints).c_str()); + } + else + { + page_right_unlock_cost->setText("-"); + } + } + // find records for this item populateRecordsSectionItems(page_right, entry.id + Compendium_t::Events_t::kEventWorldOffset, name.c_str()); @@ -32732,10 +32772,6 @@ namespace MainMenu { } } - constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); - constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); - constexpr auto compendiumContentsDivColor = makeColorRGB(42, 22, 18); - struct CompendiumTooltipFrames_t { Frame* tooltipContainerFrame = nullptr; @@ -32743,6 +32779,7 @@ namespace MainMenu { Frame* tooltipFrame = nullptr; Frame* interactFrame = nullptr; Frame* tooltipPromptFrame = nullptr; + bool tooltipGamepadOpen = false; void clear() { tooltipContainerFrame = nullptr; @@ -32750,6 +32787,7 @@ namespace MainMenu { tooltipFrame = nullptr; interactFrame = nullptr; tooltipPromptFrame = nullptr; + tooltipGamepadOpen = false; } }; static CompendiumTooltipFrames_t compendiumItemTooltip; @@ -32992,8 +33030,31 @@ namespace MainMenu { if ( entryType >= Compendium_t::Events_t::kEventMonsterOffset && entryType < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) { - if ( (entryType - Compendium_t::Events_t::kEventMonsterOffset) - == Compendium_t::Events_t::monsterUniqueIDLookup["ghost"] ) + bool recruitable = true; + int monsterType = (entryType - Compendium_t::Events_t::kEventMonsterOffset); + if ( monsterType == MINOTAUR + || monsterType == LICH + || monsterType == DEVIL + || monsterType == SHOPKEEPER + || monsterType == LICH_ICE + || monsterType == LICH_FIRE + || monsterType == SHADOW + || monsterType == MIMIC + || monsterType == SPELLBOT + || monsterType == SENTRYBOT + || monsterType == GYROBOT + || monsterType == DUMMYBOT ) + { + recruitable = false; + } + else if ( monsterType == Compendium_t::Events_t::monsterUniqueIDLookup["bram kindly"] + || monsterType == Compendium_t::Events_t::monsterUniqueIDLookup["artemisia"] + || monsterType == Compendium_t::Events_t::monsterUniqueIDLookup["baratheon"] ) + { + recruitable = false; + } + + if ( monsterType == Compendium_t::Events_t::monsterUniqueIDLookup["ghost"] ) { displayedEvents = { Compendium_t::EventTags::CPDM_GHOST_SPAWNED, @@ -33002,14 +33063,45 @@ namespace MainMenu { Compendium_t::EventTags::CPDM_GHOST_PINGS }; } + else if ( monsterType == Compendium_t::Events_t::monsterUniqueIDLookup["mysterious shop"] ) + { + displayedEvents = { + Compendium_t::EventTags::CPDM_MERCHANT_ORBS, + Compendium_t::EventTags::CPDM_SHOP_BOUGHT, + Compendium_t::EventTags::CPDM_SHOP_SPENT, + Compendium_t::EventTags::CPDM_KILLED_BY + }; + } + else if ( monsterType == DUMMYBOT ) + { + displayedEvents = { + Compendium_t::EventTags::CPDM_KILLED_SOLO + }; + } + else if ( monsterType == GYROBOT ) + { + displayedEvents = { + Compendium_t::EventTags::CPDM_GYROBOT_FLIPS + }; + } + else if ( monsterType == SENTRYBOT || monsterType == SPELLBOT ) + { + displayedEvents = { + Compendium_t::EventTags::CPDM_KILLED_SOLO, + Compendium_t::EventTags::CPDM_KILLED_BY + }; + } else { displayedEvents = { Compendium_t::EventTags::CPDM_KILLED_SOLO, - Compendium_t::EventTags::CPDM_KILLED_MULTIPLAYER, - Compendium_t::EventTags::CPDM_KILLED_BY, - Compendium_t::EventTags::CPDM_RECRUITED + Compendium_t::EventTags::CPDM_KILL_XP, + Compendium_t::EventTags::CPDM_KILLED_BY }; + if ( recruitable ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_RECRUITED); + } } } else @@ -33022,15 +33114,192 @@ namespace MainMenu { if ( entryType >= 0 && entryType < NUMITEMS ) { - if ( items[entryType].category == SPELLBOOK ) + if ( entryType == TOOL_DETONATOR_CHARGE ) + { + displayedEvents.clear(); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_DETONATOR_SCRAPPED); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_DETONATOR_SCRAPPED_METAL); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_DETONATOR_SCRAPPED_MAGIC); + } + else if ( entryType == TOOL_MIRROR ) + { + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_SHIELD_REFLECT ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else if ( entryType == TOOL_LANTERN ) { - displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELLBOOK_LEARNT); + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_TORCH_WALLS ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else if ( entryType == MASK_ARTIFACT_VISOR /* + || (items[entryType].item_slot == EQUIPPABLE_IN_SLOT_MASK && !Item::doesItemProvideBeatitudeAC((ItemType)entryType))*/ ) + { + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_BROKEN ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else if ( entryType == MIRROR_SHIELD ) + { + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_MIRROR_TELEPORTS ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else if ( entryType == TOOL_SLEEP_BOMB ) + { + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_BOMB_DMG ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else if ( entryType == TOOL_TINOPENER ) + { + displayedEvents.clear(); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_RUNS_COLLECTED); + } + else if ( entryType == GEM_ROCK ) + { + displayedEvents.clear(); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_RUNS_COLLECTED); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_THROWN_HITS); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_THROWN_DMG_TOTAL); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_DMG_MAX); + } + else if ( items[entryType].category == SPELLBOOK ) + { + int spellID = getSpellIDFromSpellbook(entryType); + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() && spellID > SPELL_NONE ) + { + if ( find->second.spellbookId == entryType ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_HEALING) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_HEAL); + } + else if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_DMG); + } + else if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_TRACK_HITS) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_TARGETS); + } + } + } displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELLBOOK_CASTS); + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELLBOOK_CAST_DEGRADES); + } + else if ( items[entryType].category == MAGICSTAFF ) + { + for ( auto& s : ItemTooltips.spellItems ) + { + if ( s.second.magicstaffId == entryType ) + { + if ( !(s.second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_HEALING) + != s.second.spellTags.end() + || s.second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + != s.second.spellTags.end()) ) + { + // no damage or healing, skip dmg tag + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_SPELL_DMG ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + else + { + // damage or healing, skip zaps + for ( auto it = displayedEvents.begin(); it != displayedEvents.end(); ) + { + if ( *it == Compendium_t::EventTags::CPDM_MAGICSTAFF_CASTS ) + { + it = displayedEvents.erase(it); + } + else + { + ++it; + } + } + } + break; + } + } } } else if ( entryType >= Compendium_t::Events_t::kEventSpellOffset && entryType < Compendium_t::Events_t::kEventSpellOffset + 1000 ) { + int spellID = entryType - Compendium_t::Events_t::kEventSpellOffset; + auto find = ItemTooltips.spellItems.find(spellID); + if ( find != ItemTooltips.spellItems.end() && spellID > SPELL_NONE ) + { + if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_HEALING) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_HEAL); + } + else if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_DAMAGE) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_DMG); + } + else if ( find->second.spellTags.find(ItemTooltips_t::SpellTagTypes::SPELL_TAG_TRACK_HITS) + != find->second.spellTags.end() ) + { + displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_TARGETS); + } + } displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_CASTS); displayedEvents.push_back(Compendium_t::EventTags::CPDM_SPELL_FAILURES); } @@ -33079,6 +33348,11 @@ namespace MainMenu { { itemname = itemNameStrings[entryType + 2]; } + else if ( Compendium_t::Events_t::eventLangEntries[tag].find(compendium_contents_current[compendium_current]) + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = compendium_contents_current[compendium_current]; + } } else if ( entryType >= Compendium_t::Events_t::kEventMonsterOffset && entryType < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) @@ -33118,6 +33392,11 @@ namespace MainMenu { { itemname = ItemTooltips.spellItems[spellID].internalName; } + else if ( Compendium_t::Events_t::eventLangEntries[tag].find("spells") + != Compendium_t::Events_t::eventLangEntries[tag].end() ) + { + itemname = "spells"; + } } } @@ -33202,6 +33481,31 @@ namespace MainMenu { } } } + else if ( customTagKey == "CUSTOM_RACE_LEAST_GAMES_WON" ) + { + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_RACE_LEAST_GAMES_WON_CLASSIC", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + { + auto res2 = Compendium_t::Events_t::getCustomEventValue("CUSTOM_RACE_LEAST_GAMES_WON_HELL", compendium_current, + compendium_contents_current[compendium_current], specificClass); + for ( auto& r : res2 ) + { + if ( r.first != "-" ) + { + results.push_back(r); + } + } + } + } if ( results.size() == 0 ) { @@ -33218,6 +33522,71 @@ namespace MainMenu { % compendiumRecordsSectionLoadedValues[index].size()].c_str()); } } + else if ( entryType >= Compendium_t::Events_t::kEventMonsterOffset + && entryType < Compendium_t::Events_t::kEventMonsterOffset + 1000 + && tag == Compendium_t::CPDM_KILLED_SOLO ) + { + val->setText("-"); + std::vector> results; + int value = 0; + { + auto findTag = Compendium_t::Events_t::playerEvents.find(tag); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + auto find = findTag->second.find(entryType); + if ( find != findTag->second.end() ) + { + value += find->second.value; + } + } + } + { + auto findTag = Compendium_t::Events_t::playerEvents.find(Compendium_t::CPDM_KILLED_MULTIPLAYER); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + auto find = findTag->second.find(entryType); + if ( find != findTag->second.end() ) + { + if ( find != findTag->second.end() ) + { + value += find->second.value; + } + } + } + } + if ( value > 0 ) + { + std::string output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + // get monster plural + { + auto find = Compendium_t::Events_t::monsterIDToString.find(entryType); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + auto& def = CompendiumEntries.monsters[find->second]; + output += ' '; + auto monsterType = (Monster)def.monsterType; + if ( monsterType == LICH_FIRE || monsterType == LICH_ICE ) + { + monsterType = LICH; + } + std::string s = value > 1 ? getMonsterLocalizedPlural(monsterType) : getMonsterLocalizedName(monsterType); + camelCaseString(s); + output += s; + } + } + results.push_back(std::make_pair(value, output)); + } + if ( results.size() > 0 ) + { + compendiumRecordsSectionLoadedValues[index].clear(); + for ( auto& pair : results ) + { + compendiumRecordsSectionLoadedValues[index].push_back(pair.second); + } + val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence + % compendiumRecordsSectionLoadedValues[index].size()].c_str()); + } + } else if ( entryType >= Compendium_t::Events_t::kEventCodexOffset && entryType <= Compendium_t::Events_t::kEventCodexOffsetMax && Compendium_t::Events_t::eventClassIds.find(tag) != Compendium_t::Events_t::eventClassIds.end() ) @@ -33247,6 +33616,29 @@ namespace MainMenu { } } } + else if ( tag == Compendium_t::CPDM_RACE_GAMES_WON ) + { + { + auto res2 = getRecordEventValue(entryName, Compendium_t::CPDM_RACE_GAMES_WON_CLASSIC); + for ( auto& r : res2 ) + { + if ( r.second != "-" ) + { + results.push_back(r); + } + } + } + { + auto res2 = getRecordEventValue(entryName, Compendium_t::CPDM_RACE_GAMES_WON_HELL); + for ( auto& r : res2 ) + { + if ( r.second != "-" ) + { + results.push_back(r); + } + } + } + } if ( results.size() > 0 ) { compendiumRecordsSectionLoadedValues[index].clear(); @@ -33277,7 +33669,57 @@ namespace MainMenu { ++total; } } - value = total; + if ( tag == Compendium_t::EventTags::CPDM_LORE_PERCENT_READ ) + { + if ( find->first == SCROLL_MAIL ) + { + value = std::max(0, std::min(100, (int)(100.0 * total / (real_t)(NUM_SCROLL_MAIL_OPTIONS)))); + } + else if ( find->first == READABLE_BOOK ) + { + // check the other bitfield to sum up + auto findTag = Compendium_t::Events_t::playerEvents.find(Compendium_t::EventTags::CPDM_LORE_PERCENT_READ_2); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + auto find = findTag->second.find(entryType); + if ( find != findTag->second.end() ) + { + if ( find->second.type == Compendium_t::Events_t::BITFIELD ) + { + for ( int i = 0; i < 32; ++i ) + { + if ( find->second.value & (1 << i) ) + { + ++total; + } + } + } + } + } + + if ( numbooks > 0 ) + { + value = std::max(0, std::min(100, (int)(100.0 * total / (real_t)(numbooks)))); + } + else + { + value = std::min(total * 100, 100); + } + } + } + else if ( tag == Compendium_t::EventTags::CPDM_GRAVE_EPITAPHS_PERCENT ) + { + value = std::max(0, std::min(100, (int)(100.0 * total / (real_t)(17)))); + } + else + { + value = total; + } + } + else if ( (find->first == TOOL_MIRROR || find->first == MIRROR_SHIELD) + && tag == Compendium_t::EventTags::CPDM_BROKEN ) + { + value = find->second.value * 7; } else { @@ -33322,7 +33764,34 @@ namespace MainMenu { } else { - output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + if ( tag == Compendium_t::CPDM_RUNS_COLLECTED ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + auto findTag = Compendium_t::Events_t::playerEvents.find(Compendium_t::CPDM_MINEHEAD_ENTER); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + auto findCat = Compendium_t::Events_t::eventWorldIDLookup.find("minehead"); + if ( findCat != Compendium_t::Events_t::eventWorldIDLookup.end() ) + { + int worldId = findCat->second + Compendium_t::Events_t::kEventWorldOffset; + if ( findTag->second.find(worldId) != findTag->second.end() + && findTag->second[worldId].value > 0 ) + { + char buf[32]; + snprintf(buf, sizeof(buf), " (%.2f%%)", std::min(100.0, 100.0 * value / (real_t)findTag->second[worldId].value)); + output += buf; + } + } + } + } + else if ( entryType >= 0 && entryType < NUMITEMS ) + { + output = Compendium_t::Events_t::formatEventRecordText(value, compendium_contents_current[compendium_current].c_str(), entryType, Compendium_t::Events_t::eventLangEntries[tag]); + } + else + { + output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, Compendium_t::Events_t::eventLangEntries[tag]); + } val->setText(output.c_str()); } } @@ -33367,6 +33836,7 @@ namespace MainMenu { } static bool contents_activate_from_tab = true; + static bool contents_activate_from_filter = false; static void selectCompendiumItemInList(Frame& toSelect, Frame& page_right_inner, bool scrollTo, bool gamepadSelect) { if ( scrollTo ) @@ -33503,6 +33973,24 @@ namespace MainMenu { Compendium_t::compendiumItemModel.skill[10] = itemType; Compendium_t::compendiumItemModel.skill[14] = appearance; + auto find = Compendium_t::Events_t::itemIDToString.find(itemLookupIndex); + if ( find != Compendium_t::Events_t::itemIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumItems_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED + || unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + // section unlocked, reveal item in list + auto& itemUnlock = Compendium_t::CompendiumItems_t::itemUnlocks[itemLookupIndex]; + if ( itemUnlock == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN + || itemUnlock == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || itemUnlock == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + itemUnlock = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED; + } + } + } + if ( !isMouseVisible() && gamepadSelect ) { toSelect.select(); @@ -33515,7 +34003,7 @@ namespace MainMenu { refreshCompendiumCamera(modelsPath); // find records for this item - populateRecordsSectionItems(page_right_inner.getParent(), itemLookupIndex); + populateRecordsSectionItems(page_right_inner.getParent(), itemLookupIndex, "", itemLookupIndex); } } else @@ -33573,7 +34061,7 @@ namespace MainMenu { static ConsoleVariablecvar_compendium_reveal("/compendium_reveal", false); #endif static bool compendiumEntryControlEnabled = false; - static void refreshCompendiumEntryItemsList(std::string name, Frame* parent) + static void refreshCompendiumEntryItemsList(std::string name, Frame* parent, const bool gamepadClick) { if ( compendium_current == "items" ) { @@ -33591,10 +34079,48 @@ namespace MainMenu { } auto& compendiumEntry = (compendium_current == "items") ? CompendiumEntries.items[name] : CompendiumEntries.magic[name]; - Compendium_t::compendiumEntityCurrent.modelRNG++; + if ( !gamepadClick ) + { + Compendium_t::compendiumEntityCurrent.modelRNG++; + } + + if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) + { + if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + { + if ( compendiumEntry.lorePoints > 0 ) + { + unlock_lore_cost->setText(std::to_string(compendiumEntry.lorePoints).c_str()); + if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= compendiumEntry.lorePoints ) + { + unlock_lore_cost->setColor(compendiumContentsSelectedColor); + } + else + { + unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + } + } + else + { + unlock_lore_cost->setText("-"); + } + } + } if ( Frame* page_right = parent->findFrame("page_right") ) { + if ( auto page_right_unlock_cost = page_right->findField("lore_cost") ) + { + if ( compendiumEntry.lorePoints > 0 ) + { + page_right_unlock_cost->setText(std::to_string(compendiumEntry.lorePoints).c_str()); + } + else + { + page_right_unlock_cost->setText("-"); + } + } + if ( page_right = page_right->findFrame("page_right_inner") ) { for ( auto f : page_right->getFrames() ) @@ -33656,6 +34182,26 @@ namespace MainMenu { selector_bg->disabled = true; } + entry->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto frame = const_cast((Frame*)(&widget)); + if ( frame->isSelected() && !isMouseVisible() ) + { + if ( auto itemBg = frame->findImage("item bg") ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuConfirm", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = frame->getAbsoluteSize().x + itemBg->pos.x + itemBg->pos.w / 2 - 1; + int y = frame->getAbsoluteSize().y + itemBg->pos.y + itemBg->pos.h - 16; + image->draw(nullptr, SDL_Rect{ x - w / 2, y, w, h }, viewport); + } + } + }); entry->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); if ( isMouseVisible() ) @@ -33688,7 +34234,7 @@ namespace MainMenu { if ( parent = parent->getParent() ) { SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( (isMouseVisible() && frame->capturesMouseInRealtimeCoords()) + if ( (isMouseVisible() && frame->capturesMouse()) || frame->isSelected() ) { if ( isMouseVisible() ) @@ -33799,8 +34345,7 @@ namespace MainMenu { auto itemName = frame->findField("item name"); bool selectable = !(itemName && !strcmp(itemName->getText(), "???")); - if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") - && isMouseVisible() ) + if ( isMouseVisible() && Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeftClick") ) { if ( selectable ) { @@ -33818,6 +34363,10 @@ namespace MainMenu { { soundActivate(); selectCompendiumItemInList(*frame, *page_right_inner, false, true); + if ( !isMouseVisible() ) + { + compendiumItemTooltip.tooltipGamepadOpen = !compendiumItemTooltip.tooltipGamepadOpen; + } } else { @@ -33827,6 +34376,10 @@ namespace MainMenu { SDL_Rect pos = frame->getSize(); bool hovered = false; + if ( isMouseVisible() ) + { + compendiumItemTooltip.tooltipGamepadOpen = false; + } if ( selector_bg ) { selector_bg->disabled = false; @@ -33841,7 +34394,7 @@ namespace MainMenu { tmp.w = itemBg->pos.w; tmp.h = itemBg->pos.h; frame->setSize(tmp); - hovered = isMouseVisible() && frame->capturesMouseInRealtimeCoords(); + hovered = isMouseVisible() && frame->capturesMouse(); } frame->setSize(pos); @@ -33851,6 +34404,7 @@ namespace MainMenu { { page_right_inner->setAllowScrollBinds(true); } + hovered = compendiumItemTooltip.tooltipGamepadOpen; } if ( hovered && selectable ) @@ -33924,6 +34478,25 @@ namespace MainMenu { { itemDetail->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(192, 192, 192)); } + + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + itemDetail->setDisabled(true); + if ( auto notifTxt = entry->addField("item notif", 128) ) + { + notifTxt->setSize(itemName->getSize()); + notifTxt->setFont(itemName->getFont()); + std::string str = "\n"; + str += Language::get(6183); + notifTxt->setText(str.c_str()); + notifTxt->setColor(makeColorRGB(255, 255, 255)); + for ( int i = 0; i < 10; ++i ) + { + notifTxt->addWordToHighlight(i + Field::TEXT_HIGHLIGHT_WORDS_PER_LINE, makeColorRGB(30, 203, 177)); + } + } + } } } else @@ -33973,6 +34546,10 @@ namespace MainMenu { { Compendium_t::compendiumItem.appearance = (modelRNGCycle + Compendium_t::compendiumEntityCurrent.modelRNG) % 4; } + else if ( id == READABLE_BOOK ) + { + Compendium_t::compendiumItem.appearance = getBook("My Journal") % items[id].variations; + } if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) { @@ -34013,8 +34590,11 @@ namespace MainMenu { entry->setAllowScrollParent(true); entry->setScrollParentOffset(SDL_Rect{ 0, -16, 0, 16 }); entry->setMenuConfirmControlType(0); + entry->setHideGlyphs(true); + entry->setHideKeyboardGlyphs(true); entry->addWidgetAction("MenuPageLeft", "tab_left"); entry->addWidgetAction("MenuPageRight", "tab_right"); + //entry->addWidgetMovement("MenuAlt2", "nav_filter_sort"); entry->setWidgetBack("right_back_button"); } @@ -34172,7 +34752,7 @@ namespace MainMenu { if ( parent = parent->getParent() ) { SDL_Rect absolutePos = frame->getAbsoluteSize(); - if ( (isMouseVisible() && frame->capturesMouseInRealtimeCoords()) + if ( (isMouseVisible() && frame->capturesMouse()) || frame->isSelected() ) { if ( isMouseVisible() ) @@ -34297,7 +34877,7 @@ namespace MainMenu { tmp.w = itemBg->pos.w; tmp.h = itemBg->pos.h; frame->setSize(tmp); - hovered = isMouseVisible() && frame->capturesMouseInRealtimeCoords(); + hovered = isMouseVisible() && frame->capturesMouse(); } frame->setSize(pos); @@ -34419,6 +34999,7 @@ namespace MainMenu { entry->setMenuConfirmControlType(0); entry->addWidgetAction("MenuPageLeft", "tab_left"); entry->addWidgetAction("MenuPageRight", "tab_right"); + //entry->addWidgetMovement("MenuAlt2", "nav_filter_sort"); if ( isMouseVisible() ) { entry->setWidgetBack("back_button"); @@ -34445,7 +35026,7 @@ namespace MainMenu { } } - static void refreshCompendiumEntryCodex(std::string name, Frame* parent) + static void refreshCompendiumEntryCodex(std::string name, Frame* parent, const bool gamepadClick) { if ( CompendiumEntries.codex.find(name) == CompendiumEntries.codex.end() ) { @@ -34500,8 +35081,43 @@ namespace MainMenu { } } + if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) + { + if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); + if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) + { + unlock_lore_cost->setColor(compendiumContentsSelectedColor); + } + else + { + unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + } + } + else + { + unlock_lore_cost->setText("-"); + } + } + } + if ( Frame* page_right = parent->findFrame("page_right") ) { + if ( auto page_right_unlock_cost = page_right->findField("lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + page_right_unlock_cost->setText(std::to_string(entry.lorePoints).c_str()); + } + else + { + page_right_unlock_cost->setText("-"); + } + } + // find records for this item if ( name == "classes list" ) { @@ -34569,7 +35185,7 @@ namespace MainMenu { static void refreshCompendiumAchievements(std::string name, Frame* parent); - static void refreshCompendiumEntryMonster(std::string name, Frame* parent) + static void refreshCompendiumEntryMonster(std::string name, Frame* parent, const bool gamepadClick) { if ( CompendiumEntries.monsters.find(name) == CompendiumEntries.monsters.end() ) { @@ -34636,8 +35252,44 @@ namespace MainMenu { blurb->setText(txt.c_str()); } } + + if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) + { + if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); + if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) + { + unlock_lore_cost->setColor(compendiumContentsSelectedColor); + } + else + { + unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + } + } + else + { + unlock_lore_cost->setText("-"); + } + } + } + if ( Frame* page_right = parent->findFrame("page_right") ) { + if ( auto page_right_unlock_cost = page_right->findField("lore_cost") ) + { + if ( entry.lorePoints > 0 ) + { + page_right_unlock_cost->setText(std::to_string(entry.lorePoints).c_str()); + } + else + { + page_right_unlock_cost->setText("-"); + } + } + // find records for this item if ( Compendium_t::Events_t::monsterUniqueIDLookup.find(entry.unique_npc) != Compendium_t::Events_t::monsterUniqueIDLookup.end() ) @@ -34936,6 +35588,10 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { + if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + { + unlock_lore_cost->setDisabled(true); + } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) { reveal_txt->setText(""); @@ -34969,6 +35625,10 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { + if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + { + unlock_lore_cost->setDisabled(true); + } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) { reveal_txt->setText(""); @@ -35002,6 +35662,11 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { + if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + { + unlock_lore_cost->setDisabled(false); + unlock_lore_cost->setText(""); + } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) { reveal_txt->setText(to_unlock.c_str()); @@ -35024,7 +35689,6 @@ namespace MainMenu { } } - static std::string compendium_sorting = "default"; static auto contents_select_unknown_fn = [](Frame::entry_t& frameEntry) { if ( !contents_activate_from_tab && !isMouseVisible() ) @@ -35044,9 +35708,15 @@ namespace MainMenu { static ConsoleCommand ccmd_compendium_sorting( "/compendium_sorting", "", [](int argc, const char** argv) { - compendium_sorting = compendium_sorting == "default" ? "alphabetical" : "default"; + Compendium_t::compendium_sorting = Compendium_t::compendium_sorting == "default" ? "alphabetical" : "default"; }); + std::string sorting = Compendium_t::compendium_sorting; + if ( Compendium_t::compendium_sorting_hide_undiscovered ) + { + sorting += "_discovered"; + } + assert(main_menu_frame); if ( !main_menu_frame ) { return; } auto compendiumFrame = main_menu_frame->findFrame("compendium"); @@ -35085,12 +35755,12 @@ namespace MainMenu { } } - auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[compendium_sorting] - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] - : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[compendium_sorting] + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[sorting] + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[sorting] + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[sorting] + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[sorting] + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[sorting] + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[sorting] : nullptr))))); auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks @@ -35348,21 +36018,21 @@ namespace MainMenu { if ( compendium_current == "codex" ) { - refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], compendiumFrame, gamepadClick); } else if ( compendium_current == "items" ) { refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], compendiumFrame); - refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame, gamepadClick); } else if ( compendium_current == "magic" ) { refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], compendiumFrame); - refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], compendiumFrame, gamepadClick); } else if ( compendium_current == "world" ) { - refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], compendiumFrame, gamepadClick); } else if ( compendium_current == "monsters" ) { @@ -35378,7 +36048,7 @@ namespace MainMenu { monsterAnimate(compendiumMonster, myStats, 0.0); } } - refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], compendiumFrame); + refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], compendiumFrame, gamepadClick); } else if ( compendium_current == "achievements" ) { @@ -35430,12 +36100,12 @@ namespace MainMenu { if ( auto contents = frame->findFrame("contents") ) { - auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[compendium_sorting] - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[compendium_sorting] - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[compendium_sorting] - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents[compendium_sorting] - : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents[compendium_sorting] - : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[compendium_sorting] + auto* entriesContents = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::contents + : (compendium_current == "magic" ? &Compendium_t::CompendiumMagic_t::contents + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents : nullptr))))); auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks @@ -35446,6 +36116,32 @@ namespace MainMenu { : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::unlocks : nullptr))))); + std::string sorting = Compendium_t::compendium_sorting; + if ( Compendium_t::compendium_sorting_hide_undiscovered && entriesContents && unlockStatus ) + { + sorting += "_discovered"; + auto& revealedEntries = (*entriesContents)[sorting]; + revealedEntries.clear(); + for ( auto& pair : (*entriesContents)[Compendium_t::compendium_sorting] ) + { + if ( pair.first == "-" ) + { + revealedEntries.push_back(pair); + continue; + } + auto find = unlockStatus->find(pair.first); + if ( find != unlockStatus->end() ) + { + if ( find->second != Compendium_t::LOCKED_UNKNOWN ) + { + revealedEntries.push_back(pair); + } + } + } + } + + auto* entries = entriesContents ? &(*entriesContents)[sorting] : nullptr; + auto toRemove = contents->getEntries(); for ( auto r : toRemove ) { @@ -35899,10 +36595,6 @@ namespace MainMenu { { auto& achName = page[i]; auto& achData = Compendium_t::achievements[achName]; - if ( keystatus[SDLK_g] ) - { - achData.unlocked = true; - } bool hiddenGroup = !achData.unlocked && achData.hidden; @@ -36203,19 +36895,37 @@ namespace MainMenu { frame->removeSelf(); } + if ( auto btn = window->findButton("achievements_page_prev") ) + { + btn->removeSelf(); + } + if ( auto btn = window->findButton("achievements_page_next") ) + { + btn->removeSelf(); + } + if ( auto frame = window->findFrame("page_left") ) { - frame->removeSelf(); + frame->setDisabled(true); } if ( auto frame = window->findFrame("page_right") ) { - frame->removeSelf(); + frame->setDisabled(true); } if ( auto frame = window->findFrame("page_right_unlock") ) { - frame->removeSelf(); + frame->setDisabled(true); + } + + if ( auto page_left_title = window->findField("page_left_title") ) + { + page_left_title->setDisabled(true); + } + if ( auto page_right_title = window->findField("page_right_title") ) + { + page_right_title->setDisabled(true); } auto background = window->findImage("background"); @@ -36224,34 +36934,148 @@ namespace MainMenu { auto frame = window->addFrame("achievements"); frame->setSize(SDL_Rect{ background->pos.x + 32, background->pos.y, 884, 536 }); - auto page_prev = window->addButton("page_prev"); - page_prev->setColor(makeColorRGB(128, 128, 128)); - page_prev->setSize(SDL_Rect{ background->pos.x, background->pos.y + background->pos.h / 2, 32, 32}); + auto page_prev = window->addButton("achievements_page_prev"); + page_prev->setSize(SDL_Rect{ background->pos.x + 6, background->pos.y + background->pos.h / 2 - 51, 38, 58}); + page_prev->setColor(makeColor(255, 255, 255, 255)); + page_prev->setHighlightColor(makeColor(255, 255, 255, 255)); + page_prev->setBackground("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrow_00.png"); + page_prev->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrowHigh_00.png"); + page_prev->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrowPress_00.png"); + page_prev->setOntop(true); page_prev->setCallback([](Button& button) { if ( compendium_current == "achievements" ) { auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[compendium_contents_current[compendium_current]]; achDisplay.currentPage = std::max(0, std::min(achDisplay.currentPage, (int)achDisplay.pages.size() - 1)); - if ( achDisplay.currentPage - 1 >= 0 ) + if ( achDisplay.currentPage == 0 ) { - achDisplay.currentPage--; - refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + soundError(); + } + else + { + if ( !isMouseVisible() ) + { + soundMove(); + playSound(83 + local_rng.rand() % 6, 64); + } + else + { + soundMove(); + playSound(83 + local_rng.rand() % 6, 64); + } + if ( achDisplay.currentPage - 1 >= 0 ) + { + achDisplay.currentPage--; + refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + } + } + } + }); + page_prev->setWidgetLeft("contents"); + page_prev->setWidgetRight("contents"); + page_prev->setWidgetUp("contents"); + page_prev->setWidgetDown("contents"); + page_prev->setWidgetBack("back_button"); + page_prev->setWidgetSearchParent("compendium"); + page_prev->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto button = const_cast((Button*)(&widget)); + auto frame = static_cast(button->getParent()); + if ( auto contents = frame->findFrame("contents") ) + { + if ( contents->isSelected() ) + { + auto& actions = contents->getWidgetActions(); + auto find = actions.find("MenuLeft"); + if ( find != actions.end() && find->second == widget.getName() ) + { + if ( !isMouseVisible() ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuLeft", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = button->getSize().x + button->getSize().w / 2; + int y = button->getSize().y + button->getSize().h; + image->draw(nullptr, SDL_Rect{ x - w / 2, y, w, h }, viewport); + } + } } } }); - auto page_next = window->addButton("page_next"); - page_next->setColor(makeColorRGB(128, 128, 128)); - page_next->setSize(SDL_Rect{ background->pos.x + background->pos.w - 32, background->pos.y + background->pos.h / 2, 32, 32 }); + auto page_next = window->addButton("achievements_page_next"); + page_next->setSize(SDL_Rect{ background->pos.x + background->pos.w - 32 - 12, background->pos.y + background->pos.h / 2 - 51, 38, 58 }); + page_next->setColor(makeColor(255, 255, 255, 255)); + page_next->setHighlightColor(makeColor(255, 255, 255, 255)); + page_next->setBackground("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrow_00.png"); + page_next->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrowHigh_00.png"); + page_next->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrowPress_00.png"); + page_next->setOntop(true); page_next->setCallback([](Button& button) { if ( compendium_current == "achievements" ) { auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[compendium_contents_current[compendium_current]]; achDisplay.currentPage = std::max(0, std::min(achDisplay.currentPage, (int)achDisplay.pages.size() - 1)); - if ( achDisplay.currentPage + 1 < achDisplay.pages.size() ) + if ( achDisplay.currentPage == achDisplay.pages.size() - 1 ) { - achDisplay.currentPage++; - refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + soundError(); + } + else + { + if ( !isMouseVisible() ) + { + soundMove(); + playSound(83 + local_rng.rand() % 6, 64); + } + else + { + soundMove(); + playSound(83 + local_rng.rand() % 6, 64); + } + if ( achDisplay.currentPage + 1 < achDisplay.pages.size() ) + { + achDisplay.currentPage++; + refreshCompendiumAchievements(compendium_contents_current[compendium_current], static_cast(button.getParent())); + } + } + } + }); + page_next->setWidgetLeft("contents"); + page_next->setWidgetRight("contents"); + page_next->setWidgetUp("contents"); + page_next->setWidgetDown("contents"); + page_next->setWidgetBack("back_button"); + page_next->setWidgetSearchParent("compendium"); + page_next->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto button = const_cast((Button*)(&widget)); + auto frame = static_cast(button->getParent()); + if ( auto contents = frame->findFrame("contents") ) + { + if ( contents->isSelected() ) + { + auto& actions = contents->getWidgetActions(); + auto find = actions.find("MenuRight"); + if ( find != actions.end() && find->second == widget.getName() ) + { + if ( !isMouseVisible() ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuRight", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = button->getSize().x + button->getSize().w / 2; + int y = button->getSize().y + button->getSize().h; + image->draw(nullptr, SDL_Rect{ x - w / 2, y, w, h }, viewport); + } + } } } }); @@ -36352,8 +37176,47 @@ namespace MainMenu { { if ( !page_right ) { return; } + if ( compendium_current == "achievements" ) + { + return; + } + compendiumPageRightHideUnlocked(page_right, false, true); + if ( Frame* parent = static_cast(page_right->getParent()) ) + { + if ( auto achievements = parent->findFrame("achievements") ) + { + achievements->removeSelf(); + } + if ( auto page_left = parent->findFrame("page_left") ) + { + page_left->setDisabled(false); + } + if ( auto page_right_unlock = parent->findFrame("page_right_unlock") ) + { + page_right_unlock->setDisabled(false); + } + if ( auto page_left_title = parent->findField("page_left_title") ) + { + page_left_title->setDisabled(false); + } + if ( auto page_right_title = parent->findField("page_right_title") ) + { + page_right_title->setDisabled(false); + } + if ( auto btn = parent->findButton("achievements_page_prev") ) + { + btn->removeSelf(); + } + if ( auto btn = parent->findButton("achievements_page_next") ) + { + btn->removeSelf(); + } + } + + page_right->setDisabled(false); + Frame* page_right_inner = page_right->findFrame("page_right_inner"); if ( !page_right_inner ) { @@ -36379,7 +37242,7 @@ namespace MainMenu { auto heading = page_right_overlay->addField("records txt", 32); heading->setFont(menu_option_font); - heading->setText("RECORDS"); + heading->setText(Language::get(6185)); heading->setHJustify(Field::justify_t::LEFT); heading->setVJustify(Field::justify_t::TOP); heading->setSize(SDL_Rect{ 14, 12, page_right_overlay->getSize().w, 28 }); @@ -36867,17 +37730,17 @@ namespace MainMenu { if ( compendium_current == "monsters" ) { CompendiumEntries.readMonstersFromFile(); - refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "world" ) { CompendiumEntries.readWorldFromFile(); - refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "codex" ) { CompendiumEntries.readCodexFromFile(); - refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); + refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "items" ) { @@ -36914,49 +37777,126 @@ namespace MainMenu { parent = parent->getParent(); if ( !parent ) { return; } - Frame* page_right = nullptr; - if ( page_right = parent->findFrame("page_right") ) - { - page_right->setOpacity(0.0); - page_right->setInvisible(true); - } - if ( auto bg = parent->findImage("page right img") ) - { - bg->disabled = true; - } - bool unlockFound = false; + bool success = false; if ( auto page_right_unlock = parent->findFrame("page_right_unlock") ) { if ( auto to_unlock = page_right_unlock->findField("to_unlock") ) { if ( compendium_contents_current[compendium_current] == to_unlock->getText() ) { - to_unlock->setText(""); - auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks - : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); + int lorePointCost = 0; + bool foundLorePointCost = false; + if ( compendium_current == "monsters" ) + { + auto find = CompendiumEntries.monsters.find(to_unlock->getText()); + if ( find != CompendiumEntries.monsters.end() ) + { + lorePointCost = find->second.lorePoints; + foundLorePointCost = true; + } + } + else if ( compendium_current == "items" ) + { + auto find = CompendiumEntries.items.find(to_unlock->getText()); + if ( find != CompendiumEntries.items.end() ) + { + lorePointCost = find->second.lorePoints; + foundLorePointCost = true; + } + } + else if ( compendium_current == "magic" ) + { + auto find = CompendiumEntries.magic.find(to_unlock->getText()); + if ( find != CompendiumEntries.magic.end() ) + { + lorePointCost = find->second.lorePoints; + foundLorePointCost = true; + } + } + else if ( compendium_current == "world" ) + { + auto find = CompendiumEntries.worldObjects.find(to_unlock->getText()); + if ( find != CompendiumEntries.worldObjects.end() ) + { + lorePointCost = find->second.lorePoints; + foundLorePointCost = true; + } + } + else if ( compendium_current == "codex" ) + { + auto find = CompendiumEntries.codex.find(to_unlock->getText()); + if ( find != CompendiumEntries.codex.end() ) + { + lorePointCost = find->second.lorePoints; + foundLorePointCost = true; + } + } - if ( unlockStatus ) + if ( foundLorePointCost + && ((Compendium_t::lorePointsSpent + lorePointCost) <= Compendium_t::lorePointsFromAchievements) ) { - auto findUnlock = unlockStatus->find(compendium_contents_current[compendium_current]); - unlockFound = true; - if ( findUnlock != unlockStatus->end() ) + success = true; + playSound(632 + local_rng.rand() % 2, 92); + to_unlock->setText(""); + auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks : nullptr)))); + + if ( unlockStatus ) { - if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) - { - findUnlock->second = Compendium_t::UNLOCKED_UNVISITED; - } - else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_VISITED ) + auto findUnlock = unlockStatus->find(compendium_contents_current[compendium_current]); + if ( findUnlock != unlockStatus->end() ) { - findUnlock->second = Compendium_t::UNLOCKED_VISITED; + unlockFound = true; + if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + findUnlock->second = Compendium_t::UNLOCKED_UNVISITED; + } + else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_VISITED ) + { + findUnlock->second = Compendium_t::UNLOCKED_VISITED; + } + if ( compendium_current == "items" || compendium_current == "magic" ) + { + refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], parent, true); + } } } + Compendium_t::updateLorePointCounts(); } } } + + if ( !success ) + { + soundError(); + return; + } + + if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + { + unlock_lore_cost->setDisabled(true); + } + } + + if ( !success ) + { + soundError(); + return; + } + + Frame* page_right = nullptr; + if ( page_right = parent->findFrame("page_right") ) + { + page_right->setOpacity(0.0); + page_right->setInvisible(true); + } + if ( auto bg = parent->findImage("page right img") ) + { + bg->disabled = true; } if ( unlockFound && page_right ) @@ -37063,22 +38003,27 @@ namespace MainMenu { { animTicks = 0; compendiumRevealAnimState = 1; - bool selected = frame->isSelected(); frame->removeSelf(); } } }); } + + button->setInvisible(true); + button->setDisabled(true); } static void openCompendium() { contents_activate_from_tab = true; - players[0]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; + players[getMenuOwner()]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; + compendiumItemTooltip.clear(); + + Compendium_t::updateLorePointCounts(); auto dimmer = main_menu_frame->addFrame("dimmer"); dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); dimmer->setActualSize(dimmer->getSize()); - dimmer->setColor(makeColor(0, 0, 0, 63)); + dimmer->setColor(makeColor(0, 0, 0, 191)); dimmer->setBorder(0); static ConsoleVariable cvar_compendium_book_x("/compendium_book_x", 119); @@ -37101,6 +38046,115 @@ namespace MainMenu { itemTooltipDisplay.scrolledToMax = 0; } + { + const int offset_y = 14; + auto lore_points = window->addFrame("lore_points_balance"); + lore_points->setHollow(true); + lore_points->setClickable(false); + lore_points->setSize(SDL_Rect{ window->getSize().w - 604, 0, 194, 50 + offset_y }); + lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); + lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); + Field* txt = lore_points->addField("lore_points_current", 32); + txt->setText(""); + txt->setFont("fonts/kongtext.ttf#16#2"); + txt->setColor(makeColorRGB(158, 146, 132)); + txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); + txt->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + txt->setText("..."); + } + else + { + int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent); + txt->setText(std::to_string(lorePointsCurrent).c_str()); + } + }); + + txt = lore_points->addField("lore_points_label", 64); + txt->setText("POINTS AVAILABLE"); + txt->setFont(smallfont_outline); + txt->setColor(makeColorRGB(192, 192, 192)); + txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); + } + + { + const int offset_y = 14; + auto lore_points = window->addFrame("lore_points_total"); + lore_points->setHollow(true); + lore_points->setClickable(false); + lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); + lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); + lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); + Field* txt = lore_points->addField("lore_points_current", 32); + txt->setText(""); + txt->setFont("fonts/kongtext.ttf#16#2"); + txt->setColor(makeColorRGB(158, 146, 132)); + txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); + txt->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + txt->setText("..."); + } + else + { + int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements); + txt->setText(std::to_string(lorePointsCurrent).c_str()); + } + }); + + txt = lore_points->addField("lore_points_total", 32); + txt->setText("/"); + txt->setFont("fonts/kongtext.ttf#16#2"); + txt->setColor(makeColorRGB(255, 255, 255)); + txt->setSize(SDL_Rect{ 100, 16 + offset_y, 94, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::LEFT); + txt->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + txt->setText("..."); + } + else + { + std::string amt = "/" + std::to_string(Compendium_t::lorePointsAchievementsTotal); + txt->setText(amt.c_str()); + } + }); + + txt = lore_points->addField("lore_points_label", 64); + txt->setText("LORE POINTS EARNED"); + txt->setFont(smallfont_outline); + txt->setColor(makeColorRGB(192, 192, 192)); + txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24}); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); + } + + { + auto completion = window->addFrame("completion"); + completion->setHollow(true); + completion->setClickable(false); + completion->setSize(SDL_Rect{ window->getSize().w - 200, 8, 194, 50 }); + completion->addImage(SDL_Rect{ 0, 0, 194, 50 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "completion_bg"); + completion->addImage(SDL_Rect{ 10, 10, 38, 28 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_Icon_00.png", "completion_icon"); + } + (void)createBackWidget(window, [](Button& button) { soundCancel(); @@ -37119,7 +38173,7 @@ namespace MainMenu { auto background = window->addImage( SDL_Rect{ *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, - (Frame::virtualScreenY - 598) / 2, + (Frame::virtualScreenY - 598) / 2 + 1, 958, 598 }, 0xffffffff, @@ -37145,6 +38199,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); @@ -37163,10 +38218,25 @@ namespace MainMenu { { Button* tab_left = window->addButton("tab_left"); SDL_Rect pos = tab->getSize(); - pos.x -= (pos.w + 8); - pos.w = 0; - pos.h = 0; + pos.x -= (8 + 38 + 26); + pos.w = 38; + pos.h = 58; tab_left->setSize(pos); + tab_left->setColor(makeColor(255, 255, 255, 255)); + tab_left->setHighlightColor(makeColor(255, 255, 255, 255)); + tab_left->setGlyphPosition(Widget::CENTERED_BOTTOM); + tab_left->setBackground("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrow_00.png"); + tab_left->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrowHigh_00.png"); + tab_left->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/AA_Button_LArrowPress_00.png"); + tab_left->setWidgetBack("back_button"); + tab_left->setWidgetLeft("contents"); + tab_left->setWidgetRight(compendiumCategories[0].c_str()); + tab_left->setWidgetUp("contents"); + tab_left->setWidgetDown("contents"); + tab_left->setWidgetSearchParent("compendium"); + tab_left->addWidgetAction("MenuPageLeft", "tab_left"); + tab_left->addWidgetAction("MenuPageRight", "tab_right"); + tab_left->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab_left->setCallback([](Button& button) { for ( int i = 0; i < compendiumCategories.size(); ++i ) { @@ -37181,6 +38251,13 @@ namespace MainMenu { tab->getCallback()(*tab); } } + else + { + /*if ( auto tab = parent->findButton(compendiumCategories[i].c_str()) ) + { + tab->getCallback()(*tab); + }*/ + } } break; } @@ -37188,7 +38265,7 @@ namespace MainMenu { }); } - const int tab_title_y = 4; + const int tab_title_y = 3; Field* tab_title = window->addField(tab->getName(), 32); tab_title->setText(Language::get(6174)); tab_title->setFont(smallfont_outline); @@ -37207,11 +38284,20 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6174); + str += '\n'; + str += std::to_string(Compendium_t::CompendiumMonsters_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { - soundActivate(); + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "monsters"; if ( auto frame = static_cast(button.getParent()) ) { @@ -37244,6 +38330,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); @@ -37258,6 +38345,34 @@ namespace MainMenu { button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsInactiveHi_00.png"); } }); + //tab->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + // if ( !main_menu_frame ) { + // return; + // } + // auto selectedWidget = main_menu_frame->findSelectedWidget(widget.getOwner()); + // if ( selectedWidget) + // { + // auto actions = selectedWidget->getWidgetActions(); + // if ( actions.find("MenuPageLeft") != actions.end() && actions["MenuPageLeft"] == "tab_left" ) + // { + // if ( !isMouseVisible() ) + // { + // // draw glyphs + // bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + // Input& input = Input::inputs[widget.getOwner()]; + // auto path = input.getGlyphPathForBinding("MenuPageLeft", pressed); + // auto image = Image::get((std::string("*") + path).c_str()); + // int w = image->getWidth(); + // int h = image->getHeight(); + // const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + // auto button = const_cast((Button*)(&widget)); + // int x = button->getSize().x; + // int y = button->getSize().y; + // image->draw(nullptr, SDL_Rect{ x - w - 8, y, w, h }, viewport); + // } + // } + // } + //}); tab_title = window->addField(tab->getName(), 32); tab_title->setText(Language::get(6175)); @@ -37277,11 +38392,20 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6175); + str += '\n'; + str += std::to_string(Compendium_t::CompendiumItems_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { - soundActivate(); + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "items"; if ( auto frame = static_cast(button.getParent()) ) { @@ -37313,6 +38437,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); @@ -37346,11 +38471,20 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6176); + str += '\n'; + str += std::to_string(Compendium_t::CompendiumMagic_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { - soundActivate(); + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "magic"; if ( auto frame = static_cast(button.getParent()) ) { @@ -37382,6 +38516,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); @@ -37415,12 +38550,21 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6177); + str += '\n'; + str += std::to_string(Compendium_t::CompendiumWorld_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "world"; - soundActivate(); if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -37451,6 +38595,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { @@ -37485,12 +38630,21 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6178); + str += '\n'; + str += std::to_string(Compendium_t::CompendiumCodex_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "codex"; - soundActivate(); if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -37520,6 +38674,7 @@ namespace MainMenu { tab->setWidgetSearchParent("compendium"); tab->addWidgetAction("MenuPageLeft", "tab_left"); tab->addWidgetAction("MenuPageRight", "tab_right"); + tab->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); tab->setTickCallback([](Widget& widget) { @@ -37539,10 +38694,25 @@ namespace MainMenu { { Button* tab_right = window->addButton("tab_right"); SDL_Rect pos = tab->getSize(); - pos.x += (pos.w + 8); - pos.w = 0; - pos.h = 0; + pos.x += (pos.w + 8 + 12); + pos.w = 38; + pos.h = 58; tab_right->setSize(pos); + tab_right->setColor(makeColor(255, 255, 255, 255)); + tab_right->setHighlightColor(makeColor(255, 255, 255, 255)); + tab_right->setBackground("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrow_00.png"); + tab_right->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrowHigh_00.png"); + tab_right->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/AA_Button_RArrowPress_00.png"); + tab_right->setGlyphPosition(Widget::CENTERED_BOTTOM); + tab_right->setWidgetBack("back_button"); + tab_right->setWidgetRight("contents"); + tab_right->setWidgetLeft(compendiumCategories[5].c_str()); + tab_right->setWidgetUp("contents"); + tab_right->setWidgetDown("contents"); + tab_right->setWidgetSearchParent("compendium"); + tab_right->addWidgetAction("MenuPageLeft", "tab_left"); + tab_right->addWidgetAction("MenuPageRight", "tab_right"); + tab_right->addWidgetMovement("MenuAlt2", "nav_filter_sort"); tab_right->setCallback([](Button& button) { for ( int i = 0; i < compendiumCategories.size(); ++i ) { @@ -37557,6 +38727,13 @@ namespace MainMenu { tab->getCallback()(*tab); } } + else + { + /*if ( auto tab = parent->findButton(compendiumCategories[i].c_str()) ) + { + tab->getCallback()(*tab); + }*/ + } } break; } @@ -37582,12 +38759,21 @@ namespace MainMenu { { field->setColor(tabTextColorInactive); } + std::string str = Language::get(6184); + str += '\n'; + str += std::to_string(Compendium_t::AchievementData_t::completionPercent); + str += '%'; + field->setText(str.c_str()); }); tab->getTickCallback()(*tab); tab->setCallback([](Button& button) { + if ( !contents_activate_from_filter ) + { + soundActivate(); + } + contents_activate_from_filter = false; compendium_current = "achievements"; - soundActivate(); if ( auto frame = static_cast(button.getParent()) ) { if ( auto page_right = frame->findFrame("page_right") ) @@ -37604,10 +38790,38 @@ namespace MainMenu { contents_activate_from_tab = false; } }); + //tab->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + // if ( !main_menu_frame ) { + // return; + // } + // auto selectedWidget = main_menu_frame->findSelectedWidget(widget.getOwner()); + // if ( selectedWidget ) + // { + // auto actions = selectedWidget->getWidgetActions(); + // if ( actions.find("MenuPageRight") != actions.end() && actions["MenuPageRight"] == "tab_right" ) + // { + // if ( !isMouseVisible() ) + // { + // // draw glyphs + // bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + // Input& input = Input::inputs[widget.getOwner()]; + // auto path = input.getGlyphPathForBinding("MenuPageRight", pressed); + // auto image = Image::get((std::string("*") + path).c_str()); + // int w = image->getWidth(); + // int h = image->getHeight(); + // const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + // auto button = const_cast((Button*)(&widget)); + // int x = button->getSize().x + button->getSize().w; + // int y = button->getSize().y; + // image->draw(nullptr, SDL_Rect{ x + 8, y, w, h }, viewport); + // } + // } + // } + //}); } auto navigation = window->addFrame("nav"); - navigation->setSize(SDL_Rect{ background->pos.x - 244 - 2, background->pos.y + 50, 244, 528 }); + navigation->setSize(SDL_Rect{ background->pos.x - 244 - 2, background->pos.y + 50 - 44, 244, 594 }); auto nav_bg = navigation->addImage( SDL_Rect{ 0, 0, 244, 528 }, 0xFFFFFFFF, @@ -37620,9 +38834,16 @@ namespace MainMenu { nav_title->setText(Language::get(6182)); nav_title->setHJustify(Field::justify_t::CENTER); nav_title->setVJustify(Field::justify_t::TOP); - nav_title->setSize(SDL_Rect{ 0, 12, navigation->getSize().w, 28 }); + nav_title->setSize(SDL_Rect{ 0, 12, navigation->getSize().w - 16, 28 }); nav_title->setColor(makeColor(42, 22, 18, 255)); + /*auto nav_title_percent = navigation->addField("nav_title_percent", 64); + nav_title_percent->setFont(smallfont_outline); + nav_title_percent->setText("100%"); + nav_title_percent->setHJustify(Field::justify_t::RIGHT); + nav_title_percent->setVJustify(Field::justify_t::TOP); + nav_title_percent->setSize(SDL_Rect{ 0, 9, navigation->getSize().w - 16, 28 });*/ + auto nav_contents_bg = navigation->addImage( SDL_Rect{8, 32, 228, 464}, 0xFFFFFFFF, @@ -37630,6 +38851,217 @@ namespace MainMenu { "nav_contents_bg" ); + { + auto nav_filters = navigation->addFrame("nav_filters"); + nav_filters->setSize(SDL_Rect{ 8, navigation->getSize().h - 86, 232, 86 }); + auto nav_filter_bg = nav_filters->addImage( + SDL_Rect{ 0, 0, 232, 86 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Filter_BG_00.png", + "nav_filter_bg" + ); + nav_filters->setDrawCallback([](const Widget& widget, SDL_Rect pos){ + auto frame = const_cast((Frame*)(&widget)); + if ( main_menu_frame ) + { + if ( auto selectedWidget = main_menu_frame->findSelectedWidget(getMenuOwner()) ) + { + auto& actions = selectedWidget->getWidgetMovements(); + auto find = actions.find("MenuAlt2"); + if ( find != actions.end() && find->second == "nav_filter_sort" ) + { + if ( !isMouseVisible() ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuAlt2", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = frame->getAbsoluteSize().x - 2 + frame->getSize().w / 2; + int y = frame->getAbsoluteSize().y + frame->getSize().h - 12; + image->draw(nullptr, SDL_Rect{ x - w / 2, y, w, h }, viewport); + } + } + } + } + }); + + auto nav_filter_sort = nav_filters->addButton("nav_filter_sort"); + nav_filter_sort->setSize(SDL_Rect{ 16, 12, 28, 24 }); + nav_filter_sort->setIcon("*#images/ui/Main Menus/Play/LobbyBrowser/Lobby_Checkbox_PickSmall00.png"); + nav_filter_sort->setStyle(Button::style_t::STYLE_CHECKBOX); + nav_filter_sort->setHighlightColor(0); + nav_filter_sort->setBorderColor(0); + nav_filter_sort->setBorder(0); + nav_filter_sort->setColor(0); + nav_filter_sort->setSelectorOffset(SDL_Rect{ 0, 2, -6, 0 }); + nav_filter_sort->setBackground(""); + nav_filter_sort->setPressed(Compendium_t::compendium_sorting != "default"); + nav_filter_sort->setWidgetSearchParent("compendium"); + nav_filter_sort->setWidgetDown("nav_filter_sort2"); + nav_filter_sort->addWidgetAction("MenuPageLeft", "tab_left"); + nav_filter_sort->addWidgetAction("MenuPageRight", "tab_right"); + nav_filter_sort->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( button->isSelected() ) + { + if ( !isMouseVisible() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuCancel") ) + { + Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuListCancel"); + if ( auto parent = static_cast(button->getParent()) ) + { + if ( parent = parent->getParent() ) + { + if ( parent = parent->getParent() ) + { + if ( auto btn = parent->findButton(compendium_current.c_str()) ) + { + soundCancel(); + contents_activate_from_tab = true; + contents_activate_from_filter = true; + btn->getCallback()(*btn); + contents_activate_from_tab = false; + contents_activate_from_filter = false; + } + } + } + } + } + } + } + }); + nav_filter_sort->setCallback([](Button& button) + { + soundCheckmark(); + if ( compendium_current == "achievements" ) + { + Compendium_t::AchievementData_t::achievementsNeedResort = true; + } + Compendium_t::AchievementData_t::sortAlphabetical = button.isPressed(); + Compendium_t::compendium_sorting = button.isPressed() ? "alphabetical" : "default"; + if ( auto parent = static_cast(button.getParent()) ) + { + if ( parent = parent->getParent() ) + { + if ( parent = parent->getParent() ) + { + if ( auto btn = parent->findButton(compendium_current.c_str()) ) + { + contents_activate_from_tab = true; + contents_activate_from_filter = true; + btn->getCallback()(*btn); + contents_activate_from_tab = false; + contents_activate_from_filter = false; + button.select(); + } + } + } + } + }); + + //auto nav_filter_sort2 = nav_filters->addButton("nav_filter_sort"); + //nav_filter_sort2->setSize(SDL_Rect{ 28, 16 + 24 + 8, 44, 44 }); + //nav_filter_sort2->setIcon("*#images/ui/Main Menus/Mods/Upload/Fill_Checked_00.png"); + //nav_filter_sort2->setStyle(Button::style_t::STYLE_CHECKBOX); + //nav_filter_sort2->setHighlightColor(0); + //nav_filter_sort2->setBorderColor(0); + //nav_filter_sort2->setBorder(0); + //nav_filter_sort2->setColor(0); + ////nav_filter_sort2->setSelectorOffset(SDL_Rect{ 0, 2, -6, 0 }); + //nav_filter_sort2->setBackground(""); + + auto nav_filter_sort2 = nav_filters->addButton("nav_filter_sort2"); + nav_filter_sort2->setSize(SDL_Rect{ 16, 16 + 24 + 8, 28, 24 }); + nav_filter_sort2->setIcon("*#images/ui/Main Menus/Play/LobbyBrowser/Lobby_Checkbox_PickSmall00.png"); + nav_filter_sort2->setStyle(Button::style_t::STYLE_CHECKBOX); + nav_filter_sort2->setHighlightColor(0); + nav_filter_sort2->setBorderColor(0); + nav_filter_sort2->setBorder(0); + nav_filter_sort2->setColor(0); + nav_filter_sort2->setSelectorOffset(SDL_Rect{ 0, 2, -6, 0 }); + nav_filter_sort2->setBackground(""); + nav_filter_sort2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + nav_filter_sort2->setWidgetSearchParent("compendium"); + nav_filter_sort2->setWidgetUp("nav_filter_sort"); + nav_filter_sort2->addWidgetAction("MenuPageLeft", "tab_left"); + nav_filter_sort2->addWidgetAction("MenuPageRight", "tab_right"); + nav_filter_sort2->setTickCallback([](Widget& widget) { + auto button = static_cast(&widget); + if ( button->isSelected() ) + { + if ( !isMouseVisible() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuCancel") ) + { + Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuListCancel"); + if ( auto parent = static_cast(button->getParent()) ) + { + if ( parent = parent->getParent() ) + { + if ( parent = parent->getParent() ) + { + if ( auto btn = parent->findButton(compendium_current.c_str()) ) + { + soundCancel(); + contents_activate_from_tab = true; + contents_activate_from_filter = true; + btn->getCallback()(*btn); + contents_activate_from_tab = false; + contents_activate_from_filter = false; + } + } + } + } + } + } + } + }); + nav_filter_sort2->setCallback([](Button& button) + { + soundCheckmark(); + Compendium_t::compendium_sorting_hide_undiscovered = button.isPressed(); + if ( auto parent = static_cast(button.getParent()) ) + { + if ( parent = parent->getParent() ) + { + if ( parent = parent->getParent() ) + { + if ( auto btn = parent->findButton(compendium_current.c_str()) ) + { + contents_activate_from_tab = true; + contents_activate_from_filter = true; + btn->getCallback()(*btn); + contents_activate_from_tab = false; + contents_activate_from_filter = false; + button.select(); + } + } + } + } + }); + + auto nav_filter_sort_txt = nav_filters->addField("nav_filter_sort_txt", 64); + nav_filter_sort_txt->setSize(SDL_Rect{ 44, 15, nav_filters->getSize().w - 44, 28 }); + nav_filter_sort_txt->setFont(smallfont_outline); + nav_filter_sort_txt->setText("SORT ALPHABETICAL"); + nav_filter_sort_txt->setHJustify(Field::justify_t::LEFT); + nav_filter_sort_txt->setVJustify(Field::justify_t::TOP); + nav_filter_sort_txt->setColor(makeColorRGB(220, 178, 113)); + + auto nav_filter_sort_txt2 = nav_filters->addField("nav_filter_sort_txt2", 64); + nav_filter_sort_txt2->setSize(SDL_Rect{ 44, 19 + 24 + 8, nav_filters->getSize().w - 44, 28 }); + nav_filter_sort_txt2->setFont(smallfont_outline); + nav_filter_sort_txt2->setText("HIDE UNDISCOVERED"); + nav_filter_sort_txt2->setHJustify(Field::justify_t::LEFT); + nav_filter_sort_txt2->setVJustify(Field::justify_t::TOP); + nav_filter_sort_txt2->setColor(makeColorRGB(220, 178, 113)); + } + auto contents = navigation->addFrame("contents"); contents->setSize(SDL_Rect{ nav_contents_bg->pos.x + 6, nav_contents_bg->pos.y + 14, 184 + 12, 436 }); contents->setActualSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getSize().h }); @@ -37639,12 +39071,19 @@ namespace MainMenu { contents->setListOffset(SDL_Rect{ 12, 1, 0, 0 }); contents->setScrollWithLeftControls(false); contents->setClickable(true); - contents->setSelectorOffset(SDL_Rect{ -10, -14, 28, 28 }); + contents->setGlyphPosition(Widget::CENTERED_BOTTOM); + contents->setButtonsOffset(SDL_Rect{ 10, 16, 0, 0 }); + contents->setSelectorOffset(SDL_Rect{ -5, -13, 25, 13 }); contents->setTickCallback([](Widget& widget) { auto frame = static_cast(&widget); + frame->setHideGlyphs(false); if ( frame->isSelected() ) { frame->setAllowScrollBinds(true); + if ( frame->isActivated() ) + { + frame->setHideGlyphs(true); + } } else { @@ -37653,8 +39092,8 @@ namespace MainMenu { if ( compendium_current == "achievements" ) { - frame->addWidgetAction("MenuLeft", "page_prev"); - frame->addWidgetAction("MenuRight", "page_next"); + frame->addWidgetAction("MenuLeft", "achievements_page_prev"); + frame->addWidgetAction("MenuRight", "achievements_page_next"); } else { @@ -37720,18 +39159,14 @@ namespace MainMenu { contents->addWidgetAction("MenuCancel", "back_button"); contents->addWidgetAction("MenuPageLeft", "tab_left"); contents->addWidgetAction("MenuPageRight", "tab_right"); + contents->addWidgetMovement("MenuAlt2", "nav_filter_sort"); contents->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); - //contents->addWidgetAction("MenuAlt1", "delete_entry"); - //contents->addWidgetAction("MenuAlt2", "open_filters"); - //contents->addWidgetAction("MenuPageLeft", "tab_left"); - //contents->addWidgetAction("MenuPageRight", "tab_left"); - //contents->addWidgetAction("MenuPageRightAlt", "kills_toggle_right"); { auto nav_slider = navigation->addSlider("nav_slider"); nav_slider->setRailSize(SDL_Rect{ navigation->getSize().w - 20 - 16, - 24 + 22, 20, navigation->getSize().h - 32 - 22 - 38 }); + 24 + 22, 20, navigation->getSize().h - 32 - 22 - 38 - 66 }); nav_slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 }); nav_slider->setBorder(16); nav_slider->setOntop(true); @@ -37749,6 +39184,7 @@ namespace MainMenu { nav_slider->addWidgetAction("MenuCancel", "back_button"); nav_slider->addWidgetAction("MenuPageLeft", "tab_left"); nav_slider->addWidgetAction("MenuPageRight", "tab_right"); + nav_slider->addWidgetMovement("MenuAlt2", "nav_filter_sort"); nav_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); nav_slider->setCallback([](Slider& slider) { @@ -38084,6 +39520,7 @@ namespace MainMenu { page_right->setWidgetSearchParent("compendium"); page_right->addWidgetAction("MenuPageLeft", "tab_left"); page_right->addWidgetAction("MenuPageRight", "tab_right"); + page_right->addWidgetMovement("MenuAlt2", "nav_filter_sort"); page_right->setTickCallback([](Widget& widget) { Frame* page_right = static_cast(&widget); if ( !page_right ) { @@ -38146,7 +39583,7 @@ namespace MainMenu { if ( main_menu_frame ) { - auto selectedWidget = main_menu_frame->findSelectedWidget(widget.getOwner()); + auto selectedWidget = main_menu_frame->findSelectedWidget(getMenuOwner()); if ( !selectedWidget || !selectedWidget->isChildOf(widget) ) { Frame* compendium = page_right->getParent(); @@ -38229,17 +39666,34 @@ namespace MainMenu { page_right_unlock_btn->setWidgetBack("back_button"); page_right_unlock_btn->addWidgetAction("MenuPageLeft", "tab_left"); page_right_unlock_btn->addWidgetAction("MenuPageRight", "tab_right"); + page_right_unlock_btn->addWidgetMovement("MenuAlt2", "nav_filter_sort"); page_right_unlock_btn->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); page_right_unlock_btn->setCallback([](Button& button) { compendiumRevealSection(&button); - button.setInvisible(true); - button.setDisabled(true); }); + auto reveal_unlock_cost = page_right_unlock->addField("unlock_lore_cost", 32); + reveal_unlock_cost->setSize(SDL_Rect{ page_right_unlock_btn->getSize().w - 64, page_right_unlock_btn->getSize().h / 2 - 24 / 2, 24, 24 }); + reveal_unlock_cost->setHJustify(Field::justify_t::CENTER); + reveal_unlock_cost->setVJustify(Field::justify_t::TOP); + reveal_unlock_cost->setText("-"); + reveal_unlock_cost->setColor(makeColorRGB(128, 128, 128)); + reveal_unlock_cost->setFont(bigfont_outline); + reveal_unlock_cost->setOntop(true); + + auto right_bg = window->addImage(SDL_Rect{ page_right->getSize().x + 4, page_right->getSize().y + 8, 386, 472 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Details_Frame_00.png", "page right img"); right_bg->disabled = true; + auto page_right_unlock_cost = page_right->addField("lore_cost", 32); + page_right_unlock_cost->setSize(SDL_Rect{ 4 + right_bg->pos.w - 34, 16, 34, 38 }); + page_right_unlock_cost->setHJustify(Field::justify_t::CENTER); + page_right_unlock_cost->setVJustify(Field::justify_t::TOP); + page_right_unlock_cost->setText(""); + page_right_unlock_cost->setFont(bigfont_outline); + page_right_unlock_cost->setColor(makeColorRGB(198, 190, 179)); + page_right_reveal_top->setHollow(true); page_right_reveal_top->setClickable(false); page_right_reveal_top->setSize(SDL_Rect{ page_right->getSize().x, page_right->getSize().y, 398, 484 }); @@ -38255,7 +39709,7 @@ namespace MainMenu { auto page_right_title = window->addField("page_right_title", 128); page_right_title->setFont("fonts/kongtext.ttf#16#0"); - page_right_title->setText("DETAILS"); + page_right_title->setText(""); page_right_title->setHJustify(Field::justify_t::CENTER); page_right_title->setVJustify(Field::justify_t::TOP); page_right_title->setSize(SDL_Rect{ page_right->getSize().x + 8, page_right->getSize().y - 10, page_right->getSize().w - 24, 28 }); @@ -38292,6 +39746,7 @@ namespace MainMenu { right_slider->addWidgetAction("MenuCancel", "right_back_button"); right_slider->addWidgetAction("MenuPageLeft", "tab_left"); right_slider->addWidgetAction("MenuPageRight", "tab_right"); + //right_slider->addWidgetMovement("MenuAlt2", "nav_filter_sort"); right_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); right_slider->setCallback([](Slider& slider) { @@ -38385,6 +39840,30 @@ namespace MainMenu { page_right_next->setVJustify(Field::justify_t::TOP); page_right_next->setSize(SDL_Rect{ page_right->getSize().x + 4, page_right->getSize().y + 475 + 8, 378, 28 }); page_right_next->setColor(makeColorRGB(135, 94, 45)); + page_right_next->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto txt = const_cast((Field*)(&widget)); + auto frame = static_cast(txt->getParent()); + if ( auto contents = frame->findFrame("contents") ) + { + if ( contents->isSelected() ) + { + if ( !isMouseVisible() ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuRight", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = txt->getSize().x + txt->getSize().w; + int y = txt->getSize().y + 16; + image->draw(nullptr, SDL_Rect{ x - w, y, w, h }, viewport); + } + } + } + }); auto page_left_prev = window->addField("page_left_prev", 128); page_left_prev->setFont(smallfont_no_outline); @@ -38393,8 +39872,39 @@ namespace MainMenu { page_left_prev->setVJustify(Field::justify_t::TOP); page_left_prev->setSize(SDL_Rect{ page_left->getSize().x + 6, page_right->getSize().y + 475 + 8, 378, 28 }); page_left_prev->setColor(makeColorRGB(135, 94, 45)); + page_left_prev->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto txt = const_cast((Field*)(&widget)); + auto frame = static_cast(txt->getParent()); + if ( auto contents = frame->findFrame("contents") ) + { + if ( contents->isSelected() ) + { + if ( !isMouseVisible() ) + { + // draw glyphs + bool pressed = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + auto path = input.getGlyphPathForBinding("MenuLeft", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + int x = txt->getSize().x; + int y = txt->getSize().y + 16; + image->draw(nullptr, SDL_Rect{ x, y, w, h }, viewport); + } + } + } + }); - compendiumPopulatePageRight(page_right); + if ( compendium_current == "achievements" ) + { + compendiumPopulateAchievements(window); + } + else + { + compendiumPopulatePageRight(page_right); + } compendiumPopulateContents(window); contents_activate_from_tab = false; } From 2c82cb95f5507d24d203614e6ca9cb913ed739f4 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 10 Aug 2024 02:12:09 +1000 Subject: [PATCH 048/244] * lang update --- lang/en.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index 1c5230b07..10692525f 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6412,5 +6412,8 @@ Magic Required: %d (%s)# 6183 New Entry!# 6184 Achievements# 6185 RECORDS# +6186 RECIPE CHART# +6187 USAGE TIPS# +6188 DETAILS# 6200 END# From a5c855ff9e1eaf48b6942f013bd0f0cbbc40cfc0 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 10 Aug 2024 02:28:05 +1000 Subject: [PATCH 049/244] * add alchemy feature img, line highlights codex world --- src/mod_tools.cpp | 78 ++++++++++++++++++++++++++++++++++- src/mod_tools.hpp | 3 ++ src/ui/MainMenu.cpp | 99 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 172 insertions(+), 8 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 6439dfff5..02651587d 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11452,6 +11452,46 @@ void Compendium_t::readCodexFromFile() { obj.lorePoints = w["lore_points"].GetInt(); } + obj.linesToHighlight.clear(); + for ( auto& line : obj.details ) + { + if ( line.size() > 0 && line[0] == '-' ) + { + line[0] = '\x1E'; + } + } + if ( w.HasMember("feature_img") ) + { + obj.featureImg = w["feature_img"].GetString(); + } + if ( w.HasMember("details_line_highlights") ) + { + for ( auto itr = w["details_line_highlights"].Begin(); itr != w["details_line_highlights"].End(); ++itr ) + { + if ( itr->HasMember("color") ) + { + Uint8 r, g, b; + if ( (*itr)["color"].HasMember("r") ) + { + r = (*itr)["color"]["r"].GetInt(); + } + if ( (*itr)["color"].HasMember("g") ) + { + g = (*itr)["color"]["g"].GetInt(); + } + if ( (*itr)["color"].HasMember("b") ) + { + b = (*itr)["color"]["b"].GetInt(); + } + + obj.linesToHighlight.push_back(makeColorRGB(r, g, b)); + } + else + { + obj.linesToHighlight.push_back(0); + } + } + } Compendium_t::Events_t::eventCodexIDLookup[name] = obj.id; Compendium_t::Events_t::codexIDToString[obj.id + Compendium_t::Events_t::kEventCodexOffset] = name; @@ -11549,7 +11589,7 @@ void Compendium_t::readWorldFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11588,6 +11628,42 @@ void Compendium_t::readWorldFromFile() { obj.lorePoints = w["lore_points"].GetInt(); } + obj.linesToHighlight.clear(); + for ( auto& line : obj.details ) + { + if ( line.size() > 0 && line[0] == '-' ) + { + line[0] = '\x1E'; + } + } + if ( w.HasMember("details_line_highlights") ) + { + for ( auto itr = w["details_line_highlights"].Begin(); itr != w["details_line_highlights"].End(); ++itr ) + { + if ( itr->HasMember("color") ) + { + Uint8 r, g, b; + if ( (*itr)["color"].HasMember("r") ) + { + r = (*itr)["color"]["r"].GetInt(); + } + if ( (*itr)["color"].HasMember("g") ) + { + g = (*itr)["color"]["g"].GetInt(); + } + if ( (*itr)["color"].HasMember("b") ) + { + b = (*itr)["color"]["b"].GetInt(); + } + + obj.linesToHighlight.push_back(makeColorRGB(r, g, b)); + } + else + { + obj.linesToHighlight.push_back(0); + } + } + } Compendium_t::Events_t::eventWorldIDLookup[name] = obj.id; Compendium_t::Events_t::worldIDToString[obj.id + Compendium_t::Events_t::kEventWorldOffset] = name; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 7b774f2b4..5b56af705 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3925,6 +3925,7 @@ struct Compendium_t std::string imagePath = ""; std::vector models; std::vector blurb; + std::vector linesToHighlight; std::vector details; std::set unlockAchievements; std::set unlockTags; @@ -3948,8 +3949,10 @@ struct Compendium_t std::string imagePath = ""; std::vector renderedImagePaths; std::vector blurb; + std::vector linesToHighlight; std::vector details; std::vector models; + std::string featureImg = ""; int id = -1; CompendiumView_t view; int lorePoints = 0; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index f497f30ce..ef345cec7 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32742,7 +32742,7 @@ namespace MainMenu { if ( page_right = page_right->findFrame("page_right_inner") ) { - if ( auto details = page_right->findField("details") ) + if ( auto details = page_right->findField("world_details") ) { std::string txt = ""; for ( auto& str : entry.details ) @@ -32751,6 +32751,14 @@ namespace MainMenu { txt += str; } details->setText(txt.c_str()); + details->clearLinesToColor(); + for ( size_t i = 0; i < entry.linesToHighlight.size(); ++i ) + { + if ( entry.linesToHighlight[i] > 0 ) + { + details->addColorToLine(i, entry.linesToHighlight[i]); + } + } const int numLines = details->getNumTextLines(); auto actualFont = Font::get(details->getFont()); @@ -35134,13 +35142,37 @@ namespace MainMenu { { f->removeSelf(); } - if ( auto details = page_right->findField("details") ) + + if ( auto details = page_right->findField("codex_details") ) { + auto featureImg = page_right->findImage("feature_img"); + if ( featureImg ) + { + featureImg->disabled = true; + } + auto featureTxt = page_right->findField("feature txt"); + if ( featureTxt ) + { + featureTxt->setDisabled(true); + } + Field* tipsTxt = page_right->findField("tips txt"); if ( tipsTxt ) { tipsTxt->setDisabled(true); + + SDL_Rect tipsPos = tipsTxt->getSize(); + if ( featureTxt ) + { + tipsPos.y = featureTxt->getSize().y; + } + tipsTxt->setSize(tipsPos); + + SDL_Rect pos = details->getSize(); + pos.y = tipsPos.y + 27; + details->setSize(pos); } + details->clearLinesToColor(); if ( name == "classes list" ) { details->setText(""); @@ -35149,6 +35181,36 @@ namespace MainMenu { } else { + if ( entry.featureImg != "" ) + { + if ( auto imgGet = Image::get(entry.featureImg.c_str()) ) + { + featureImg->pos.w = imgGet->getWidth(); + featureImg->pos.h = imgGet->getHeight(); + featureImg->disabled = false; + featureImg->path = entry.featureImg; + } + } + if ( !featureImg->disabled ) + { + if ( featureTxt ) + { + featureTxt->setDisabled(false); + } + SDL_Rect pos = details->getSize(); + int offset = featureImg->pos.y + featureImg->pos.h + 16; + if ( tipsTxt ) + { + SDL_Rect tipsPos = tipsTxt->getSize(); + tipsPos.y += offset; + tipsTxt->setSize(tipsPos); + + SDL_Rect pos = details->getSize(); + pos.y = tipsPos.y + 27; + details->setSize(pos); + } + } + if ( tipsTxt ) { tipsTxt->setDisabled(false); @@ -35160,6 +35222,13 @@ namespace MainMenu { txt += str; } details->setText(txt.c_str()); + for ( size_t i = 0; i < entry.linesToHighlight.size(); ++i ) + { + if ( entry.linesToHighlight[i] > 0 ) + { + details->addColorToLine(i, entry.linesToHighlight[i]); + } + } } const int numLines = details->getNumTextLines(); @@ -37391,15 +37460,27 @@ namespace MainMenu { { auto charTxt = page_right_inner->addField("tips txt", 64); charTxt->setFont(menu_option_font); - charTxt->setText("USAGE TIPS"); + charTxt->setText(Language::get(6187)); charTxt->setHJustify(Field::justify_t::LEFT); charTxt->setVJustify(Field::justify_t::TOP); charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); charTxt->setColor(makeColor(198, 190, 179, 255)); + auto featureTxt = page_right_inner->addField("feature txt", 64); + featureTxt->setFont(menu_option_font); + featureTxt->setText(Language::get(6186)); + featureTxt->setHJustify(Field::justify_t::LEFT); + featureTxt->setVJustify(Field::justify_t::TOP); + featureTxt->setSize(charTxt->getSize()); + featureTxt->setColor(makeColor(198, 190, 179, 255)); + featureTxt->setDisabled(true); + auto featureImg = page_right_inner->addImage(SDL_Rect{ 8, featureTxt->getSize().y + 28, 0, 0}, + 0xFFFFFFFF, "", "feature_img"); + featureImg->disabled = true; + int statx = padx + 4; int staty = pady + 18 + 9; - auto statsTxt = page_right_inner->addField("details", 1024); + auto statsTxt = page_right_inner->addField("codex_details", 1024); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); statsTxt->setHJustify(Field::justify_t::LEFT); @@ -37411,7 +37492,7 @@ namespace MainMenu { { auto charTxt = page_right_inner->addField("tips txt", 1024); charTxt->setFont(menu_option_font); - charTxt->setText("DETAILS"); + charTxt->setText(Language::get(6188)); charTxt->setHJustify(Field::justify_t::LEFT); charTxt->setVJustify(Field::justify_t::TOP); charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); @@ -37419,7 +37500,7 @@ namespace MainMenu { int statx = padx + 4; int staty = pady + 18 + 9; - auto statsTxt = page_right_inner->addField("details", 1024); + auto statsTxt = page_right_inner->addField("world_details", 1024); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); statsTxt->setHJustify(Field::justify_t::LEFT); @@ -39186,6 +39267,11 @@ namespace MainMenu { nav_slider->addWidgetAction("MenuPageRight", "tab_right"); nav_slider->addWidgetMovement("MenuAlt2", "nav_filter_sort"); nav_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + nav_slider->setWidgetLeft("contents"); + nav_slider->setWidgetRight("contents"); + nav_slider->setWidgetUp("contents"); + nav_slider->setWidgetDown("contents"); + nav_slider->addWidgetAction("MenuConfirm", "contents"); nav_slider->setCallback([](Slider& slider) { if ( auto frame = static_cast(slider.getParent()) ) @@ -39746,7 +39832,6 @@ namespace MainMenu { right_slider->addWidgetAction("MenuCancel", "right_back_button"); right_slider->addWidgetAction("MenuPageLeft", "tab_left"); right_slider->addWidgetAction("MenuPageRight", "tab_right"); - //right_slider->addWidgetMovement("MenuAlt2", "nav_filter_sort"); right_slider->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); right_slider->setCallback([](Slider& slider) { From 61a5658f2d7c0b4a46a6bb38295b7949ff4b29cd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 13 Aug 2024 10:15:59 +1000 Subject: [PATCH 050/244] * fix appraisal not being synced to server --- src/actgeneral.cpp | 1 - src/actplayer.cpp | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index e450ecdff..4961af18e 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -2654,7 +2654,6 @@ void TextSourceScript::handleTextSourceScript(Entity& src, std::string input) } } } - statOnlyUpdateNeeded = true; } } } diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 031f9484a..ec36781eb 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -4375,7 +4375,6 @@ void actPlayer(Entity* my) Entity* additionalLimb = nullptr; Entity* torso = nullptr; node_t* node; - Item* item; int i, bodypart; double dist = 0; bool wearingring = false; @@ -5644,12 +5643,40 @@ void actPlayer(Entity* my) //messagePlayer(0, "Appraisal level up chance: 1 in %d", appraisalEaseOfDifficulty); if ( local_rng.rand() % appraisalEaseOfDifficulty == 0 ) { - my->increaseSkill(PRO_APPRAISAL); + if ( multiplayer == CLIENT ) + { + // request level up + strcpy((char*)net_packet->data, "CSKL"); + net_packet->data[4] = PLAYER_NUM; + net_packet->data[5] = PRO_APPRAISAL; + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 6; + sendPacketSafe(net_sock, -1, net_packet, 0); + } + else + { + my->increaseSkill(PRO_APPRAISAL); + } } } else if ( local_rng.rand() % 7 == 0 ) { - my->increaseSkill(PRO_APPRAISAL); + if ( multiplayer == CLIENT ) + { + // request level up + strcpy((char*)net_packet->data, "CSKL"); + net_packet->data[4] = PLAYER_NUM; + net_packet->data[5] = PRO_APPRAISAL; + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 6; + sendPacketSafe(net_sock, -1, net_packet, 0); + } + else + { + my->increaseSkill(PRO_APPRAISAL); + } } } From db4a77f14ce3850ad9e5487ff7cdb7b0716ca88c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 13 Aug 2024 10:16:40 +1000 Subject: [PATCH 051/244] * compendium add monster lvl, species * fix save bug in compendium * exclude class playtime tutorial --- src/mod_tools.cpp | 87 +++++++++++++++++++++++++++++++++++++++++++---- src/mod_tools.hpp | 14 ++++++++ 2 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 02651587d..bee521d65 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11440,6 +11440,10 @@ void Compendium_t::readCodexFromFile() jsonVecToVec(w["blurb"], obj.blurb); jsonVecToVec(w["details"], obj.details); obj.imagePath = w["img"].GetString(); + if ( w.HasMember("enable_tutorial") ) + { + obj.enableTutorial = w["enable_tutorial"].GetBool(); + } if ( w.HasMember("rendered_imgs") ) { jsonVecToVec(w["rendered_imgs"], obj.renderedImagePaths); @@ -11878,6 +11882,44 @@ void Compendium_t::readMonstersFromFile() jsonVecToVec(stats["atk"], monster.atk); jsonVecToVec(stats["rangeatk"], monster.rangeatk); jsonVecToVec(stats["pwr"], monster.pwr); + if ( stats.HasMember("lvl") ) + { + jsonVecToVec(stats["lvl"], monster.lvl); + } + if ( stats.HasMember("species") ) + { + auto species = CompendiumMonsters_t::SPECIES_NONE; + std::string str = stats["species"].GetString(); + if ( str == "humanoid" ) + { + species = CompendiumMonsters_t::SPECIES_HUMANOID; + } + else if ( str == "beast" ) + { + species = CompendiumMonsters_t::SPECIES_BEAST; + } + else if ( str == "beastfolk" ) + { + species = CompendiumMonsters_t::SPECIES_BEASTFOLK; + } + else if ( str == "undead" ) + { + species = CompendiumMonsters_t::SPECIES_UNDEAD; + } + else if ( str == "demonoid" ) + { + species = CompendiumMonsters_t::SPECIES_DEMONOID; + } + else if ( str == "construct" ) + { + species = CompendiumMonsters_t::SPECIES_CONSTRUCT; + } + else if ( str == "elemental" ) + { + species = CompendiumMonsters_t::SPECIES_ELEMENTAL; + } + monster.species = species; + } jsonVecToVec(m["abilities"], monster.abilities); { @@ -14146,10 +14188,6 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } auto& e = (multiplayer == SERVER && playernum != 0 && !loadingValue) ? serverPlayerEvents[playernum][tag] : playerEvents[tag]; - if ( e.find(itemType) == e.end() ) - { - e[itemType] = EventVal_t(tag); - } if ( def.eventTrackingType == EventTrackingType::ONCE_PER_RUN && !loadingValue ) { @@ -14162,9 +14200,13 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con players[playernum]->compendiumProgress.itemEvents[def.name][itemType] += value; } - auto& val = e[itemType]; if ( loadingValue ) { + if ( e.find(itemType) == e.end() ) + { + e[itemType] = EventVal_t(tag); + } + auto& val = e[itemType]; val.value = value; // reading from savefile val.firstValue = false; } @@ -14190,7 +14232,11 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con } value = players[playernum]->compendiumProgress.itemEvents[def.name][itemType]; } - + if ( e.find(itemType) == e.end() ) + { + e[itemType] = EventVal_t(tag); + } + auto& val = e[itemType]; if ( val.applyValue(value) ) { if ( *cvar_compendiumDebugSave ) @@ -14654,6 +14700,33 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag if ( !loadingValue ) { + if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL + || gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL_INIT ) + { + // don't update in tutorial + if ( def.eventTrackingType == EventTrackingType::UNIQUE_PER_RUN + || def.eventTrackingType == EventTrackingType::UNIQUE_PER_FLOOR ) + { + return; + } + else if ( tag == Compendium_t::CPDM_CLASS_MOVING_TIME + || tag == Compendium_t::CPDM_CLASS_SNEAK_TIME ) + { + return; + } + if ( category ) + { + auto find = CompendiumEntries.codex.find(category); + if ( find != CompendiumEntries.codex.end() ) + { + if ( !find->second.enableTutorial ) + { + return; + } + } + } + } + if ( def.clienttype == CLIENT_ONLY ) { if ( multiplayer != SINGLE ) @@ -14710,7 +14783,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag auto find = eventCodexIDLookup.find(cat); if ( find != eventCodexIDLookup.end() ) { - if ( eventCodexIDLookup[cat] == codexID ) + if ( (eventCodexIDLookup[cat] + kEventCodexOffset) == codexID ) { foundCategory = true; break; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 5b56af705..345fd6dbd 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3871,6 +3871,17 @@ struct Compendium_t struct CompendiumMonsters_t { + enum MonsterSpecies + { + SPECIES_NONE, + SPECIES_HUMANOID, + SPECIES_BEAST, + SPECIES_BEASTFOLK, + SPECIES_UNDEAD, + SPECIES_DEMONOID, + SPECIES_CONSTRUCT, + SPECIES_ELEMENTAL + }; struct Monster_t { int monsterType = NOTHING; @@ -3882,6 +3893,8 @@ struct Compendium_t std::vector atk; std::vector rangeatk; std::vector pwr; + MonsterSpecies species; + std::vector lvl; std::array resistances; std::vector abilities; std::vector inventory; @@ -3956,6 +3969,7 @@ struct Compendium_t int id = -1; CompendiumView_t view; int lorePoints = 0; + bool enableTutorial = false; }; static std::map>> contents; static std::map contentsMap; From 4c2ff43a90f130ea8d8374d723373b7af2a780d5 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 13 Aug 2024 10:17:33 +1000 Subject: [PATCH 052/244] * touch up monster page formatting, add species * fix sound on dpad movement in lists --- src/ui/MainMenu.cpp | 265 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 224 insertions(+), 41 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index ef345cec7..c9c315197 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -34836,6 +34836,7 @@ namespace MainMenu { frames[i]->select(); frames[i]->scrollParent(); soundMove(); + foundSelection = true; selectCompendiumItemInList(*frames[i], *page_right_inner, false, true); break; } @@ -35376,87 +35377,171 @@ namespace MainMenu { if ( page_right = page_right->findFrame("page_right_inner") ) { + if ( auto txt = page_right->findField("level type") ) + { + std::string str = Language::get(6190); + if ( entry.lvl.size() > 0 ) + { + char buf[32] = "-"; + auto& stat = entry.lvl; + if ( stat.size() > 1 ) + { + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); + } + else if ( stat.size() == 1 ) + { + snprintf(buf, sizeof(buf), "%d", stat[0]); + } + str += buf; + } + else + { + str += "???"; + } + if ( entry.species != Compendium_t::CompendiumMonsters_t::SPECIES_NONE ) + { + const char* lang = nullptr; + switch ( entry.species ) + { + case Compendium_t::CompendiumMonsters_t::SPECIES_HUMANOID: + lang = Language::get(6202); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_BEAST: + lang = Language::get(6204); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_BEASTFOLK: + lang = Language::get(6203); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_UNDEAD: + lang = Language::get(6205); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_DEMONOID: + lang = Language::get(6206); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_CONSTRUCT: + lang = Language::get(6207); + break; + case Compendium_t::CompendiumMonsters_t::SPECIES_ELEMENTAL: + lang = Language::get(6208); + break; + default: + break; + } + if ( lang ) + { + str += " "; + //str += "("; + str += lang; + //str += ")"; + } + } + txt->setText(str.c_str()); + } if ( auto txt = page_right->findField("hp") ) + { + txt->setText(Language::get(6199)); + } + if ( auto txt = page_right->findField("hp val") ) { char buf[32] = "-"; auto& stat = entry.hp; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "HP %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "HP %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } if ( auto txt = page_right->findField("ac") ) + { + txt->setText(Language::get(6200)); + } + if ( auto txt = page_right->findField("ac val") ) { char buf[32] = "-"; auto& stat = entry.ac; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "AC %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "AC %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } if ( auto txt = page_right->findField("spd") ) + { + txt->setText(Language::get(6201)); + } + if ( auto txt = page_right->findField("spd val") ) { char buf[32] = "-"; auto& stat = entry.spd; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "SPD %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "SPD %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } if ( auto txt = page_right->findField("atk") ) + { + txt->setText(Language::get(6197)); + } + if ( auto txt = page_right->findField("atk val") ) { char buf[32] = "-"; auto& stat = entry.atk; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "ATK %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "ATK %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } if ( auto txt = page_right->findField("rangeatk") ) + { + txt->setText(Language::get(6197)); + } + if ( auto txt = page_right->findField("rangeatk val") ) { char buf[32] = "-"; auto& stat = entry.rangeatk; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "ATK %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "ATK %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } if ( auto txt = page_right->findField("pwr") ) + { + txt->setText(Language::get(6198)); + } + if ( auto txt = page_right->findField("pwr val") ) { char buf[32] = "-"; auto& stat = entry.pwr; if ( stat.size() > 1 ) { - snprintf(buf, sizeof(buf), "PWR %d - %d", stat[0], stat[1]); + snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); } else if ( stat.size() == 1 ) { - snprintf(buf, sizeof(buf), "PWR %d", stat[0]); + snprintf(buf, sizeof(buf), "%d", stat[0]); } txt->setText(buf); } @@ -35499,7 +35584,7 @@ namespace MainMenu { } else { - field->setColor(hudColors.characterSheetNeutral); + field->setColor(compendiumContentsDefaultColor); field->setText("100%"); } } @@ -35520,8 +35605,8 @@ namespace MainMenu { } else { - field->setColor(hudColors.characterSheetNeutral); - field->setText("-"); + field->setColor(compendiumContentsDefaultColor); + field->setText(" -"); } } } @@ -35622,7 +35707,7 @@ namespace MainMenu { inv->setText(txt.c_str()); const int numInvLines = inv->getNumTextLines(); SDL_Rect invPos = inv->getSize(); - invPos.y = pos.y + 26; + invPos.y = pos.y + 22; if ( actualFont ) { invPos.h = std::max(24, 24 + (numInvLines - 1) * actualFont->height(true)); @@ -37513,7 +37598,7 @@ namespace MainMenu { { auto charTxt = page_right_inner->addField("characteristics txt", 64); charTxt->setFont(menu_option_font); - charTxt->setText("CHARACTERISTICS"); + charTxt->setText(Language::get(6189)); charTxt->setHJustify(Field::justify_t::LEFT); charTxt->setVJustify(Field::justify_t::TOP); charTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); @@ -37522,16 +37607,16 @@ namespace MainMenu { padx += 4; pady += 23; - Field* statsTxt = page_right_inner->addField("stats txt", 64); + Field* statsTxt = page_right_inner->addField("level type", 64); statsTxt->setFont(smallfont_outline); - statsTxt->setText("STATS"); + statsTxt->setText(Language::get(6190)); statsTxt->setHJustify(Field::justify_t::LEFT); statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); statsTxt->setColor(makeColor(135, 94, 45, 255)); int statx = padx + 30; - int staty = pady + 26; + int staty = pady + 22; statsTxt = page_right_inner->addField("hp", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37539,11 +37624,18 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("hp val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); page_right_inner->addImage(SDL_Rect{ statx - 18 - 8, staty + 11 - 8, 16, 16 }, 0xFFFFFFFF, "*images/ui/Inventory/potions/healing.png"); - staty += 28; + staty += 26; statsTxt = page_right_inner->addField("ac", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37551,11 +37643,18 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("ac val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_AC_00.png"); - staty += 28; + staty += 26; statsTxt = page_right_inner->addField("spd", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37563,12 +37662,19 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("spd val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_DEX_00.png"); - statx = padx + 30 + 166; - staty = pady + 26; + statx = padx + 30 + 178; + staty = pady + 22; statsTxt = page_right_inner->addField("atk", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37576,11 +37682,18 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("atk val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_ATT_00.png"); - staty += 28; + staty += 26; statsTxt = page_right_inner->addField("rangeatk", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37588,11 +37701,18 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("rangeatk val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Ranged01.png"); - staty += 28; + staty += 26; statsTxt = page_right_inner->addField("pwr", 64); statsTxt->setFont(smallfont_outline); statsTxt->setText(""); @@ -37600,6 +37720,14 @@ namespace MainMenu { statsTxt->setVJustify(Field::justify_t::TOP); statsTxt->setSize(SDL_Rect{ statx, staty, 132, 24 }); statsTxt->setColor(statValColor); + statsTxt = page_right_inner->addField("pwr val", 64); + statsTxt->setFont(smallfont_outline); + statsTxt->setText(""); + statsTxt->setHJustify(Field::justify_t::LEFT); + statsTxt->setVJustify(Field::justify_t::TOP); + statsTxt->setSize(SDL_Rect{ statx + 42, staty, 132, 24 }); + statsTxt->setColor(statValColor); + page_right_inner->addImage(SDL_Rect{ statx - 18 - 12, staty + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/Charsheet/HUD_CharSheet_SPWR_00.png"); @@ -37611,7 +37739,7 @@ namespace MainMenu { { Field* resTxt = page_right_inner->addField("res txt", 64); resTxt->setFont(smallfont_outline); - resTxt->setText("WEAKNESSES"); + resTxt->setText(Language::get(6191)); resTxt->setHJustify(Field::justify_t::LEFT); resTxt->setVJustify(Field::justify_t::TOP); resTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); @@ -37622,7 +37750,7 @@ namespace MainMenu { const int padIconx = -6; const int padIcony = -2; - const int padTextx = 0; + const int padTextx = -4; const int padTexty = 0; const bool useIcons = false; Frame::image_t* icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, @@ -37640,7 +37768,7 @@ namespace MainMenu { page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Unarmed01.png", "res_unarmed_img"); - resy += 40; + resy += 32; icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_ranged"); icon->disabled = !useIcons; @@ -37657,7 +37785,7 @@ namespace MainMenu { 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Ranged01.png", "res_ranged_img"); resy = pady + 26; - resx += 84; + resx += 82; icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_sword"); @@ -37674,7 +37802,7 @@ namespace MainMenu { page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Swords01.png", "res_sword_img"); - resy += 40; + resy += 32; icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_mace"); icon->disabled = !useIcons; @@ -37708,7 +37836,7 @@ namespace MainMenu { page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Polearms01.png", "res_polearm_img"); - resy += 40; + resy += 32; icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_axe"); icon->disabled = !useIcons; @@ -37725,7 +37853,7 @@ namespace MainMenu { 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Axes01.png", "res_axe_img"); resy = pady + 26; - resx += 84; + resx += 82; icon = page_right_inner->addImage(SDL_Rect{ resx + padIconx, resy + padIcony, 24, 24 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/res_neutral.png", "res_magic"); @@ -37742,21 +37870,21 @@ namespace MainMenu { page_right_inner->addImage(SDL_Rect{ resx - 18 - 12, resy + 11 - 12, 24, 24 }, 0xFFFFFFFF, "*images/ui/SkillSheet/Icons/Magic01.png", "res_magic_img"); - resy += 40; + resy += 32; pady = resy + *compendiumMonsterSectionPadY; } { Field* abilitiesTxt = page_right_inner->addField("abilities txt", 64); abilitiesTxt->setFont(smallfont_outline); - abilitiesTxt->setText("ABILITIES"); + abilitiesTxt->setText(Language::get(6192)); abilitiesTxt->setHJustify(Field::justify_t::LEFT); abilitiesTxt->setVJustify(Field::justify_t::TOP); abilitiesTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); abilitiesTxt->setColor(makeColor(135, 94, 45, 255)); int abilityx = padx + 8; - int abilityy = pady + 26; + int abilityy = pady + 22; abilitiesTxt = page_right_inner->addField("abilities", 1024); abilitiesTxt->setFont(smallfont_outline); @@ -37772,14 +37900,14 @@ namespace MainMenu { { Field* invTxt = page_right_inner->addField("inventory txt", 64); invTxt->setFont(smallfont_outline); - invTxt->setText("INVENTORY"); + invTxt->setText(Language::get(6193)); invTxt->setHJustify(Field::justify_t::LEFT); invTxt->setVJustify(Field::justify_t::TOP); invTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); invTxt->setColor(makeColor(135, 94, 45, 255)); int invx = padx + 8; - int invy = pady + 26; + int invy = pady + 22; invTxt = page_right_inner->addField("inventory", 1024); invTxt->setFont(smallfont_outline); @@ -38158,7 +38286,7 @@ namespace MainMenu { }); txt = lore_points->addField("lore_points_label", 64); - txt->setText("POINTS AVAILABLE"); + txt->setText(Language::get(6194)); txt->setFont(smallfont_outline); txt->setColor(makeColorRGB(192, 192, 192)); txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24 }); @@ -39129,7 +39257,7 @@ namespace MainMenu { auto nav_filter_sort_txt = nav_filters->addField("nav_filter_sort_txt", 64); nav_filter_sort_txt->setSize(SDL_Rect{ 44, 15, nav_filters->getSize().w - 44, 28 }); nav_filter_sort_txt->setFont(smallfont_outline); - nav_filter_sort_txt->setText("SORT ALPHABETICAL"); + nav_filter_sort_txt->setText(Language::get(6195)); nav_filter_sort_txt->setHJustify(Field::justify_t::LEFT); nav_filter_sort_txt->setVJustify(Field::justify_t::TOP); nav_filter_sort_txt->setColor(makeColorRGB(220, 178, 113)); @@ -39137,7 +39265,7 @@ namespace MainMenu { auto nav_filter_sort_txt2 = nav_filters->addField("nav_filter_sort_txt2", 64); nav_filter_sort_txt2->setSize(SDL_Rect{ 44, 19 + 24 + 8, nav_filters->getSize().w - 44, 28 }); nav_filter_sort_txt2->setFont(smallfont_outline); - nav_filter_sort_txt2->setText("HIDE UNDISCOVERED"); + nav_filter_sort_txt2->setText(Language::get(6196)); nav_filter_sort_txt2->setHJustify(Field::justify_t::LEFT); nav_filter_sort_txt2->setVJustify(Field::justify_t::TOP); nav_filter_sort_txt2->setColor(makeColorRGB(220, 178, 113)); @@ -39607,6 +39735,52 @@ namespace MainMenu { page_right->addWidgetAction("MenuPageLeft", "tab_left"); page_right->addWidgetAction("MenuPageRight", "tab_right"); page_right->addWidgetMovement("MenuAlt2", "nav_filter_sort"); + page_right->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto frame = const_cast((Frame*)(&widget)); + const real_t fpsScale = getFPSScale(144.0); + auto slider = frame->findSlider("right_slider"); + if ( !slider ) { return; } + auto page_right_inner = frame->findFrame("page_right_inner"); + if ( !page_right_inner ) { return; } + + const int maxScrollY = page_right_inner->getActualSize().h - page_right_inner->getSize().h; + const int currentScrollY = page_right_inner->getActualSize().y; + const real_t startFadeTop = 20.0; + const real_t startFadeBottom = maxScrollY - startFadeTop; + real_t fadeAlphaTop = std::max(0.0, std::min(1.0, (startFadeTop - currentScrollY) / startFadeTop)); + real_t fadeAlphaBottom = std::min(1.0, std::max(0.0, (currentScrollY - startFadeBottom) / startFadeTop)); + + if ( auto fader_bottom = frame->findImage("page_right_gradient_bottom") ) + { + fader_bottom->disabled = slider->isInvisible(); + + Uint8 r, g, b, a; + getColor(fader_bottom->color, &r, &g, &b, &a); + int newAlpha = (1.0 - fadeAlphaBottom) * 255; + newAlpha = std::min(newAlpha, 255); + a = newAlpha; + fader_bottom->color = makeColor(r, g, b, a); + if ( a == 0 ) + { + fader_bottom->disabled = true; + } + } + if ( auto fader_top = frame->findImage("page_right_gradient_top") ) + { + fader_top->disabled = slider->isInvisible(); + + Uint8 r, g, b, a; + getColor(fader_top->color, &r, &g, &b, &a); + int newAlpha = (1.0 - fadeAlphaTop) * 255; + newAlpha = std::min(newAlpha, 255); + a = newAlpha; + fader_top->color = makeColor(r, g, b, a); + if ( a == 0 ) + { + fader_top->disabled = true; + } + } + }); page_right->setTickCallback([](Widget& widget) { Frame* page_right = static_cast(&widget); if ( !page_right ) { @@ -39733,6 +39907,15 @@ namespace MainMenu { } }); + auto page_right_gradient_top = page_right->addImage(SDL_Rect{ 12, 26, 338 + 8, 56 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Contents_ScrollFaderTop_00.png", "page_right_gradient_top"); + page_right_gradient_top->ontop = true; + auto page_right_gradient_bottom = page_right->addImage(SDL_Rect{ 12, page_right->getSize().h - 148 - 40, 338 + 8, 56}, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Contents_ScrollFader_00.png", "page_right_gradient_bottom"); + page_right_gradient_bottom->ontop = true; + auto page_right_unlock = window->addFrame("page_right_unlock"); page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 18, 376, 116 }); page_right_unlock->setHollow(true); From e5e4e9708710053661feb88878d240b78f92ae95 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 01:26:26 +1000 Subject: [PATCH 053/244] * debug open areas, /maplevel2 command to reveal objects on map --- src/game.hpp | 1 + src/interface/consolecommand.cpp | 17 ++++++++ src/maps.cpp | 75 +++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/game.hpp b/src/game.hpp index 247d867d9..bae6775b3 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -366,6 +366,7 @@ extern bool enabledDLCPack2; extern std::vector physFSFilesInDirectory; void loadRandomNames(); void mapLevel(int player); +void mapLevel2(int player); void mapFoodOnLevel(int player); bool mapTileDiggable(const int x, const int y); diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 9614af2d1..8018f2068 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -2353,6 +2353,23 @@ namespace ConsoleCommands { mapLevel(clientnum); }); + static ConsoleCommand ccmd_maplevel2("/maplevel2", "magic mapping for the level (cheat)", []CCMD{ + if ( !(svFlags & SV_FLAG_CHEATS) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); + return; + } + if ( multiplayer != SINGLE ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(299)); + return; + } + + messagePlayer(clientnum, MESSAGE_MISC, Language::get(412)); + + mapLevel2(clientnum); + }); + static ConsoleCommand ccmd_drunky("/drunky", "make me drunk (cheat)", []CCMD{ if (!(svFlags & SV_FLAG_CHEATS)) { diff --git a/src/maps.cpp b/src/maps.cpp index f4a2cc5c2..0412d3cdb 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -7121,7 +7121,7 @@ void assignActions(map_t* map) } std::vector chests; - + //std::set takenSlots; for ( auto node = map->entities->first; node != nullptr; ) { Entity* postProcessEntity = (Entity*)node->element; @@ -7136,8 +7136,52 @@ void assignActions(map_t* map) { chests.push_back(postProcessEntity); } + + //int x = (int)postProcessEntity->x >> 4; + //int y = (int)postProcessEntity->y >> 4; + //takenSlots.insert(x + y * 10000); + } + } + + /*int num5x5s = 0; + // open area debugging tool + for ( int x = 0; x < map->width; ++x ) + { + for ( int y = 0; y < map->height; ++y ) + { + if ( takenSlots.find(x + y * 10000) == takenSlots.end() ) + { + if ( !map->tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map->height] + && !map->tiles[2 + y * MAPLAYERS + x * MAPLAYERS * map->height] ) + { + int numTiles = 0; + for ( int x1 = x; x1 < map->width && x1 < x + 5; ++x1 ) + { + for ( int y1 = y; y1 < map->height && y1 < y + 5; ++y1 ) + { + if ( takenSlots.find(x1 + y1 * 10000) == takenSlots.end() ) + { + if ( !map->tiles[OBSTACLELAYER + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] + && !map->tiles[2 + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] ) + { + ++numTiles; + } + } + } + } + if ( numTiles == 25 ) + { + ++num5x5s; + Entity* ent = newEntity(245, 0, map->entities, nullptr); + ent->behavior == &actBoulder; + ent->x = x * 16.0 + 8; + ent->y = y * 16.0 + 8; + } + } + } } } + messagePlayer(0, MESSAGE_DEBUG, "%d 5x5s", num5x5s);*/ if ( true /*currentlevel == 0*/ ) { @@ -7295,6 +7339,35 @@ void mapLevel(int player) } } +void mapLevel2(int player) +{ + int x, y; + for ( y = 0; y < map.height; ++y ) + { + for ( x = 0; x < map.width; ++x ) + { + if ( map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + if ( !minimap[y][x] ) + { + minimap[y][x] = 2; + } + } + else if ( map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + if ( !minimap[y][x] ) + { + minimap[y][x] = 1; + } + } + else + { + minimap[y][x] = 0; + } + } + } +} + void mapFoodOnLevel(int player) { int numFood = 0; From 3950bd2333353843eb0aeb97112e6fab0f4e1a8e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:20:06 +1000 Subject: [PATCH 054/244] * lang update, monsters fix some sections, amplify magic spellbook name update --- lang/compendium_lang/contents_monsters.json | 6 ++--- lang/en.txt | 30 ++++++++++++++++++--- lang/item_names.json | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json index 397e7366e..49e425250 100644 --- a/lang/compendium_lang/contents_monsters.json +++ b/lang/compendium_lang/contents_monsters.json @@ -10,11 +10,11 @@ {"MYSTERIOUS MERCHANT": "mysterious shop"}, {" BEASTS": "-"}, {"RAT": "rat"}, + {"ALGERNON": "algernon"}, {"SPIDER": "spider"}, {"SHELOB": "shelob"}, {"TROLL": "troll"}, {"THUMPUS": "thumpus"}, - {"ALGERNON": "algernon"}, {"SCORPION": "scorpion"}, {"SKRABBLAG": "skrabblag"}, {"SCARAB": "scarab"}, @@ -39,10 +39,9 @@ {"ARTEMISIA": "artemisia"}, {"BARATHEON": "baratheon"}, {"BARON HERX": "lich"}, - {"BAPHOMET": "devil"}, {"ERUDYCE": "lichice"}, {"ORPHEUS": "lichfire"}, - {" DEMONOIDS": "-"}, + {" DEMONICS": "-"}, {"SUCCUBUS": "succubus"}, {"LILITH": "lilith"}, {"SUCCUBUS CONSORTS": "bram succubi"}, @@ -50,6 +49,7 @@ {"IMP": "imp"}, {"DEMON": "demon"}, {"DEU DE'BREAU": "deudebreau"}, + {"BAPHOMET": "devil"}, {" CONSTRUCTS": "-"}, {"AUTOMATON": "automaton"}, {"SENTRYBOT": "sentrybot"}, diff --git a/lang/en.txt b/lang/en.txt index 10692525f..a8b221e0b 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -5587,7 +5587,7 @@ Lv%d# 5610 ADVENTURE ARCHIVES# 5611 ACHIEVEMENTS# -5612 DUNGEON COMPENDIUM# +5612 COMPENDIUM# 5613 HIGHSCORES# 5614 LEADERBOARDS# 5615 STORY INTRODUCTION# @@ -5727,7 +5727,7 @@ I'm kicking you out of this game.# 5760 Press to Start# 5761 BACK TO GAME# 5762 ASSIGN CONTROLLERS# -5763 DUNGEON COMPENDIUM# +5763 COMPENDIUM# 5764 ACHIEVEMENTS# 5765 SETTINGS# 5766 END LIFE# @@ -6415,5 +6415,27 @@ Magic Required: %d (%s)# 6186 RECIPE CHART# 6187 USAGE TIPS# 6188 DETAILS# - -6200 END# +6189 STATS# +6190 LVL # +6191 DAMAGE TYPE EFFECTIVENESS:# +6192 ABILITIES:# +6193 INVENTORY:# +6194 LORE POINTS AVAILABLE# +6195 SORT ALPHABETICAL# +6196 HIDE UNDISCOVERED# +6197 ATK:# +6198 PWR:# +6199 HP:# +6200 AC:# +6201 SPD:# +6202 HUMANOID# +6203 BEASTFOLK# +6204 BEAST# +6205 UNDEAD# +6206 DEMONIC# +6207 CONSTRUCT# +6208 ELEMENTAL# +6209 PTS# +6210 TOTAL COMPLETION# + +6250 END# diff --git a/lang/item_names.json b/lang/item_names.json index 4f8a31b62..b8b4b7e40 100644 --- a/lang/item_names.json +++ b/lang/item_names.json @@ -982,7 +982,7 @@ "name_unidentified": "shaman mask" }, "spellbook_amplify_magic": { - "name_identified": "spellbook of amplify magic", + "name_identified": "spellbook of elemental focus", "name_unidentified": "spellbook" }, "spellbook_shadow_tag": { From 462cf5eb372c33895d4e77e8daaacc027a6c0d38 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:24:17 +1000 Subject: [PATCH 055/244] * some fixes for compendium tooltip main menu blocking stats to exclude player skill --- src/entity.cpp | 4 ++-- src/interface/playerinventory.cpp | 22 ++++++++++++++-------- src/items.cpp | 1 + src/stat.cpp | 16 ++++++++++++---- src/stat.hpp | 4 ++-- src/ui/GameUI.cpp | 4 ++-- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 37b4b336e..4b7d6b18c 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -11542,12 +11542,12 @@ int AC(Stat* stat) if ( stat->shield ) { - int shieldskill = stat->getPassiveShieldBonus(true); + int shieldskill = stat->getPassiveShieldBonus(true, false); armor += shieldskill; if ( stat->defending ) { //messagePlayer(0, "shield up! +%d", 5 + stat->PROFICIENCIES[PRO_SHIELD] / 5); - armor += stat->getActiveShieldBonus(true); + armor += stat->getActiveShieldBonus(true, false); } } diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 24a152fb8..258913f4d 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -4656,6 +4656,12 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int imgTopBackgroundRight->path = "images/ui/Inventory/tooltips/Hover_TR00.png"; } + auto minWidth = itemTooltip.minWidths[minWidthKey]; + if ( compendiumTooltip ) + { + minWidth = std::max(minWidth, 242); + } + if ( ItemTooltips.itemDebug && (svFlags & SV_FLAG_CHEATS) ) { auto headerBg = frameMain->findImage("inventory mouse tooltip header bg"); @@ -4669,7 +4675,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int tooltipMin->pos = SDL_Rect{ imgTopBackgroundLeft->pos.x + imgTopBackgroundLeft->pos.w + padx, 2, - itemTooltip.minWidths[minWidthKey], + minWidth, 1 }; tooltipMin->disabled = false; auto tooltipMax = frameMain->findImage("inventory mouse tooltip max"); @@ -4688,9 +4694,9 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int headerMax->disabled = false; } - if ( itemTooltip.minWidths[minWidthKey] > 0 ) + if ( minWidth > 0 ) { - textx = std::max(itemTooltip.minWidths[minWidthKey], textx); + textx = std::max(minWidth, textx); } if ( itemTooltip.maxWidths[maxWidthKey] > 0 ) { @@ -5191,7 +5197,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int if ( tag.compare("weapon_durability") == 0 ) { int proficiency = itemCategory(item) == ARMOR ? PRO_UNARMED : getWeaponSkill(item); - if ( stats[player]->getModifiedProficiency(proficiency) == SKILL_LEVEL_LEGENDARY ) + if ( !(compendiumTooltip && intro) && stats[player]->getModifiedProficiency(proficiency) == SKILL_LEVEL_LEGENDARY ) { continue; } @@ -5199,7 +5205,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int else if ( tag.compare("weapon_legendary_durability") == 0 ) { int proficiency = itemCategory(item) == ARMOR ? PRO_UNARMED: getWeaponSkill(item); - if ( stats[player]->getModifiedProficiency(proficiency) != SKILL_LEVEL_LEGENDARY ) + if ( (compendiumTooltip && intro) || stats[player]->getModifiedProficiency(proficiency) != SKILL_LEVEL_LEGENDARY ) { continue; } @@ -5463,7 +5469,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { continue; } - if ( stats[player]->getModifiedProficiency(PRO_SHIELD) == SKILL_LEVEL_LEGENDARY ) + if ( !(compendiumTooltip && intro) && stats[player]->getModifiedProficiency(PRO_SHIELD) == SKILL_LEVEL_LEGENDARY ) { continue; } @@ -5474,7 +5480,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int { continue; } - if ( stats[player]->getModifiedProficiency(PRO_SHIELD) != SKILL_LEVEL_LEGENDARY ) + if ( (compendiumTooltip && intro) || stats[player]->getModifiedProficiency(PRO_SHIELD) != SKILL_LEVEL_LEGENDARY ) { continue; } @@ -5490,7 +5496,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int tagText += '\n'; } } - ItemTooltips.formatItemDetails(player, tooltipType, *item, tagText, tag); + ItemTooltips.formatItemDetails(player, tooltipType, *item, tagText, tag, parentFrame); if ( detailsTextString.compare("") != 0 ) { detailsTextString += '\n'; diff --git a/src/items.cpp b/src/items.cpp index ac43a3032..a5d5938ac 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -5699,6 +5699,7 @@ bool Item::shouldItemStackInShop(bool ignoreStackLimit) bool shouldInvertEquipmentBeatitude(const Stat* const wielder) { + if ( !wielder ) { return false; } if ( wielder->type == SUCCUBUS || wielder->type == INCUBUS ) { return true; diff --git a/src/stat.cpp b/src/stat.cpp index 99f416f17..494d1786e 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -1400,11 +1400,11 @@ void Stat::copyNPCStatsAndInventoryFrom(Stat& src) intro = oldIntro; } -int Stat::getActiveShieldBonus(bool checkShield) const +int Stat::getActiveShieldBonus(bool checkShield, bool excludeSkill) const { if ( !checkShield ) { - return (5 + (getModifiedProficiency(PRO_SHIELD) / 5)); + return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); } if ( shield ) @@ -1413,7 +1413,7 @@ int Stat::getActiveShieldBonus(bool checkShield) const { return 0; } - return (5 + (getModifiedProficiency(PRO_SHIELD) / 5)); + return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); } else { @@ -1421,10 +1421,14 @@ int Stat::getActiveShieldBonus(bool checkShield) const } } -int Stat::getPassiveShieldBonus(bool checkShield) const +int Stat::getPassiveShieldBonus(bool checkShield, bool excludeSkill) const { if ( !checkShield ) { + if ( excludeSkill ) + { + return 0; + } return (getModifiedProficiency(PRO_SHIELD) / 25); } @@ -1435,6 +1439,10 @@ int Stat::getPassiveShieldBonus(bool checkShield) const { return 0; } + if ( excludeSkill ) + { + return 0; + } return (getModifiedProficiency(PRO_SHIELD) / 25); } else diff --git a/src/stat.hpp b/src/stat.hpp index 81b9cebd9..f1a5a787c 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -346,8 +346,8 @@ class Stat MONSTER_FORCE_PLAYER_ENEMY, MONSTER_FORCE_PLAYER_RECRUITABLE }; - int getPassiveShieldBonus(bool checkShield) const; - int getActiveShieldBonus(bool checkShield) const; + int getPassiveShieldBonus(bool checkShield, bool excludeSkill) const; + int getActiveShieldBonus(bool checkShield, bool excludeSkill) const; std::string getAttribute(std::string key) const { if ( attributes.find(key) != attributes.end() ) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index b26480781..5b81c80f3 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -32401,12 +32401,12 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& { if ( tag == "BLOCK_AC_INCREASE" ) { - val = stats[playernum]->getActiveShieldBonus(false); + val = stats[playernum]->getActiveShieldBonus(false, false); snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } else if ( tag == "PASSIVE_AC_INCREASE" ) { - val = stats[playernum]->getPassiveShieldBonus(false); + val = stats[playernum]->getPassiveShieldBonus(false, false); snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } else if ( tag == "BLOCK_DEGRADE_NORMAL_CHANCE" ) From 823485362d10a530b7082908c1da73e4627e582a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:26:16 +1000 Subject: [PATCH 056/244] * compendium item bless/repair widget, unread indicators --- src/ui/MainMenu.cpp | 1079 ++++++++++++++++++++++++++++++++++++++----- src/ui/Widget.hpp | 1 + 2 files changed, 970 insertions(+), 110 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index c9c315197..568b61e34 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -23938,9 +23938,9 @@ namespace MainMenu { Option options[] = { {"Dungeon Compendium", Language::get(5612), archivesDungeonCompendium}, #ifndef STEAMWORKS -#if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) - {"Achievements", Language::get(5611), archivesAchievements}, -#endif +//#if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) +// {"Achievements", Language::get(5611), archivesAchievements}, +//#endif #endif #ifdef NINTENDO {"Leaderboards", Language::get(5613), archivesLeaderboards}, @@ -23993,10 +23993,29 @@ namespace MainMenu { y += button->getSize().h; //y += 4; } + if ( !strcmp(options[c].name, "Dungeon Compendium") ) + { + button->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + Compendium_t::PointsAnim_t::countUnreadNotifs(); + if ( Compendium_t::PointsAnim_t::mainMenuAlert ) + { + if ( auto imgGet = Image::get("*images/ui/Main Menus/AdventureArchives/C_New_Icon_00.png") ) + { + int baseY = pos.y + 2; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + baseY += 2; + } + imgGet->drawColor(nullptr, SDL_Rect{ pos.x + pos.w - 24, baseY, 8, 28 }, + SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }, 0xFFFFFFFF); + } + } + }); + } } y += 16; - auto archives = buttons->findButton("Leaderboards"); + auto archives = buttons->findButton("Dungeon Compendium"); if (archives) { archives->select(); } @@ -25681,9 +25700,9 @@ namespace MainMenu { {"Assign Controllers", Language::get(5762), mainAssignControllers}, {"Dungeon Compendium", Language::get(5763), archivesDungeonCompendium}, #ifndef STEAMWORKS -#if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) - {"Achievements", Language::get(5764), archivesAchievements}, -#endif +//#if defined(USE_EOS) || defined(LOCAL_ACHIEVEMENTS) +// {"Achievements", Language::get(5764), archivesAchievements}, +//#endif #endif {"Settings", Language::get(5765), mainSettings}, }); @@ -25848,6 +25867,26 @@ namespace MainMenu { } y += button->getSize().h; //y += 4; + + if ( !strcmp(options[c].name, "Dungeon Compendium") ) + { + button->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + Compendium_t::PointsAnim_t::countUnreadNotifs(); + if ( Compendium_t::PointsAnim_t::mainMenuAlert ) + { + if ( auto imgGet = Image::get("*images/ui/Main Menus/AdventureArchives/C_New_Icon_00.png") ) + { + int baseY = pos.y + 2; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + baseY += 2; + } + imgGet->drawColor(nullptr, SDL_Rect{ pos.x + pos.w - 24, baseY, 8, 28 }, + SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }, 0xFFFFFFFF); + } + } + }); + } } y += 16; @@ -32702,18 +32741,18 @@ namespace MainMenu { if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = page_right_unlock->findButton("unlock_lore_cost") ) { if ( entry.lorePoints > 0 ) { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setColor(compendiumContentsSelectedColor); + unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); } else { - unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); } } else @@ -32788,6 +32827,7 @@ namespace MainMenu { Frame* interactFrame = nullptr; Frame* tooltipPromptFrame = nullptr; bool tooltipGamepadOpen = false; + bool tooltipPromptActive = false; void clear() { tooltipContainerFrame = nullptr; @@ -32796,6 +32836,7 @@ namespace MainMenu { interactFrame = nullptr; tooltipPromptFrame = nullptr; tooltipGamepadOpen = false; + tooltipPromptActive = false; } }; static CompendiumTooltipFrames_t compendiumItemTooltip; @@ -33995,6 +34036,51 @@ namespace MainMenu { || itemUnlock == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) { itemUnlock = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED; + + bool allRevealed = true; + for ( auto& i : contents ) + { + int itemID = i.itemID == SPELL_ITEM ? Compendium_t::Events_t::kEventSpellOffset + i.spellID : i.itemID; + auto find = Compendium_t::CompendiumItems_t::itemUnlocks.find(itemID); + if ( find != Compendium_t::CompendiumItems_t::itemUnlocks.end() ) + { + if ( find->second == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || find->second == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + allRevealed = false; + break; + } + } + } + if ( allRevealed ) + { + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED; + if ( main_menu_frame ) + { + if ( auto compendium = main_menu_frame->findFrame("compendium") ) + { + if ( auto nav = compendium->findFrame("nav") ) + { + if ( auto contents = nav->findFrame("contents") ) + { + if ( auto contents_notif = contents->findFrame("notifs") ) + { + std::string findImg = "notif_" + compendium_contents_current[compendium_current]; + if ( auto img = contents_notif->findImage(findImg.c_str()) ) + { + img->disabled = true; + } + } + } + } + } + } + } + } + Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + Compendium_t::PointsAnim_t::countUnreadNotifs(); } } } @@ -34094,18 +34180,18 @@ namespace MainMenu { if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = page_right_unlock->findButton("unlock_lore_cost") ) { if ( compendiumEntry.lorePoints > 0 ) { unlock_lore_cost->setText(std::to_string(compendiumEntry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= compendiumEntry.lorePoints ) { - unlock_lore_cost->setColor(compendiumContentsSelectedColor); + unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); } else { - unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); } } else @@ -34150,6 +34236,7 @@ namespace MainMenu { const int entrySize = 64; Compendium_t::compendiumItem.status = EXCELLENT; + Compendium_t::compendiumItem.beatitude = 0; Compendium_t::compendiumItem.count = 1; Compendium_t::compendiumItem.identified = true; @@ -34163,6 +34250,259 @@ namespace MainMenu { compendiumItemTooltip.interactFrame, compendiumItemTooltip.tooltipPromptFrame); compendiumItemTooltip.tooltipFrame->setDisabled(false); + if ( compendiumItemTooltip.tooltipContainerFrame ) + { + if ( false ) + { + auto item_widget = compendiumItemTooltip.tooltipContainerFrame->addFrame("item_widget"); + item_widget->setSize(SDL_Rect{ 360, 360, 144, 166 }); + item_widget->addImage(SDL_Rect{ 0, 0, 144, 166 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_ItemConditions_Panel_00.png", + "item_widget_bg"); + item_widget->setClickable(false); + item_widget->setInheritParentFrameOpacity(false); + item_widget->setOpacity(0.0); + item_widget->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto frame = const_cast((Frame*)(&widget)); + if ( auto parent = frame->getParent() ) + { + if ( auto tooltip = parent->findFrame("player tooltip 0") ) + { + if ( tooltip->getSize().w == 0 ) + { + frame->setOpacity(0.0); + } + else + { + frame->setOpacity(tooltip->getOpacity()); + } + if ( frame->getOpacity() > 0.001 ) + { + SDL_Rect framePos = frame->getSize(); + framePos.x = tooltip->getSize().x - framePos.w; + framePos.y = tooltip->getSize().y; + frame->setSize(framePos); + + // draw glyphs + bool pressed = false;// ticks% TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + auto color = makeColor(255, 255, 255, 255 * frame->getOpacity() / 100.0); + if ( auto txt = frame->findField("txt_1") ) + { + auto path = input.getGlyphPathForBinding("MenuUp", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2; + image->drawColor(nullptr, SDL_Rect{ x, y, w, h }, viewport, color); + } + if ( auto txt = frame->findField("txt_2") ) + { + auto path = input.getGlyphPathForBinding("MenuDown", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2; + image->drawColor(nullptr, SDL_Rect{ x, y, w, h }, viewport, color); + } + if ( auto txt = frame->findField("txt_3") ) + { + auto path = input.getGlyphPathForBinding("MenuRight", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2; + image->drawColor(nullptr, SDL_Rect{ x, y, w, h }, viewport, color); + } + if ( auto txt = frame->findField("txt_4") ) + { + auto path = input.getGlyphPathForBinding("MenuLeft", pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2; + image->drawColor(nullptr, SDL_Rect{ x, y, w, h }, viewport, color); + } + } + } + } + }); + + auto txt = item_widget->addField("txt_1", 64); + + SDL_Rect pos{ 54, 21, 86, 24 }; + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("BLESS +"); + txt->setColor(makeColorRGB(67, 195, 157)); + + pos.y += 36; + txt = item_widget->addField("txt_2", 64); + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("CURSE -"); + txt->setColor(hudColors.characterSheetRed); + + pos.y += 36; + txt = item_widget->addField("txt_3", 64); + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("REPAIR"); + txt->setColor(makeColorRGB(188, 154, 114)); + + pos.y += 36; + txt = item_widget->addField("txt_4", 64); + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("DAMAGE"); + txt->setColor(makeColorRGB(188, 154, 114)); + } + else + { + auto item_widget = compendiumItemTooltip.tooltipContainerFrame->addFrame("item_widget"); + item_widget->setSize(SDL_Rect{ 360, 360, 274, 50 }); + item_widget->addImage(SDL_Rect{ 0, 0, 274, 50 }, makeColor(255, 255, 255, 192), + "*images/ui/Main Menus/AdventureArchives/C_ItemConditions_Panel_01.png", + "item_widget_bg"); + item_widget->setClickable(false); + item_widget->setInheritParentFrameOpacity(false); + item_widget->setOpacity(0.0); + item_widget->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + auto frame = const_cast((Frame*)(&widget)); + if ( auto parent = frame->getParent() ) + { + if ( auto tooltip = parent->findFrame("player tooltip 0") ) + { + if ( frame->getOpacity() > 0.001 ) + { + // draw glyphs + bool pressed = false; // ticks% TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + Input& input = Input::inputs[getMenuOwner()]; + const SDL_Rect viewport{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }; + auto color = makeColor(255, 255, 255, 255 * frame->getOpacity() / 100.0); + const int yoffset = -2; + if ( auto txt = frame->findField("txt_1") ) + { + bool toggle = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; + char* binding = "MenuPageRight"; + if ( input.input("MenuUp").isBindingUsingKeyboard() ) + { + binding = "MenuUp"; + } + pressed = input.binary(binding); + auto path = input.getGlyphPathForBinding(binding, pressed); + Image* image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x1 = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y1 = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2 + yoffset; + image->drawColor(nullptr, SDL_Rect{ x1, y1, w, h }, viewport, color); + + binding = "MenuPageLeft"; + if ( input.input("MenuDown").isBindingUsingKeyboard() ) + { + binding = "MenuDown"; + } + pressed = input.binary(binding); + path = input.getGlyphPathForBinding(binding, pressed); + image = Image::get((std::string("*") + path).c_str()); + w = image->getWidth(); + h = image->getHeight(); + int x2 = x1 - 4 - w; + int y2 = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2 + yoffset; + image->drawColor(nullptr, SDL_Rect{ x2, y2, w, h }, viewport, color); + } + if ( auto txt = frame->findField("txt_2") ) + { + char* binding = "MenuPageRightAlt"; + if ( input.input("MenuRight").isBindingUsingKeyboard() ) + { + binding = "MenuRight"; + } + pressed = input.binary(binding); + auto path = input.getGlyphPathForBinding(binding, pressed); + auto image = Image::get((std::string("*") + path).c_str()); + int w = image->getWidth(); + int h = image->getHeight(); + int x1 = frame->getAbsoluteSize().x + txt->getSize().x - 8 - w; + int y1 = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2 + yoffset; + image->drawColor(nullptr, SDL_Rect{ x1, y1, w, h }, viewport, color); + + binding = "MenuPageLeftAlt"; + if ( input.input("MenuLeft").isBindingUsingKeyboard() ) + { + binding = "MenuLeft"; + } + pressed = input.binary(binding); + path = input.getGlyphPathForBinding(binding, pressed); + image = Image::get((std::string("*") + path).c_str()); + w = image->getWidth(); + h = image->getHeight(); + int x2 = x1 - 4 - w; + int y2 = frame->getAbsoluteSize().y + txt->getSize().y + 8 + 2 - h / 2 + yoffset; + image->drawColor(nullptr, SDL_Rect{ x2, y2, w, h }, viewport, color); + } + } + } + } + }); + + auto txt = item_widget->addField("txt_1", 64); + + SDL_Rect pos{ 8 + 32 + 4 + 32 + 8, 21 - 16 + 10, 86, 24 }; + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("BLESS"); + txt->setColor(makeColorRGB(67, 195, 157)); + + txt = item_widget->addField("txt_2", 64); + txt->setFont(smallfont_outline); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("REPAIR"); + txt->setColor(makeColorRGB(188, 154, 114)); + if ( auto textGet = txt->getTextObject() ) + { + pos.x = item_widget->getSize().w; + pos.x -= textGet->getWidth(); + pos.x -= 12; + txt->setSize(pos); + } + + /*pos.y += 36; + txt = item_widget->addField("txt_3", 64); + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("REPAIR"); + txt->setColor(makeColorRGB(188, 154, 114)); + + pos.y += 36; + txt = item_widget->addField("txt_4", 64); + txt->setFont(smallfont_outline); + txt->setSize(pos); + txt->setHJustify(Field::justify_t::LEFT); + txt->setVJustify(Field::justify_t::TOP); + txt->setText("DAMAGE"); + txt->setColor(makeColorRGB(188, 154, 114));*/ + } + } } compendiumItemTooltip.clear(); @@ -34218,7 +34558,18 @@ namespace MainMenu { } else { - frame->setWidgetBack("right_back_button"); + if ( compendiumItemTooltip.tooltipGamepadOpen ) + { + frame->removeWidgetAction("MenuCancel"); + frame->removeWidgetAction("MenuPageLeft"); + frame->removeWidgetAction("MenuPageRight"); + } + else + { + frame->setWidgetBack("right_back_button"); + frame->addWidgetAction("MenuPageLeft", "tab_left"); + frame->addWidgetAction("MenuPageRight", "tab_right"); + } } if ( Frame* page_right_inner = frame->getParent() ) { @@ -34369,11 +34720,19 @@ namespace MainMenu { { if ( selectable ) { - soundActivate(); selectCompendiumItemInList(*frame, *page_right_inner, false, true); if ( !isMouseVisible() ) { - compendiumItemTooltip.tooltipGamepadOpen = !compendiumItemTooltip.tooltipGamepadOpen; + if ( compendiumItemTooltip.tooltipGamepadOpen ) + { + soundCancel(); + compendiumItemTooltip.tooltipGamepadOpen = false; + } + else + { + soundActivate(); + compendiumItemTooltip.tooltipGamepadOpen = true; + } } } else @@ -34388,6 +34747,11 @@ namespace MainMenu { { compendiumItemTooltip.tooltipGamepadOpen = false; } + else if ( compendiumItemTooltip.tooltipGamepadOpen && Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuCancel") ) + { + soundCancel(); + compendiumItemTooltip.tooltipGamepadOpen = false; + } if ( selector_bg ) { selector_bg->disabled = false; @@ -34428,6 +34792,139 @@ namespace MainMenu { Compendium_t::compendiumItem.appearance = (reinterpret_cast(frame->getUserData()) & 0x7F); } + if ( Compendium_t::compendiumItem.type == SPELL_ITEM ) + { + compendiumItemTooltip.tooltipPromptActive = false; + } + else + { + compendiumItemTooltip.tooltipPromptActive = true; + } + + if ( compendiumItemTooltip.tooltipPromptActive ) + { + if ( Input::inputs[getMenuOwner()].input("MenuUp").isBindingUsingKeyboard() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuUp") ) + { + if ( Compendium_t::compendiumItem.beatitude >= 9 ) + { + soundError(); + } + else + { + soundMove(); + Compendium_t::compendiumItem.beatitude++; + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuDown").isBindingUsingKeyboard() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuDown") ) + { + if ( Compendium_t::compendiumItem.beatitude <= -9 ) + { + soundError(); + } + else + { + soundMove(); + Compendium_t::compendiumItem.beatitude--; + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuPageLeft").isBindingUsingGamepad() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuPageLeft") ) + { + if ( Compendium_t::compendiumItem.beatitude <= -9 ) + { + soundError(); + } + else + { + soundMove(); + Compendium_t::compendiumItem.beatitude--; + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuPageRight").isBindingUsingGamepad() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuPageRight") ) + { + if ( Compendium_t::compendiumItem.beatitude >= 9 ) + { + soundError(); + } + else + { + soundMove(); + Compendium_t::compendiumItem.beatitude++; + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuLeft").isBindingUsingKeyboard() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuLeft") ) + { + if ( Compendium_t::compendiumItem.status > DECREPIT ) + { + soundMove(); + Compendium_t::compendiumItem.status = (Status)(Compendium_t::compendiumItem.status - 1); + } + else + { + soundError(); + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuRight").isBindingUsingKeyboard() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuRight") ) + { + if ( Compendium_t::compendiumItem.status < EXCELLENT ) + { + soundMove(); + Compendium_t::compendiumItem.status = (Status)(Compendium_t::compendiumItem.status + 1); + } + else + { + soundError(); + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuPageLeftAlt").isBindingUsingGamepad() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuPageLeftAlt") ) + { + if ( Compendium_t::compendiumItem.status > DECREPIT ) + { + soundMove(); + Compendium_t::compendiumItem.status = (Status)(Compendium_t::compendiumItem.status - 1); + } + else + { + soundError(); + } + } + } + if ( Input::inputs[getMenuOwner()].input("MenuPageRightAlt").isBindingUsingGamepad() ) + { + if ( Input::inputs[getMenuOwner()].consumeBinaryToggle("MenuPageRightAlt") ) + { + if ( Compendium_t::compendiumItem.status < EXCELLENT ) + { + soundMove(); + Compendium_t::compendiumItem.status = (Status)(Compendium_t::compendiumItem.status + 1); + } + else + { + soundError(); + } + } + } + } + Compendium_t::tooltipPos.x = absolutePos.x - 16; Compendium_t::tooltipPos.y = absolutePos.y; Compendium_t::tooltipNeedUpdate = true; @@ -35092,18 +35589,18 @@ namespace MainMenu { if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = page_right_unlock->findButton("unlock_lore_cost") ) { if ( entry.lorePoints > 0 ) { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setColor(compendiumContentsSelectedColor); + unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); } else { - unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); } } else @@ -35325,18 +35822,18 @@ namespace MainMenu { if ( Frame* page_right_unlock = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = page_right_unlock->findButton("unlock_lore_cost") ) { if ( entry.lorePoints > 0 ) { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setColor(compendiumContentsSelectedColor); + unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); } else { - unlock_lore_cost->setColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); } } else @@ -35742,9 +36239,10 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = reveal_frame->findButton("unlock_lore_cost") ) { unlock_lore_cost->setDisabled(true); + unlock_lore_cost->setInvisible(true); } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) { @@ -35779,9 +36277,10 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = reveal_frame->findButton("unlock_lore_cost") ) { unlock_lore_cost->setDisabled(true); + unlock_lore_cost->setInvisible(true); } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) { @@ -35816,9 +36315,10 @@ namespace MainMenu { } if ( auto reveal_frame = parent->findFrame("page_right_unlock") ) { - if ( auto unlock_lore_cost = reveal_frame->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = reveal_frame->findButton("unlock_lore_cost") ) { - unlock_lore_cost->setDisabled(false); + unlock_lore_cost->setDisabled(true); + unlock_lore_cost->setInvisible(false); unlock_lore_cost->setText(""); } if ( auto reveal_txt = reveal_frame->findField("to_unlock") ) @@ -35952,11 +36452,37 @@ namespace MainMenu { { if ( findUnlock->second == Compendium_t::UNLOCKED_UNVISITED ) { - findUnlock->second = Compendium_t::UNLOCKED_VISITED; + bool allRevealed = true; + if ( compendium_current == "items" || compendium_current == "magic" ) + { + auto& compendiumEntry = (compendium_current == "items") ? CompendiumEntries.items[entry.first] : CompendiumEntries.magic[entry.first]; + for ( auto& i : compendiumEntry.items_in_category ) + { + int itemID = i.itemID == SPELL_ITEM ? Compendium_t::Events_t::kEventSpellOffset + i.spellID : i.itemID; + auto find = Compendium_t::CompendiumItems_t::itemUnlocks.find(itemID); + if ( find != Compendium_t::CompendiumItems_t::itemUnlocks.end() ) + { + if ( find->second == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || find->second == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + allRevealed = false; + break; + } + } + } + } + if ( allRevealed ) + { + findUnlock->second = Compendium_t::UNLOCKED_VISITED; + Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + Compendium_t::PointsAnim_t::countUnreadNotifs(); + } } else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_UNVISITED ) { findUnlock->second = Compendium_t::LOCKED_REVEALED_VISITED; + Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + Compendium_t::PointsAnim_t::countUnreadNotifs(); } if ( findUnlock->second == Compendium_t::UNLOCKED_VISITED @@ -35984,7 +36510,6 @@ namespace MainMenu { { if ( auto notifs = contentsFrame->findFrame("notifs") ) { - std::vector toRemove; for ( auto img : notifs->getImages() ) { std::string name = ""; @@ -36002,15 +36527,11 @@ namespace MainMenu { if ( findUnlock->second == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED || findUnlock->second == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) { - toRemove.push_back(img->name); + img->disabled = true; } } } } - for ( auto& r : toRemove ) - { - notifs->remove(r.c_str()); - } } } } @@ -36476,14 +36997,15 @@ namespace MainMenu { } } - if ( drawNotification ) + contents_notif->setSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getActualSize().h }); + if ( data.first != "-" ) { - contents_notif->setSize(SDL_Rect{ 0, 0, contents->getSize().w, contents->getActualSize().h }); std::string imgName = "notif_" + data.first; - contents_notif->addImage(SDL_Rect{ 4, 4 + (int)(contents->getEntries().size() - 1) * contents->getEntrySize(), 6, 14 }, + auto img = contents_notif->addImage(SDL_Rect{ 4, 4 + (int)(contents->getEntries().size() - 1) * contents->getEntrySize(), 6, 14 }, 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/ExclamationAnim00.png", imgName.c_str()); + img->disabled = !drawNotification; } } @@ -38066,14 +38588,64 @@ namespace MainMenu { } else if ( findUnlock->second == Compendium_t::LOCKED_REVEALED_VISITED ) { - findUnlock->second = Compendium_t::UNLOCKED_VISITED; + bool allRevealed = true; + if ( compendium_current == "items" || compendium_current == "magic" ) + { + auto& compendiumEntry = (compendium_current == "items") ? CompendiumEntries.items[compendium_contents_current[compendium_current]] : CompendiumEntries.magic[compendium_contents_current[compendium_current]]; + for ( auto i : compendiumEntry.items_in_category ) + { + int itemID = i.itemID == SPELL_ITEM ? Compendium_t::Events_t::kEventSpellOffset + i.spellID : i.itemID; + auto find = Compendium_t::CompendiumItems_t::itemUnlocks.find(itemID); + if ( find != Compendium_t::CompendiumItems_t::itemUnlocks.end() ) + { + if ( find->second == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED + || find->second == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + allRevealed = false; + break; + } + } + } + } + if ( allRevealed ) + { + findUnlock->second = Compendium_t::UNLOCKED_VISITED; + } + else + { + findUnlock->second = Compendium_t::UNLOCKED_UNVISITED; + + if ( main_menu_frame ) + { + if ( auto compendium = main_menu_frame->findFrame("compendium") ) + { + if ( auto nav = compendium->findFrame("nav") ) + { + if ( auto contents = nav->findFrame("contents") ) + { + if ( auto contents_notif = contents->findFrame("notifs") ) + { + std::string findImg = "notif_" + compendium_contents_current[compendium_current]; + if ( auto img = contents_notif->findImage(findImg.c_str()) ) + { + img->disabled = false; + } + } + } + } + } + } + } } + Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + Compendium_t::PointsAnim_t::countUnreadNotifs(); if ( compendium_current == "items" || compendium_current == "magic" ) { refreshCompendiumEntryItemsList(compendium_contents_current[compendium_current], parent, true); } } } + Compendium_t::PointsAnim_t::pointsChangeEvent(-lorePointCost); Compendium_t::updateLorePointCounts(); } } @@ -38081,13 +38653,14 @@ namespace MainMenu { if ( !success ) { - soundError(); + Compendium_t::PointsAnim_t::noFundsEvent(); return; } - if ( auto unlock_lore_cost = page_right_unlock->findField("unlock_lore_cost") ) + if ( auto unlock_lore_cost = page_right_unlock->findButton("unlock_lore_cost") ) { unlock_lore_cost->setDisabled(true); + unlock_lore_cost->setInvisible(true); } } @@ -38227,7 +38800,28 @@ namespace MainMenu { players[getMenuOwner()]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; compendiumItemTooltip.clear(); - Compendium_t::updateLorePointCounts(); + if ( !Compendium_t::PointsAnim_t::firstLoad ) + { + auto balanceOld = Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent; + Compendium_t::updateLorePointCounts(); + auto balanceCurrent = Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent; + if ( balanceOld < balanceCurrent ) + { + int diff = balanceCurrent - balanceOld; + Compendium_t::lorePointsFromAchievements -= diff; // tmp hack to make the anim count up + Compendium_t::PointsAnim_t::pointsChangeEvent(diff); + Compendium_t::lorePointsFromAchievements += diff; + } + } + else + { + Compendium_t::updateLorePointCounts(); + Compendium_t::PointsAnim_t::pointsChangeEvent(0); + } + if ( !Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + Compendium_t::PointsAnim_t::firstLoad = false; + } auto dimmer = main_menu_frame->addFrame("dimmer"); dimmer->setSize(SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY }); @@ -38260,28 +38854,74 @@ namespace MainMenu { auto lore_points = window->addFrame("lore_points_balance"); lore_points->setHollow(true); lore_points->setClickable(false); - lore_points->setSize(SDL_Rect{ window->getSize().w - 604, 0, 194, 50 + offset_y }); + lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); + "*images/ui/Main Menus/AdventureArchives/C_LP_Available_00.png", "lore_points_bg"); lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); Field* txt = lore_points->addField("lore_points_current", 32); txt->setText(""); txt->setFont("fonts/kongtext.ttf#16#2"); txt->setColor(makeColorRGB(158, 146, 132)); - txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + txt->setSize(SDL_Rect{ 0, 16 + offset_y, 172 - 64, 24 }); txt->setVJustify(Field::justify_t::TOP); txt->setHJustify(Field::justify_t::RIGHT); + txt->setDrawCallback([](const Widget& widget, SDL_Rect pos) { + Compendium_t::PointsAnim_t::tickAnimate(); + auto txt = const_cast((Field*)(&widget)); + SDL_Rect size = txt->getSize(); + size.x = 0; + if ( Compendium_t::PointsAnim_t::noFundsAnimate ) + { + size.x += -2 + 2 * (cos(Compendium_t::PointsAnim_t::animNoFunds * 4 * PI)); + } + txt->setSize(size); + }); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + Compendium_t::PointsAnim_t::noFundsEvent(); + }*/ + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + if ( keystatus[SDLK_LSHIFT] ) + { + Compendium_t::PointsAnim_t::pointsChangeEvent(-6); + } + else + { + Compendium_t::PointsAnim_t::pointsChangeEvent(10); + } + }*/ + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) { txt->setText("..."); } else { - int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent); - txt->setText(std::to_string(lorePointsCurrent).c_str()); + if ( !strcmp(txt->getText(), "...") ) + { + Compendium_t::updateLorePointCounts(); + Compendium_t::PointsAnim_t::pointsChangeEvent(0); + } + //int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent); + static ConsoleVariable cvar_lore_point_highlight("/compendium_lore_point_highlight", Vector4{ 63, 64, -48, 0 }); + if ( Compendium_t::PointsAnim_t::noFundsAnimate ) + { + txt->setColor(makeColor(215, 38, 61, 255)); // red + } + else + { + txt->setColor(makeColorRGB(158 + cvar_lore_point_highlight->x * Compendium_t::PointsAnim_t::anim, + 146 + cvar_lore_point_highlight->y * Compendium_t::PointsAnim_t::anim, + 132 + cvar_lore_point_highlight->z * Compendium_t::PointsAnim_t::anim)); + } + std::string str = std::to_string(Compendium_t::PointsAnim_t::txtCurrentPoints).c_str(); + txt->setText(str.c_str()); } }); @@ -38292,78 +38932,163 @@ namespace MainMenu { txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24 }); txt->setVJustify(Field::justify_t::TOP); txt->setHJustify(Field::justify_t::RIGHT); - } - { - const int offset_y = 14; - auto lore_points = window->addFrame("lore_points_total"); - lore_points->setHollow(true); - lore_points->setClickable(false); - lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); - lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); - lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); - Field* txt = lore_points->addField("lore_points_current", 32); + txt = window->addField("lore_points_anim", 32); txt->setText(""); txt->setFont("fonts/kongtext.ttf#16#2"); txt->setColor(makeColorRGB(158, 146, 132)); - txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + txt->setSize(SDL_Rect{ lore_points->getSize().x - 32 + 112, lore_points->getSize().y + 16 + offset_y, 92, 24}); txt->setVJustify(Field::justify_t::TOP); txt->setHJustify(Field::justify_t::RIGHT); + txt->setText(""); + txt->setOntop(true); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); - if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + if ( auto parent = static_cast(txt->getParent()) ) { - txt->setText("..."); + if ( auto lore_points_balance = parent->findFrame("lore_points_balance") ) + { + if ( auto lore_points_current = lore_points_balance->findField("lore_points_current") ) + { + txt->setColor(lore_points_current->getColor()); + SDL_Rect pos1 = lore_points_current->getSize(); + SDL_Rect pos2 = txt->getSize(); + pos2.x = 958 + pos1.x; + txt->setSize(pos2); + } + } + } + if ( Compendium_t::PointsAnim_t::showChanged ) + { + if ( Compendium_t::PointsAnim_t::txtChangePoints >= 0 ) + { + txt->setColor(makeColorRGB(67, 195, 157)); + std::string str = "+" + std::to_string(Compendium_t::PointsAnim_t::txtChangePoints); + txt->setText(str.c_str()); + } + else + { + //txt->setColor(hudColors.characterSheetRed); + //txt->setText(std::to_string(Compendium_t::PointsAnim_t::txtChangePoints).c_str()); + //txt->setColor(makeColorRGB(158, 146, 132)); + txt->setText(Language::get(6209)); + } } else { - int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements); - txt->setText(std::to_string(lorePointsCurrent).c_str()); + //txt->setColor(makeColorRGB(158, 146, 132)); + txt->setText(Language::get(6209)); } }); + } + + //if ( false ) // not in use + //{ + // const int offset_y = 14; + // auto lore_points = window->addFrame("lore_points_total"); + // lore_points->setHollow(true); + // lore_points->setClickable(false); + // lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); + // lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, + // "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); + // lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, + // "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); + // Field* txt = lore_points->addField("lore_points_current", 32); + // txt->setText(""); + // txt->setFont("fonts/kongtext.ttf#16#2"); + // txt->setColor(makeColorRGB(158, 146, 132)); + // txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::RIGHT); + // txt->setTickCallback([](Widget& widget) { + // Field* txt = static_cast(&widget); + // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + // { + // txt->setText("..."); + // } + // else + // { + // int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements); + // txt->setText(std::to_string(lorePointsCurrent).c_str()); + // } + // }); + + // txt = lore_points->addField("lore_points_total", 32); + // txt->setText("/"); + // txt->setFont("fonts/kongtext.ttf#16#2"); + // txt->setColor(makeColorRGB(255, 255, 255)); + // txt->setSize(SDL_Rect{ 100, 16 + offset_y, 94, 24 }); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::LEFT); + // txt->setTickCallback([](Widget& widget) { + // Field* txt = static_cast(&widget); + // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + // { + // txt->setText("..."); + // } + // else + // { + // std::string amt = "/" + std::to_string(Compendium_t::lorePointsAchievementsTotal); + // txt->setText(amt.c_str()); + // } + // }); + + // txt = lore_points->addField("lore_points_label", 64); + // txt->setText("LORE POINTS EARNED"); + // txt->setFont(smallfont_outline); + // txt->setColor(makeColorRGB(192, 192, 192)); + // txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24}); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::RIGHT); + //} - txt = lore_points->addField("lore_points_total", 32); - txt->setText("/"); + { + const int offset_y = 14; + auto completion = window->addFrame("completion"); + completion->setHollow(true); + completion->setClickable(false); + completion->setSize(SDL_Rect{ window->getSize().w - 200, 0, 152, 50 + offset_y }); + completion->addImage(SDL_Rect{ 0, offset_y, 152, 50 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "completion_bg"); + completion->addImage(SDL_Rect{ 10, 10 + offset_y, 38, 28 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_Icon_00.png", "completion_icon"); + Field* txt = completion->addField("completion_percent", 32); + txt->setText(""); txt->setFont("fonts/kongtext.ttf#16#2"); - txt->setColor(makeColorRGB(255, 255, 255)); - txt->setSize(SDL_Rect{ 100, 16 + offset_y, 94, 24 }); + txt->setColor(makeColorRGB(158, 146, 132)); + txt->setSize(SDL_Rect{ 0, 16 + offset_y, 130, 24 }); txt->setVJustify(Field::justify_t::TOP); - txt->setHJustify(Field::justify_t::LEFT); + txt->setHJustify(Field::justify_t::RIGHT); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); - if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) - { - txt->setText("..."); - } - else - { - std::string amt = "/" + std::to_string(Compendium_t::lorePointsAchievementsTotal); - txt->setText(amt.c_str()); - } + int totalCompletion = Compendium_t::AchievementData_t::completionPercent; + totalCompletion += Compendium_t::CompendiumCodex_t::completionPercent; + totalCompletion += Compendium_t::CompendiumWorld_t::completionPercent; + totalCompletion += Compendium_t::CompendiumMonsters_t::completionPercent; + totalCompletion += Compendium_t::CompendiumItems_t::completionPercent; + totalCompletion += Compendium_t::CompendiumMagic_t::completionPercent; + totalCompletion /= 6; + totalCompletion = std::min(100, std::max(0, totalCompletion)); + std::string str = std::to_string(totalCompletion).c_str(); + str += '%'; + txt->setText(str.c_str()); + + real_t percent = totalCompletion / 100.0; + static ConsoleVariable cvar_completion_point_highlight("/cvar_completion_point_highlight", Vector4{ 63, 64, -48, 0 }); + txt->setColor(makeColorRGB(158 + cvar_completion_point_highlight->x * percent, + 146 + cvar_completion_point_highlight->y * percent, + 132 + cvar_completion_point_highlight->z * percent)); }); - txt = lore_points->addField("lore_points_label", 64); - txt->setText("LORE POINTS EARNED"); + txt = completion->addField("completion_label", 64); + txt->setText(Language::get(6210)); txt->setFont(smallfont_outline); txt->setColor(makeColorRGB(192, 192, 192)); - txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24}); + txt->setSize(SDL_Rect{ 0, 1, completion->getSize().w - 4, 24 }); txt->setVJustify(Field::justify_t::TOP); txt->setHJustify(Field::justify_t::RIGHT); } - { - auto completion = window->addFrame("completion"); - completion->setHollow(true); - completion->setClickable(false); - completion->setSize(SDL_Rect{ window->getSize().w - 200, 8, 194, 50 }); - completion->addImage(SDL_Rect{ 0, 0, 194, 50 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "completion_bg"); - completion->addImage(SDL_Rect{ 10, 10, 38, 28 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_Icon_00.png", "completion_icon"); - } - (void)createBackWidget(window, [](Button& button) { soundCancel(); @@ -38480,11 +39205,12 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { auto field = static_cast(&widget); + Compendium_t::PointsAnim_t::countUnreadNotifs(); if ( compendium_current == field->getName() ) { field->setColor(tabTextColorActive); @@ -38497,6 +39223,21 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::CompendiumMonsters_t::completionPercent); str += '%'; + + if ( Compendium_t::CompendiumMonsters_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } field->setText(str.c_str()); }); @@ -38588,7 +39329,7 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { @@ -38605,6 +39346,22 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::CompendiumItems_t::completionPercent); str += '%'; + + if ( Compendium_t::CompendiumItems_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } + field->setText(str.c_str()); }); @@ -38667,7 +39424,7 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { @@ -38684,6 +39441,22 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::CompendiumMagic_t::completionPercent); str += '%'; + + if ( Compendium_t::CompendiumMagic_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } + field->setText(str.c_str()); }); @@ -38746,7 +39519,7 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { @@ -38763,6 +39536,22 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::CompendiumWorld_t::completionPercent); str += '%'; + + if ( Compendium_t::CompendiumWorld_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } + field->setText(str.c_str()); }); @@ -38826,7 +39615,7 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { @@ -38843,6 +39632,22 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::CompendiumCodex_t::completionPercent); str += '%'; + + if ( Compendium_t::CompendiumCodex_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } + field->setText(str.c_str()); }); @@ -38955,7 +39760,7 @@ namespace MainMenu { tab_title->setFont(smallfont_outline); tab_title->setOntop(true); tab_title->setColor(makeColorRGB(220, 178, 113)); - tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, tab->getSize().h }); + tab_title->setSize(SDL_Rect{ tab->getSize().x, tab->getSize().y + tab_title_y, tab->getSize().w, 96 }); tab_title->setVJustify(Field::justify_t::TOP); tab_title->setHJustify(Field::justify_t::CENTER); tab_title->setTickCallback([](Widget& widget) { @@ -38972,6 +39777,22 @@ namespace MainMenu { str += '\n'; str += std::to_string(Compendium_t::AchievementData_t::completionPercent); str += '%'; + + if ( Compendium_t::AchievementData_t::numUnread > 0 ) + { + str += '\n'; + str += '\n'; + str += '\x1E'; + if ( (ticks % TICKS_PER_SECOND) < (TICKS_PER_SECOND / 2) ) + { + field->addColorToLine(3, makeColorRGB(43, 254, 207)); + } + else + { + field->addColorToLine(3, makeColorRGB(30, 203, 177)); + } + } + field->setText(str.c_str()); }); @@ -39113,6 +39934,8 @@ namespace MainMenu { nav_filter_sort->setWidgetDown("nav_filter_sort2"); nav_filter_sort->addWidgetAction("MenuPageLeft", "tab_left"); nav_filter_sort->addWidgetAction("MenuPageRight", "tab_right"); + nav_filter_sort->setGlyphPosition(Widget::CENTERED_LEFT); + nav_filter_sort->setButtonsOffset(SDL_Rect{ -16, 0, 0, 0 }); nav_filter_sort->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( button->isSelected() ) @@ -39199,6 +40022,8 @@ namespace MainMenu { nav_filter_sort2->setWidgetUp("nav_filter_sort"); nav_filter_sort2->addWidgetAction("MenuPageLeft", "tab_left"); nav_filter_sort2->addWidgetAction("MenuPageRight", "tab_right"); + nav_filter_sort2->setGlyphPosition(Widget::CENTERED_LEFT); + nav_filter_sort2->setButtonsOffset(SDL_Rect{ -16, 0, 0, 0 }); nav_filter_sort2->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); if ( button->isSelected() ) @@ -39306,15 +40131,8 @@ namespace MainMenu { } else { - auto w = frame->getWidgetActions(); - if ( w.find("MenuLeft") != w.end() ) - { - w.erase("MenuLeft"); - } - if ( w.find("MenuRight") != w.end() ) - { - w.erase("MenuRight"); - } + frame->removeWidgetAction("MenuLeft"); + frame->removeWidgetAction("MenuRight"); } if ( !isMouseVisible() ) @@ -39927,12 +40745,21 @@ namespace MainMenu { auto page_right_unlock_btn = page_right_unlock->addButton("page_right_unlock_btn"); page_right_unlock_btn->setSize(SDL_Rect{ 0, 0, 376, 116 }); + page_right_unlock_btn->setHighlightColor(0xFFFFFFFF); + page_right_unlock_btn->setColor(0xFFFFFFFF); page_right_unlock_btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); page_right_unlock_btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); page_right_unlock_btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-press_00.png"); page_right_unlock_btn->setOntop(true); page_right_unlock_btn->setWidgetSearchParent("compendium"); page_right_unlock_btn->setWidgetBack("back_button"); + page_right_unlock_btn->setWidgetLeft("contents"); + page_right_unlock_btn->setWidgetRight("contents"); + page_right_unlock_btn->setWidgetUp("contents"); + page_right_unlock_btn->setWidgetDown("contents"); + page_right_unlock_btn->addWidgetAction("MenuConfirm", "FraggleMaggleStiggleWortz"); // some garbage so that this glyph isn't auto-bound + page_right_unlock_btn->setMenuConfirmControlType(0); + page_right_unlock_btn->setButtonsOffset(SDL_Rect{ 0, -20, 0, 0 }); page_right_unlock_btn->addWidgetAction("MenuPageLeft", "tab_left"); page_right_unlock_btn->addWidgetAction("MenuPageRight", "tab_right"); page_right_unlock_btn->addWidgetMovement("MenuAlt2", "nav_filter_sort"); @@ -39941,14 +40768,46 @@ namespace MainMenu { compendiumRevealSection(&button); }); - auto reveal_unlock_cost = page_right_unlock->addField("unlock_lore_cost", 32); - reveal_unlock_cost->setSize(SDL_Rect{ page_right_unlock_btn->getSize().w - 64, page_right_unlock_btn->getSize().h / 2 - 24 / 2, 24, 24 }); + /*auto reveal_unlock_badge = page_right_unlock->addImage(SDL_Rect{ 376 - 74, 28, 56, 62 }, + 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/A_Icon_BaronyShield_Large_00.png", + "unlock_badge"); + reveal_unlock_badge->ontop = true;*/ + auto unlock_research_txt = page_right_unlock->addField("unlock_research_txt", 32); + unlock_research_txt->setHJustify(Field::justify_t::LEFT); + unlock_research_txt->setVJustify(Field::justify_t::TOP); + unlock_research_txt->setText("RESEARCH"); + unlock_research_txt->setDisabled(false); + unlock_research_txt->setSize(SDL_Rect{ 30 - 4, 44 - 4, 272, 62 }); + unlock_research_txt->setFont("fonts/kongtext.ttf#32#0"); + unlock_research_txt->setOntop(true); + unlock_research_txt->setColor(makeColor(166, 166, 166, 255)); + unlock_research_txt->setInvisible(true); + unlock_research_txt->setTickCallback([](Widget& widget) { + auto parent = static_cast(widget.getParent()); + if ( parent ) + { + if ( auto btn = parent->findButton("unlock_lore_cost") ) + { + widget.setInvisible(btn->isInvisible()); + } + } + }); + + auto reveal_unlock_cost = page_right_unlock->addButton("unlock_lore_cost"); reveal_unlock_cost->setHJustify(Field::justify_t::CENTER); - reveal_unlock_cost->setVJustify(Field::justify_t::TOP); + reveal_unlock_cost->setVJustify(Field::justify_t::CENTER); reveal_unlock_cost->setText("-"); - reveal_unlock_cost->setColor(makeColorRGB(128, 128, 128)); - reveal_unlock_cost->setFont(bigfont_outline); + reveal_unlock_cost->setDisabled(true); + reveal_unlock_cost->setSize(SDL_Rect{ 376 - 74, 28, 56, 62 }); + reveal_unlock_cost->setTextOffset(SDL_Rect{ -2, -2, 0, 0 }); + reveal_unlock_cost->setFont("fonts/kongtext.ttf#32#2"); reveal_unlock_cost->setOntop(true); + reveal_unlock_cost->setHighlightColor(0xFFFFFFFF); + reveal_unlock_cost->setColor(0xFFFFFFFF); + reveal_unlock_cost->setBackground("*images/ui/Main Menus/AdventureArchives/A_Icon_BaronyShield_Large_00.png"); + reveal_unlock_cost->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_Icon_BaronyShield_Large_00.png"); + reveal_unlock_cost->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/A_Icon_BaronyShield_Large_00.png"); auto right_bg = window->addImage(SDL_Rect{ page_right->getSize().x + 4, page_right->getSize().y + 8, 386, 472 }, 0xFFFFFFFF, diff --git a/src/ui/Widget.hpp b/src/ui/Widget.hpp index 41f6b117f..211a7d32e 100644 --- a/src/ui/Widget.hpp +++ b/src/ui/Widget.hpp @@ -82,6 +82,7 @@ class Widget { void setWidgetPageLeft(const char* s) { widgetActions["MenuPageLeft"] = s; } void setWidgetPageRight(const char* s) { widgetActions["MenuPageRight"] = s; } void setWidgetBack(const char* s) { widgetActions["MenuCancel"] = s; } + void removeWidgetAction(const char* binding) { if ( widgetActions.find(binding) != widgetActions.end() ) { widgetActions.erase(binding); } } void setWidgetSearchParent(const char* s) { widgetSearchParent = s; } void addWidgetAction(const char* binding, const char* action) { widgetActions[binding] = action; } void addWidgetMovement(const char* binding, const char* action) { widgetMovements[binding] = action; } From e84d67aa2ff7ea956628be57c06035b611f4dbd7 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:27:33 +1000 Subject: [PATCH 057/244] * compendium tooltip show 0 stats * unread indicators --- src/mod_tools.cpp | 476 +++++++++++++++++++++++++++++++++++++++++----- src/mod_tools.hpp | 35 +++- 2 files changed, 456 insertions(+), 55 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index bee521d65..7e4ee0bfe 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -2080,7 +2080,7 @@ bool ItemTooltips_t::bSpellHasBasicHitMessage(const int spellID) return false; } -int ItemTooltips_t::getSpellDamageOrHealAmount(const int player, spell_t* spell, Item* spellbook) +int ItemTooltips_t::getSpellDamageOrHealAmount(const int player, spell_t* spell, Item* spellbook, const bool excludePlayerStats) { #ifdef EDITOR return 0; @@ -2123,10 +2123,19 @@ int ItemTooltips_t::getSpellDamageOrHealAmount(const int player, spell_t* spell, { if ( spellbook && itemCategory(spellbook) == SPELLBOOK ) { - bonus = getSpellbookBonusPercent(players[player]->entity, stats[player], spellbook); + bonus = getSpellbookBonusPercent( + excludePlayerStats ? nullptr : players[player]->entity, + excludePlayerStats ? nullptr : stats[player], + spellbook); } - damage += (damage * (bonus * 0.01 + getBonusFromCasterOfSpellElement(players[player]->entity, stats[player], primaryElement, spell ? spell->ID : SPELL_NONE))); - heal += (heal * (bonus * 0.01 + getBonusFromCasterOfSpellElement(players[player]->entity, stats[player], primaryElement, spell ? spell->ID : SPELL_NONE))); + damage += (damage * (bonus * 0.01 + + getBonusFromCasterOfSpellElement( + excludePlayerStats ? nullptr : players[player]->entity, + excludePlayerStats ? nullptr : stats[player], primaryElement, spell ? spell->ID : SPELL_NONE))); + heal += (heal * (bonus * 0.01 + + getBonusFromCasterOfSpellElement( + excludePlayerStats ? nullptr : players[player]->entity, + excludePlayerStats ? nullptr : stats[player], primaryElement, spell ? spell->ID : SPELL_NONE))); } } if ( spell->ID == SPELL_HEALING || spell->ID == SPELL_EXTRAHEALING ) @@ -2177,7 +2186,7 @@ std::string& ItemTooltips_t::getIconLabel(Item& item) #endif } -std::string ItemTooltips_t::getSpellIconText(const int player, Item& item) +std::string ItemTooltips_t::getSpellIconText(const int player, Item& item, const bool compendiumTooltipIntro) { #ifndef EDITOR spell_t* spell = nullptr; @@ -2227,10 +2236,13 @@ std::string ItemTooltips_t::getSpellIconText(const int player, Item& item) if ( spellItems[spell->ID].internalName == "spell_summon" ) { int numSummons = 1; - if ( (statGetINT(stats[player], players[player]->entity) - + stats[player]->getModifiedProficiency(PRO_MAGIC)) >= SKILL_LEVEL_EXPERT ) + if ( !compendiumTooltipIntro ) { - numSummons = 2; + if ( (statGetINT(stats[player], players[player]->entity) + + stats[player]->getModifiedProficiency(PRO_MAGIC)) >= SKILL_LEVEL_EXPERT ) + { + numSummons = 2; + } } char buf[128]; memset(buf, 0, sizeof(buf)); @@ -2242,7 +2254,7 @@ std::string ItemTooltips_t::getSpellIconText(const int player, Item& item) { char buf[128]; memset(buf, 0, sizeof(buf)); - snprintf(buf, sizeof(buf), str.c_str(), getSpellDamageOrHealAmount(player, spell, &item)); + snprintf(buf, sizeof(buf), str.c_str(), getSpellDamageOrHealAmount(player, spell, &item, compendiumTooltipIntro)); str = buf; } @@ -2719,11 +2731,22 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I static char buf[1024]; memset(buf, 0, sizeof(buf)); + bool compendiumTooltip = false; + bool compendiumTooltipIntro = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + if ( intro ) + { + compendiumTooltipIntro = true; + } + } + if ( conditionalAttribute.find("magicstaff_") != std::string::npos ) { if ( str == "" ) { - str = getSpellIconText(player, item); + str = getSpellIconText(player, item, compendiumTooltipIntro); } return; } @@ -2735,7 +2758,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I } else if ( conditionalAttribute == "SPELL_ICON_EFFECT" ) { - str = getSpellIconText(player, item); + str = getSpellIconText(player, item, compendiumTooltipIntro); } return; } @@ -2743,7 +2766,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I { if ( conditionalAttribute == "SPELLBOOK_SPELLINFO_LEARNED" ) { - str = getSpellIconText(player, item); + str = getSpellIconText(player, item, compendiumTooltipIntro); return; } else if ( conditionalAttribute == "SPELLBOOK_SPELLINFO_UNLEARNED" ) @@ -2758,7 +2781,9 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I && items[item.type].hasAttribute(conditionalAttribute) ) { int spellBookBonusPercent = 0; - spellBookBonusPercent += getSpellbookBonusPercent(players[player]->entity, stats[player], &item); + spellBookBonusPercent += getSpellbookBonusPercent( + compendiumTooltipIntro ? nullptr : players[player]->entity, + compendiumTooltipIntro ? nullptr : stats[player], &item); spellBookBonusPercent *= ((items[item.type].attributes["SPELLBOOK_CAST_BONUS"]) / 100.0); int spellID = getSpellIDFromSpellbook(item.type); @@ -2842,13 +2867,17 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I int baseSpellDamage = 0; if ( item.type == TOOL_FREEZE_BOMB ) { - baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_COLD), nullptr); + baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_COLD), nullptr, compendiumTooltipIntro); } else if ( item.type == TOOL_BOMB ) { - baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_FIREBALL), nullptr); + baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_FIREBALL), nullptr, compendiumTooltipIntro); } int bonusFromPER = std::max(0, statGetPER(stats[player], players[player]->entity)) * items[item.type].attributes["BOMB_DMG_PER_MULT"]; + if ( compendiumTooltipIntro ) + { + bonusFromPER = 0; + } bonusFromPER /= 100; snprintf(buf, sizeof(buf), str.c_str(), baseDamage + bonusFromPER + baseSpellDamage); str = buf; @@ -2879,7 +2908,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I else if ( conditionalAttribute.find("TINKERBOT_MAGICATK") != std::string::npos ) { int spellID = item.status == EXCELLENT ? SPELL_MAGICMISSILE : SPELL_FORCEBOLT; - int spellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(spellID), nullptr); + int spellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(spellID), nullptr, compendiumTooltipIntro); snprintf(buf, sizeof(buf), str.c_str(), spellDamage); str = buf; } @@ -3137,7 +3166,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I } }*/ - int val = (stats[player]->getModifiedProficiency(PRO_STEALTH) / 20 + 2) * 2; // backstab dmg + int val = ((compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(PRO_STEALTH) / 20)) + 2) * 2; // backstab dmg if ( skillCapstoneUnlocked(player, PRO_STEALTH) ) { val *= 2; @@ -3612,17 +3641,17 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I if ( item.type == POTION_HEALING ) { - const int statBonus = 2 * std::max(0, statGetCON(stats[player], players[player]->entity)); + const int statBonus = compendiumTooltipIntro ? 0 : (2 * std::max(0, statGetCON(stats[player], players[player]->entity))); healthVal += statBonus; } else if ( item.type == POTION_EXTRAHEALING ) { - const int statBonus = 4 * std::max(0, statGetCON(stats[player], players[player]->entity)); + const int statBonus = compendiumTooltipIntro ? 0 : (4 * std::max(0, statGetCON(stats[player], players[player]->entity))); healthVal += statBonus; } else if ( item.type == POTION_RESTOREMAGIC ) { - const int statBonus = std::min(30, 2 * std::max(0, statGetINT(stats[player], players[player]->entity))); + const int statBonus = std::min(30, 2 * (compendiumTooltipIntro ? 0 : (std::max(0, statGetINT(stats[player], players[player]->entity))))); healthVal += statBonus; } @@ -3678,7 +3707,7 @@ void ItemTooltips_t::formatItemIcon(const int player, std::string tooltipType, I { if ( conditionalAttribute == "SCROLL_LABEL" ) { - if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + if ( compendiumTooltip ) { snprintf(buf, sizeof(buf), str.c_str(), "???"); // hide labels in compendium } @@ -3726,7 +3755,7 @@ void ItemTooltips_t::formatItemDescription(const int player, std::string tooltip return; } -void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType, Item& item, std::string& str, std::string detailTag) +void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType, Item& item, std::string& str, std::string detailTag, Frame* parentFrame) { #ifndef EDITOR if ( !stats[player] ) @@ -3740,6 +3769,17 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType return; } + bool compendiumTooltip = false; + bool compendiumTooltipIntro = false; + if ( parentFrame && !strcmp(parentFrame->getName(), "compendium") ) + { + compendiumTooltip = true; + if ( intro ) + { + compendiumTooltipIntro = true; + } + } + auto itemTooltip = ItemTooltips.tooltips[tooltipType]; memset(buf, 0, sizeof(buf)); @@ -3756,18 +3796,19 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("armor_shield_bonus") == 0 ) { + bool excludeSkill = compendiumTooltipIntro; if ( tooltipType.find("tooltip_offhand") != std::string::npos ) { snprintf(buf, sizeof(buf), str.c_str(), - stats[player]->getActiveShieldBonus(false), + stats[player]->getActiveShieldBonus(false, excludeSkill), getItemProficiencyName(PRO_SHIELD).c_str()); } else { snprintf(buf, sizeof(buf), str.c_str(), - stats[player]->getPassiveShieldBonus(false), + stats[player]->getPassiveShieldBonus(false, excludeSkill), getItemProficiencyName(PRO_SHIELD).c_str(), - stats[player]->getActiveShieldBonus(false), + stats[player]->getActiveShieldBonus(false, excludeSkill), getItemProficiencyName(PRO_SHIELD).c_str()); } } @@ -3779,7 +3820,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("shield_durability") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(PRO_SHIELD) / 10; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(PRO_SHIELD) / 10); int durabilityBonus = skillLVL * 10; if ( itemCategory(&item) == ARMOR ) { @@ -3793,7 +3834,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("knuckle_skill_modifier") == 0 ) { - int atk = (stats[player]->getModifiedProficiency(PRO_UNARMED) / 20); // 0 - 5 + int atk = compendiumTooltipIntro ? 0 : ((stats[player]->getModifiedProficiency(PRO_UNARMED) / 20)); // 0 - 5 snprintf(buf, sizeof(buf), str.c_str(), atk, getItemProficiencyName(PRO_UNARMED).c_str()); } else if ( detailTag.compare("knuckle_knockback_modifier") == 0 ) @@ -3803,7 +3844,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("weapon_atk_from_player_stat") == 0 ) { - snprintf(buf, sizeof(buf), str.c_str(), stats[player] ? statGetSTR(stats[player], players[player]->entity) : 0); + snprintf(buf, sizeof(buf), str.c_str(), (!compendiumTooltipIntro && stats[player]) ? statGetSTR(stats[player], players[player]->entity) : 0); } else if ( detailTag.compare("ring_unarmed_atk") == 0 ) { @@ -3812,7 +3853,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("weapon_durability") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(PRO_UNARMED) / 20; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(PRO_UNARMED) / 20); int durabilityBonus = skillLVL * 20; snprintf(buf, sizeof(buf), str.c_str(), durabilityBonus, getItemProficiencyName(PRO_UNARMED).c_str()); } @@ -3910,8 +3951,11 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType if ( item.beatitude >= 0 || shouldInvertEquipmentBeatitude(stats[player]) ) { baseBonus = 3 + 1 * std::min(5, abs(item.beatitude)); - chanceBonus += std::min(10, (stats[player]->getModifiedProficiency(PRO_LEADERSHIP) - + std::max(0, 3 * statGetCHR(stats[player], players[player]->entity))) / 10); + if ( !compendiumTooltipIntro ) + { + chanceBonus += std::min(10, (stats[player]->getModifiedProficiency(PRO_LEADERSHIP) + + std::max(0, 3 * statGetCHR(stats[player], players[player]->entity))) / 10); + } if ( baseBonus + chanceBonus > 15 ) { @@ -3973,11 +4017,11 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("thrown_atk_from_player_stat") == 0 ) { - snprintf(buf, sizeof(buf), str.c_str(), stats[player] ? (statGetDEX(stats[player], players[player]->entity) / 4) : 0); + snprintf(buf, sizeof(buf), str.c_str(), (!compendiumTooltipIntro && stats[player]) ? (statGetDEX(stats[player], players[player]->entity) / 4) : 0); } else if ( detailTag.compare("thrown_skill_modifier") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(proficiency) / 10; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(proficiency) / 10); snprintf(buf, sizeof(buf), str.c_str(), skillLVL, getItemProficiencyName(proficiency).c_str()); } @@ -4055,11 +4099,11 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("thrown_atk_from_player_stat") == 0 ) { - snprintf(buf, sizeof(buf), str.c_str(), stats[player] ? (statGetDEX(stats[player], players[player]->entity) / 4) : 0); + snprintf(buf, sizeof(buf), str.c_str(), (!compendiumTooltipIntro && stats[player]) ? (statGetDEX(stats[player], players[player]->entity) / 4) : 0); } else if ( detailTag.compare("thrown_skill_modifier") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(proficiency) / 20; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(proficiency) / 20); snprintf(buf, sizeof(buf), str.c_str(), static_cast(100 * thrownDamageSkillMultipliers[std::min(skillLVL, 5)] - 100), getItemProficiencyName(proficiency).c_str()); } @@ -4134,12 +4178,12 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType if ( proficiency == PRO_POLEARM ) { //int weaponEffectiveness = -8 + (stats[player]->PROFICIENCIES[proficiency] / 3); // -8% to +25% - int weaponEffectiveness = -25 + (stats[player]->getModifiedProficiency(proficiency) / 2); // -25% to +25% + int weaponEffectiveness = -25 + compendiumTooltipIntro ? 0 : ((stats[player]->getModifiedProficiency(proficiency) / 2)); // -25% to +25% snprintf(buf, sizeof(buf), str.c_str(), weaponEffectiveness, getItemProficiencyName(proficiency).c_str()); } else { - int weaponEffectiveness = -25 + (stats[player]->getModifiedProficiency(proficiency) / 2); // -25% to +25% + int weaponEffectiveness = -25 + compendiumTooltipIntro ? 0 : ((stats[player]->getModifiedProficiency(proficiency) / 2)); // -25% to +25% snprintf(buf, sizeof(buf), str.c_str(), weaponEffectiveness, getItemProficiencyName(proficiency).c_str()); } } @@ -4150,20 +4194,24 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType int atk = (stats[player] ? statGetDEX(stats[player], players[player]->entity) : 0); atk += (stats[player] ? statGetSTR(stats[player], players[player]->entity) : 0); atk = std::min(atk / 2, atk); + if ( compendiumTooltipIntro ) + { + atk = 0; + } snprintf(buf, sizeof(buf), str.c_str(), atk); } else if ( proficiency == PRO_RANGED ) { - snprintf(buf, sizeof(buf), str.c_str(), stats[player] ? statGetDEX(stats[player], players[player]->entity) : 0); + snprintf(buf, sizeof(buf), str.c_str(), (!compendiumTooltipIntro && stats[player]) ? statGetDEX(stats[player], players[player]->entity) : 0); } else { - snprintf(buf, sizeof(buf), str.c_str(), stats[player] ? statGetSTR(stats[player], players[player]->entity) : 0); + snprintf(buf, sizeof(buf), str.c_str(), (!compendiumTooltipIntro && stats[player]) ? statGetSTR(stats[player], players[player]->entity) : 0); } } else if ( detailTag.compare("weapon_durability") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(proficiency) / 20; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(proficiency) / 20); int durabilityBonus = skillLVL * 20; snprintf(buf, sizeof(buf), str.c_str(), durabilityBonus, getItemProficiencyName(proficiency).c_str()); } @@ -4185,6 +4233,10 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType else if ( detailTag.compare("weapon_ranged_armor_pierce") == 0 ) { int statChance = std::min(std::max((stats[player] ? statGetPER(stats[player], players[player]->entity) : 0) / 2, 0), 50); // 0 to 50 value. + if ( compendiumTooltipIntro ) + { + statChance = 0; + } statChance += (items[item.type].hasAttribute("ARMOR_PIERCE") ? items[item.type].attributes["ARMOR_PIERCE"] : 0); snprintf(buf, sizeof(buf), str.c_str(), statChance); } @@ -4220,7 +4272,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("potion_restoremagic_bonus") == 0 ) { - if ( stats[player] && statGetINT(stats[player], players[player]->entity) > 0 ) + if ( stats[player] && statGetINT(stats[player], players[player]->entity) > 0 && !compendiumTooltipIntro ) { snprintf(buf, sizeof(buf), str.c_str(), std::min(30, 2 * std::max(0, statGetINT(stats[player], players[player]->entity)))); } @@ -4231,7 +4283,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("potion_healing_bonus") == 0 ) { - if ( stats[player] && statGetCON(stats[player], players[player]->entity) > 0 ) + if ( stats[player] && statGetCON(stats[player], players[player]->entity) > 0 && !compendiumTooltipIntro ) { snprintf(buf, sizeof(buf), str.c_str(), 2 * std::max(0, statGetCON(stats[player], players[player]->entity))); } @@ -4242,7 +4294,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("potion_extrahealing_bonus") == 0 ) { - if ( stats[player] && statGetCON(stats[player], players[player]->entity) > 0 ) + if ( stats[player] && statGetCON(stats[player], players[player]->entity) > 0 && !compendiumTooltipIntro ) { snprintf(buf, sizeof(buf), str.c_str(), 4 * std::max(0, statGetCON(stats[player], players[player]->entity))); } @@ -4285,14 +4337,13 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( detailTag.compare("potion_multiplier") == 0 ) { - int skillLVL = stats[player]->getModifiedProficiency(PRO_ALCHEMY) / 20; + int skillLVL = compendiumTooltipIntro ? 0 : (stats[player]->getModifiedProficiency(PRO_ALCHEMY) / 20); snprintf(buf, sizeof(buf), str.c_str(), static_cast(100 * potionDamageSkillMultipliers[std::min(skillLVL, 5)] - 100), getItemPotionHarmAllyAdjective(item).c_str()); } } else if ( tooltipType.compare("tooltip_tool_lockpick") == 0 ) { - Sint32 PER = statGetPER(stats[player], players[player]->entity); if ( detailTag.compare("lockpick_chestsdoors_unlock_chance") == 0 ) { int chance = stats[player]->getModifiedProficiency(PRO_LOCKPICKING) / 2; // lockpick chests/doors @@ -4300,11 +4351,19 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { chance = 100; } + if ( compendiumTooltipIntro ) + { + chance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), chance); } else if ( detailTag.compare("lockpick_chests_scrap_chance") == 0 ) { int chance = std::min(100, stats[player]->getModifiedProficiency(PRO_LOCKPICKING) + 50); + if ( compendiumTooltipIntro ) + { + chance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), chance); } else if ( detailTag.compare("lockpick_arrow_disarm") == 0 ) @@ -4314,6 +4373,10 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { chance = 0; } + if ( compendiumTooltipIntro ) + { + chance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), chance); } else if ( detailTag.compare("lockpick_automaton_disarm") == 0 ) @@ -4327,6 +4390,10 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { chance = (100 - 100 / (static_cast(stats[player]->getModifiedProficiency(PRO_LOCKPICKING) / 20 + 1))); // lockpick automatons } + if ( compendiumTooltipIntro ) + { + chance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), chance); } else @@ -4336,7 +4403,9 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType } else if ( tooltipType.compare("tooltip_tool_skeletonkey") == 0 ) { - Sint32 PER = statGetPER(stats[player], players[player]->entity); + Sint32 PER = statGetPER( + compendiumTooltipIntro ? nullptr : stats[player], + compendiumTooltipIntro ? nullptr : players[player]->entity); if ( detailTag.compare("lockpick_arrow_disarm") == 0 ) { int chance = (100 - 100 / (std::max(1, static_cast(stats[player]->getModifiedProficiency(PRO_LOCKPICKING) / 10)))); // disarm arrow traps @@ -4344,6 +4413,10 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { chance = 0; } + if ( compendiumTooltipIntro ) + { + chance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), chance); } else @@ -4399,10 +4472,14 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType spell_t* spell = getSpellFromID(getSpellIDFromSpellbook(item.type)); if ( !spell ) { return; } - int intBonus = (statGetINT(stats[player], players[player]->entity) * 0.5); + int intBonus = (statGetINT( + compendiumTooltipIntro ? nullptr : stats[player], + compendiumTooltipIntro ? nullptr : players[player]->entity) * 0.5); real_t mult = ((items[item.type].attributes["SPELLBOOK_CAST_BONUS"]) / 100.0); intBonus *= mult; - int beatitudeBonus = (mult * getSpellbookBonusPercent(players[player]->entity, stats[player], &item)) - intBonus; + int beatitudeBonus = (mult * getSpellbookBonusPercent( + compendiumTooltipIntro ? nullptr : players[player]->entity, + compendiumTooltipIntro ? nullptr : stats[player], &item)) - intBonus; std::string damageOrHealing = adjectives["spell_strings"]["damage"]; if ( spellItems[spell->ID].spellTags.find(SpellTagTypes::SPELL_TAG_HEALING) @@ -4494,7 +4571,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType Sint32 oldINT = stats[player]->INT; stats[player]->INT = 0; - int baseDamage = getSpellDamageOrHealAmount(-1, spell, nullptr); + int baseDamage = getSpellDamageOrHealAmount(-1, spell, nullptr, compendiumTooltipIntro); real_t bonusEquipPercent = 100.0 * getBonusFromCasterOfSpellElement(players[player]->entity, stats[player], nullptr, spell ? spell->ID : SPELL_NONE); @@ -4531,6 +4608,11 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType int leaderChance = ((statGetCHR(stats[player], players[player]->entity) + stats[player]->getModifiedProficiency(PRO_LEADERSHIP)) / 20) * 5; int intChance = (statGetINT(stats[player], players[player]->entity) * 2); + if ( compendiumTooltipIntro ) + { + leaderChance = 0; + intChance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), intChance, leaderChance); } else @@ -4558,6 +4640,10 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType { int leaderChance = ((statGetCHR(stats[player], players[player]->entity) + stats[player]->getModifiedProficiency(PRO_LEADERSHIP)) / 20) * 10; + if ( compendiumTooltipIntro ) + { + leaderChance = 0; + } snprintf(buf, sizeof(buf), str.c_str(), leaderChance); } else @@ -4584,18 +4670,20 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType int baseSpellDamage = 0; if ( item.type == TOOL_FREEZE_BOMB ) { - baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_COLD), nullptr); + baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_COLD), nullptr, compendiumTooltipIntro); } else if ( item.type == TOOL_BOMB ) { - baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_FIREBALL), nullptr); + baseSpellDamage = getSpellDamageOrHealAmount(-1, getSpellFromID(SPELL_FIREBALL), nullptr, compendiumTooltipIntro); } snprintf(buf, sizeof(buf), str.c_str(), baseDmg + baseSpellDamage); } else if ( detailTag.compare("tool_bomb_per_atk") == 0 ) { int perMult = (items[item.type].hasAttribute("BOMB_DMG_PER_MULT") ? items[item.type].attributes["BOMB_DMG_PER_MULT"] : 0); - int perDmg = std::max(0, statGetPER(stats[player], players[player]->entity)) * perMult / 100.0; + int perDmg = std::max(0, statGetPER( + compendiumTooltipIntro ? nullptr : stats[player], + compendiumTooltipIntro ? nullptr : players[player]->entity)) * perMult / 100.0; snprintf(buf, sizeof(buf), str.c_str(), perDmg, perMult); } else @@ -10676,6 +10764,12 @@ int Compendium_t::CompendiumItems_t::completionPercent = 0; int Compendium_t::CompendiumMagic_t::completionPercent = 0; int Compendium_t::CompendiumWorld_t::completionPercent = 0; int Compendium_t::AchievementData_t::completionPercent = 0; +int Compendium_t::CompendiumMonsters_t::numUnread = 0; +int Compendium_t::CompendiumCodex_t::numUnread = 0; +int Compendium_t::CompendiumItems_t::numUnread = 0; +int Compendium_t::CompendiumMagic_t::numUnread = 0; +int Compendium_t::CompendiumWorld_t::numUnread = 0; +int Compendium_t::AchievementData_t::numUnread = 0; std::map Compendium_t::Events_t::monsterIDToString; std::map Compendium_t::Events_t::codexIDToString; @@ -10790,6 +10884,35 @@ void Compendium_t::updateTooltip() { players[MainMenu::getMenuOwner()]->hud.updateFrameTooltip(&compendiumItem, tooltipPos.x, tooltipPos.y, Player::PANEL_JUSTIFY_RIGHT, compendiumFrame); } + + if ( Frame* tooltipContainerFrame = compendiumFrame->findFrame("player tooltip container 0") ) + { + if ( auto prompt = tooltipContainerFrame->findFrame("item_widget") ) + { + if ( auto tooltip = tooltipContainerFrame->findFrame("player tooltip 0") ) + { + if ( tooltip->getSize().w == 0 ) + { + prompt->setOpacity(0.0); + } + else + { + prompt->setOpacity(tooltip->getOpacity()); + } + if ( Compendium_t::compendiumItem.type == SPELL_ITEM ) + { + prompt->setOpacity(0.0); + } + SDL_Rect framePos = prompt->getSize(); + framePos.x = tooltip->getSize().x + tooltip->getSize().w - 6 - framePos.w; + framePos.y = tooltip->getSize().y + tooltip->getSize().h - 10; + //framePos.x = 802 + 378 / 2 - framePos.w / 2; + //framePos.y = 110 - framePos.h + 14; + prompt->setSize(framePos); + } + } + } + } } @@ -15396,8 +15519,10 @@ void Compendium_t::exportCurrentMonster(Entity* monster) return; } +bool Compendium_t::lorePointsFirstLoad = true; void Compendium_t::updateLorePointCounts() { + lorePointsFirstLoad = false; lorePointsFromAchievements = 0; lorePointsSpent = 0; lorePointsAchievementsTotal = 0; @@ -15597,6 +15722,253 @@ void Compendium_t::updateLorePointCounts() } CompendiumMonsters_t::completionPercent = 100.0 * (completed / (real_t)total); + + Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + Compendium_t::PointsAnim_t::countUnreadNotifs(); +} + +real_t Compendium_t::PointsAnim_t::anim = 0.0; +real_t Compendium_t::PointsAnim_t::animNoFunds = 0.0; +Uint32 Compendium_t::PointsAnim_t::noFundsTick = 0; +Uint32 Compendium_t::PointsAnim_t::startTicks = 0; +Sint32 Compendium_t::PointsAnim_t::pointsCurrent = 0; +Sint32 Compendium_t::PointsAnim_t::pointsChange = 0; +Sint32 Compendium_t::PointsAnim_t::txtCurrentPoints = 0; +Sint32 Compendium_t::PointsAnim_t::txtChangePoints = 0; +bool Compendium_t::PointsAnim_t::showChanged = false; +bool Compendium_t::PointsAnim_t::noFundsAnimate = false; +bool Compendium_t::PointsAnim_t::firstLoad = true; +bool Compendium_t::PointsAnim_t::mainMenuAlert = false; +Uint32 Compendium_t::PointsAnim_t::countUnreadLastTicks = 0; + +void Compendium_t::PointsAnim_t::countUnreadNotifs() +{ + if ( countUnreadLastTicks == 0 || (ticks - countUnreadLastTicks) > TICKS_PER_SECOND ) + { + Compendium_t::CompendiumMonsters_t::numUnread = 0; + Compendium_t::CompendiumCodex_t::numUnread = 0; + Compendium_t::CompendiumItems_t::numUnread = 0; + Compendium_t::CompendiumMagic_t::numUnread = 0; + Compendium_t::CompendiumWorld_t::numUnread = 0; + Compendium_t::AchievementData_t::numUnread = 0; + + countUnreadLastTicks = ticks; + + int numUnread = 0; + for ( auto& unlockStatus : CompendiumCodex_t::unlocks ) + { + if ( unlockStatus.second == Compendium_t::UNLOCKED_UNVISITED + || unlockStatus.second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + if ( CompendiumCodex_t::contentsMap.find(unlockStatus.first) != CompendiumCodex_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::CompendiumCodex_t::numUnread; + } + } + } + for ( auto& unlockStatus : CompendiumWorld_t::unlocks ) + { + if ( unlockStatus.second == Compendium_t::UNLOCKED_UNVISITED + || unlockStatus.second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + if ( CompendiumWorld_t::contentsMap.find(unlockStatus.first) != CompendiumWorld_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::CompendiumWorld_t::numUnread; + } + } + } + for ( auto& unlockStatus : CompendiumItems_t::unlocks ) + { + if ( unlockStatus.second == Compendium_t::UNLOCKED_UNVISITED + || unlockStatus.second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + if ( CompendiumItems_t::contentsMap.find(unlockStatus.first) != CompendiumItems_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::CompendiumItems_t::numUnread; + } + if ( CompendiumMagic_t::contentsMap.find(unlockStatus.first) != CompendiumMagic_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::CompendiumMagic_t::numUnread; + } + } + } + for ( auto& unlockStatus : CompendiumMonsters_t::unlocks ) + { + if ( unlockStatus.second == Compendium_t::UNLOCKED_UNVISITED + || unlockStatus.second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + if ( CompendiumMonsters_t::contentsMap.find(unlockStatus.first) != CompendiumMonsters_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::CompendiumMonsters_t::numUnread; + } + } + } + for ( auto& unlockStatus : Compendium_t::AchievementData_t::unlocks ) + { + if ( unlockStatus.second == Compendium_t::UNLOCKED_UNVISITED + || unlockStatus.second == Compendium_t::LOCKED_REVEALED_UNVISITED ) + { + if ( Compendium_t::AchievementData_t::contentsMap.find(unlockStatus.first) != Compendium_t::AchievementData_t::contentsMap.end() ) + { + ++numUnread; + ++Compendium_t::AchievementData_t::numUnread; + } + } + } + + mainMenuAlert = numUnread > 0; + } +} + +void Compendium_t::PointsAnim_t::tickAnimate() +{ + const auto balance = Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent; + + noFundsAnimate = false; + { + // constant decay for animation + const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz + real_t setpointDiffX = fpsScale * 1.0 / 25.0; + animNoFunds -= setpointDiffX; + animNoFunds = std::max(0.0, animNoFunds); + + if ( animNoFunds > 0.001 || (ticks - noFundsTick) < TICKS_PER_SECOND * .8 ) + { + noFundsAnimate = true; + } + } + + bool pauseChangeAnim = false; + if ( pointsChange != 0 ) + { + Uint32 duration = pointsChange > 0 ? (3 * TICKS_PER_SECOND) : (TICKS_PER_SECOND / 2); + if ( ((ticks - startTicks) > duration) ) + { + const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz + real_t setpointDiffX = fpsScale * std::max(.1, (anim)) / 10.0; + anim -= setpointDiffX; + anim = std::max(0.0, anim); + + if ( anim <= 0.0001 ) + { + pointsChange = 0; + } + } + else + { + pauseChangeAnim = true; + + const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz + real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - anim)) / 10.0; + anim += setpointDiffX; + anim = std::min(1.0, anim); + anim = 1.0; + } + } + + showChanged = false; + if ( pointsChange != 0 ) + { + Sint32 displayedChange = anim * pointsChange; + if ( pauseChangeAnim ) + { + displayedChange = pointsChange; + } + if ( abs(displayedChange) > 0 ) + { + showChanged = true; + //changeGoldText->setDisabled(false); + std::string s = "+"; + if ( pointsChange < 0 ) + { + s = ""; + } + s += std::to_string(displayedChange); + txtChangePoints = displayedChange; + //changeGoldText->setText(s.c_str()); + Sint32 displayedCurrent = pointsCurrent + + (pointsChange - displayedChange); + //currentGoldText->setText(std::to_string(displayedCurrentGold).c_str()); + txtCurrentPoints = displayedCurrent; + } + } + + if ( !showChanged ) + { + txtChangePoints = 0; + txtCurrentPoints = balance; + //changeGoldText->setDisabled(true); + //changeGoldText->setText(std::to_string(displayedChangeGold).c_str()); + //currentGoldText->setText(std::to_string(balance).c_str()); + } +} + +void Compendium_t::PointsAnim_t::noFundsEvent() +{ + playSound(90, 64); + noFundsTick = ticks; + animNoFunds = 1.0; +} + +void Compendium_t::PointsAnim_t::pointsChangeEvent(Sint32 amount) +{ + bool addedToCurrentTotal = false; + Uint32 duration = pointsChange > 0 ? (3 * TICKS_PER_SECOND) : (TICKS_PER_SECOND / 2); + const bool isAnimatingValue = ((ticks - startTicks) > duration); + const auto balance = Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent; + if ( amount < 0 ) + { + if ( pointsChange < 0 + && !isAnimatingValue + && abs(amount) > 0 ) + { + addedToCurrentTotal = true; + if ( balance + amount < 0 ) + { + pointsChange -= balance; + } + else + { + pointsChange += amount; + } + } + else + { + if ( balance + amount < 0 ) + { + pointsChange = -balance; + } + else + { + pointsChange = amount; + } + } + } + else + { + if ( pointsChange > 0 + && !isAnimatingValue + && abs(amount) > 0 ) + { + addedToCurrentTotal = true; + pointsChange += amount; + } + else + { + pointsChange = amount; + } + } + startTicks = ticks; + anim = 0.0; + if ( !addedToCurrentTotal ) + { + pointsCurrent = balance; + } } #endif diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 345fd6dbd..844b66bb5 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2876,20 +2876,20 @@ class ItemTooltips_t std::string& getItemEquipmentEffectsForAttributesText(std::string& attribute); std::string& getProficiencyLevelName(Sint32 proficiencyLevel); std::string& getIconLabel(Item& item); - std::string getSpellIconText(const int player, Item& item); + std::string getSpellIconText(const int player, Item& item, const bool excludePlayerStats); std::string getSpellDescriptionText(const int player, Item& item); std::string getSpellIconPath(const int player, Item& item, int spellID); std::string getCostOfSpellString(const int player, Item& item); std::string& getSpellTypeString(const int player, Item& item); node_t* getSpellNodeFromSpellID(int spellID); real_t getSpellSustainCostPerSecond(int spellID); - int getSpellDamageOrHealAmount(const int player, spell_t* spell, Item* spellbook); + int getSpellDamageOrHealAmount(const int player, spell_t* spell, Item* spellbook, const bool excludePlayerStats); bool bIsSpellDamageOrHealingType(spell_t* spell); bool bSpellHasBasicHitMessage(const int spellID); void formatItemIcon(const int player, std::string tooltipType, Item& item, std::string& str, int iconIndex, std::string& conditionalAttribute, Frame* parentFrame = nullptr); void formatItemDescription(const int player, std::string tooltipType, Item& item, std::string& str); - void formatItemDetails(const int player, std::string tooltipType, Item& item, std::string& str, std::string detailTag); + void formatItemDetails(const int player, std::string tooltipType, Item& item, std::string& str, std::string detailTag, Frame* parentFrame = nullptr); void stripOutPositiveNegativeItemDetails(std::string& str, std::string& positiveValues, std::string& negativeValues); void stripOutHighlightBracketText(std::string& str, std::string& bracketText); void getWordIndexesItemDetails(void* field, std::string& str, std::string& highlightValues, std::string& positiveValues, std::string& negativeValues, @@ -3497,6 +3497,28 @@ struct Compendium_t bool inUse = false; }; + struct PointsAnim_t + { + static real_t anim; + static Uint32 startTicks; + static Sint32 pointsCurrent; + static Sint32 pointsChange; + static Sint32 txtCurrentPoints; + static Sint32 txtChangePoints; + static real_t animNoFunds; + static Uint32 noFundsTick; + static bool firstLoad; + static bool noFundsAnimate; + static bool showChanged; + static void reset(); + static void tickAnimate(); + static void noFundsEvent(); + static bool mainMenuAlert; + static Uint32 countUnreadLastTicks; + static void countUnreadNotifs(); + static void pointsChangeEvent(Sint32 amount); + }; + static void readContentsLang(std::string name, std::map>>& contents, std::map& contentsMap); enum CompendiumUnlockStatus : int { @@ -3541,6 +3563,7 @@ struct Compendium_t static std::map contentsMap; static std::map unlocks; static int completionPercent; + static int numUnread; static void readContentsLang(); struct CompendiumAchievementsDisplay @@ -3908,6 +3931,7 @@ struct Compendium_t static void readContentsLang(); static std::map unlocks; static int completionPercent; + static int numUnread; }; std::map monsters; void readMonstersFromFile(); @@ -3950,6 +3974,7 @@ struct Compendium_t static void readContentsLang(); static std::map unlocks; static int completionPercent; + static int numUnread; }; std::map worldObjects; void readWorldFromFile(); @@ -3976,6 +4001,7 @@ struct Compendium_t static void readContentsLang(); static std::map unlocks; static int completionPercent; + static int numUnread; }; std::map codex; void readCodexFromFile(); @@ -4005,6 +4031,7 @@ struct Compendium_t static std::map unlocks; static std::map itemUnlocks; static int completionPercent; + static int numUnread; }; std::map items; void readItemsFromFile(); @@ -4015,6 +4042,7 @@ struct Compendium_t static std::map contentsMap; static void readContentsLang(); static int completionPercent; + static int numUnread; }; std::map magic; void readMagicFromFile(); @@ -4027,6 +4055,7 @@ struct Compendium_t static int lorePointsFromAchievements; static int lorePointsAchievementsTotal; static int lorePointsSpent; + static bool lorePointsFirstLoad; static void updateLorePointCounts(); static void writeUnlocksSaveData(); static void readUnlocksSaveData(); From 92bc5a7940c11043ea323258e598a5b5824279e6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:29:13 +1000 Subject: [PATCH 058/244] * cheats disable compendium input --- src/mod_tools.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 7e4ee0bfe..86102a85e 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -14235,6 +14235,12 @@ bool allowedCompendiumProgress() { return false; // challenge event run } + if ( Mods::disableSteamAchievements || (svFlags & SV_FLAG_CHEATS) ) + { +#ifndef DEBUG_ACHIEVEMENTS + return false; +#endif + } return true; } From e24083b1c7795dc025ff76eb21565516859e0d54 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:29:40 +1000 Subject: [PATCH 059/244] * remove some debug msgs for debug_achievements --- src/steam.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/steam.cpp b/src/steam.cpp index 99f9127fa..ee2796878 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -804,7 +804,7 @@ bool achievementUnlocked(const char* achName) void steamAchievement(const char* achName) { #ifdef DEBUG_ACHIEVEMENTS - messagePlayer(clientnum, MESSAGE_DEBUG, "%s", achName); + //messagePlayer(clientnum, MESSAGE_DEBUG, "%s", achName); #endif if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) @@ -867,23 +867,23 @@ void steamAchievement(const char* achName) find->second.unlocked = true; find->second.unlockTime = getTime(); auto& unlockStatus = Compendium_t::AchievementData_t::unlocks[find->second.category]; - if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) - { - unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; - } - else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) - { - unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; - } - else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) - { - unlockStatus = Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_VISITED ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + else if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED; + } + Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(achName); + Compendium_t::AchievementData_t::achievementsNeedResort = true; } - Compendium_t::AchievementData_t::achievementUnlockedLookup.insert(achName); - Compendium_t::AchievementData_t::achievementsNeedResort = true; } } -} void steamUnsetAchievement(const char* achName) { @@ -961,7 +961,9 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) if ( conductGameChallenges[CONDUCT_CHEATS_ENABLED] || conductGameChallenges[CONDUCT_MODDED_NO_ACHIEVEMENTS] ) { +#ifndef DEBUG_ACHIEVEMENTS return; +#endif } } else @@ -1238,7 +1240,9 @@ void steamStatisticUpdateClient(int player, int statisticNum, ESteamStatTypes ty if ( conductGameChallenges[CONDUCT_CHEATS_ENABLED] || conductGameChallenges[CONDUCT_MODDED_NO_ACHIEVEMENTS] ) { +#ifndef DEBUG_ACHIEVEMENTS return; +#endif } } else @@ -1452,8 +1456,8 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) break; } #ifdef DEBUG_ACHIEVEMENTS - messagePlayer(clientnum, MESSAGE_DEBUG, "%s: %d, %d", steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), - iVal, steamStatAchStringsAndMaxVals[statisticNum].second); + /*messagePlayer(clientnum, MESSAGE_DEBUG, "%s: %d, %d", steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), + iVal, steamStatAchStringsAndMaxVals[statisticNum].second);*/ #endif } #endif // !STEAMWORKS From 8a3468c8990c50d6829e6c424e36f2a4aa6aa3c2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 11:30:33 +1000 Subject: [PATCH 060/244] * update base debug solution to compile --- VS.2015/Barony.sln | 1 - VS.2015/Barony/Barony.vcxproj | 4 ++-- VS.2015/editor/editor.vcxproj | 6 +++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/VS.2015/Barony.sln b/VS.2015/Barony.sln index 5f5016201..a6bebcf43 100644 --- a/VS.2015/Barony.sln +++ b/VS.2015/Barony.sln @@ -86,7 +86,6 @@ Global {8D0CEB30-DFEC-462D-B107-1EA02D317151}.Debug|Win32.ActiveCfg = Release|Win32 {8D0CEB30-DFEC-462D-B107-1EA02D317151}.Debug|Win32.Build.0 = Release|Win32 {8D0CEB30-DFEC-462D-B107-1EA02D317151}.Debug|x64.ActiveCfg = Debug|x64 - {8D0CEB30-DFEC-462D-B107-1EA02D317151}.Debug|x64.Build.0 = Debug|x64 {8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS Debug|Win32.ActiveCfg = EOS Debug|Win32 {8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS Debug|x64.ActiveCfg = EOS Debug|x64 {8D0CEB30-DFEC-462D-B107-1EA02D317151}.EOS|Win32.ActiveCfg = EOS|Win32 diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index 18dd60ac4..c72244ef9 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -464,7 +464,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% Level3 Disabled - _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions);BARONY_DRM_FREE;USE_IMGUI;USE_THEORA_VIDEO;CURL_STATICLIB; true "$(SolutionDir)\..\VS-includes" true @@ -474,7 +474,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% Windows true - OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmodex_vc.lib;libpng.lib;zlib.lib;steam_api.lib;SDL2_ttf.lib;physfs.lib;%(AdditionalDependencies) + OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;%(AdditionalDependencies) robocopy "$(TargetDir)\" "$(SolutionDir)Release" $(TargetFileName) diff --git a/VS.2015/editor/editor.vcxproj b/VS.2015/editor/editor.vcxproj index 0c4099665..c458b24e2 100644 --- a/VS.2015/editor/editor.vcxproj +++ b/VS.2015/editor/editor.vcxproj @@ -709,7 +709,7 @@ Level3 - MaxSpeed + Disabled true true true @@ -728,6 +728,10 @@ OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;glew32.lib;%(AdditionalDependencies) Windows + + + + From a4053844a6daec22ba11efc3feeedd4e14ef9c5d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 13:05:15 +1000 Subject: [PATCH 061/244] * fix epic achievement card text for achievements > 1 line * fix epic achievement card stuck at logging in when returning to main menu --- src/eos.cpp | 14 ++++++++++++++ src/interface/ui.hpp | 6 ++++++ src/interface/ui_general.cpp | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/src/eos.cpp b/src/eos.cpp index e87a8cf1f..cab900b6b 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -3545,6 +3545,20 @@ void EOSFuncs::Accounts_t::handleLogin() } AccountAuthenticationCompleted = EOS_EResult::EOS_Success; } + else + { + if ( popupType == POPUP_TOAST ) + { + UIToastNotification* n = UIToastNotificationManager.getNotificationSingle(UIToastNotification::CardType::UI_CARD_EOS_ACCOUNT); + if ( n ) + { + if ( n->getDisplayedText() != "Logged in successfully!" ) + { + n->updateCardEvent(false, true); + } + } + } + } return; } AccountAuthenticationCompleted = EOS_EResult::EOS_NotConfigured; diff --git a/src/interface/ui.hpp b/src/interface/ui.hpp index a419d8aec..367c1d608 100644 --- a/src/interface/ui.hpp +++ b/src/interface/ui.hpp @@ -91,6 +91,12 @@ class UIToastNotification void setDisplayedText(const char* text) { displayedText = text; } + + const std::string& getDisplayedText() + { + return displayedText; + } + void setIdleSeconds(Uint32 seconds) { idleTicksToHide = seconds * TICKS_PER_SECOND; } diff --git a/src/interface/ui_general.cpp b/src/interface/ui_general.cpp index 7a763dda1..acaab6ed6 100644 --- a/src/interface/ui_general.cpp +++ b/src/interface/ui_general.cpp @@ -398,6 +398,11 @@ void UIToastNotification::drawMainCard() mainField->setHJustify(Field::justify_t::LEFT); mainField->setVJustify(Field::justify_t::CENTER); mainField->setText(displayedText.c_str()); + if ( mainField->getNumTextLines() > 1 ) + { + mainField->setVJustify(Field::justify_t::TOP); + mainField->setSize(SDL_Rect{ bodyx + imgSize + offset, 29, r.w - bodyx - imgSize, r.h - 29 }); + } frameImage->path = notificationImage; frameImage->disabled = false; From 33c9d2497331b814c107bdcd48ddc85cbebd6cba Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 13:55:10 +1000 Subject: [PATCH 062/244] * fix multiplayer negative stats - limit of 248 for stats, 255 LVL * fix level die events not working --- src/actgeneral.cpp | 4 +- src/actplayer.cpp | 2 + src/entity.cpp | 234 +++++++++++++++++++++++++++------------------ src/menu.cpp | 4 +- src/mod_tools.cpp | 52 +++++----- src/mod_tools.hpp | 4 +- src/net.cpp | 36 +++---- src/stat.hpp | 2 +- 8 files changed, 199 insertions(+), 139 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 4961af18e..fdbcc27d3 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -3087,8 +3087,8 @@ void TextSourceScript::updateClientInformation(int player, bool clearInventory, net_packet->data[8] = (Sint8)stats[player]->INT; net_packet->data[9] = (Sint8)stats[player]->PER; net_packet->data[10] = (Sint8)stats[player]->CHR; - net_packet->data[11] = (Sint8)stats[player]->EXP; - net_packet->data[12] = (Sint8)stats[player]->LVL; + net_packet->data[11] = (Uint8)stats[player]->EXP; + net_packet->data[12] = (Uint8)stats[player]->LVL; SDLNet_Write16((Sint16)stats[player]->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)stats[player]->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)stats[player]->MP, &net_packet->data[17]); diff --git a/src/actplayer.cpp b/src/actplayer.cpp index ec36781eb..19a744ef3 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -7577,6 +7577,8 @@ void actPlayer(Entity* my) net_packet->address.host = net_clients[PLAYER_NUM - 1].host; net_packet->address.port = net_clients[PLAYER_NUM - 1].port; sendPacketSafe(net_sock, -1, net_packet, PLAYER_NUM - 1); + + Compendium_t::Events_t::sendClientDataOverNet(PLAYER_NUM); } } diff --git a/src/entity.cpp b/src/entity.cpp index 4b7d6b18c..058f37153 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1919,8 +1919,8 @@ bool Entity::increaseSkill(int skill, bool notify) net_packet->data[8] = (Sint8)myStats->INT; net_packet->data[9] = (Sint8)myStats->PER; net_packet->data[10] = (Sint8)myStats->CHR; - net_packet->data[11] = (Sint8)myStats->EXP; - net_packet->data[12] = (Sint8)myStats->LVL; + net_packet->data[11] = (Uint8)myStats->EXP; + net_packet->data[12] = (Uint8)myStats->LVL; SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]); @@ -2972,7 +2972,17 @@ void Entity::handleEffects(Stat* myStats) if ( myStats->EXP >= 100 ) { myStats->EXP -= 100; - myStats->LVL++; + if ( player >= 0 ) + { + if ( myStats->LVL < 255 ) + { + myStats->LVL++; + } + } + else + { + myStats->LVL++; + } if ( player >= 0 && players[player]->isLocalPlayer() ) { @@ -3195,133 +3205,169 @@ void Entity::handleEffects(Stat* myStats) { case STAT_STR: // STR { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->STR, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->STR < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->STR, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "str", 1); - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->STR + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "str", 1); + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->STR += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "str", 1); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_STR_MAX, "str", myStats->STR); } - myStats->STR += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "str", 1); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_STR_MAX, "str", myStats->STR); break; } case STAT_DEX: // DEX { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->DEX, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->DEX < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->DEX, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "dex", 1); - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->DEX + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "dex", 1); + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->DEX += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "dex", increment); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_DEX_MAX, "dex", myStats->DEX); } - myStats->DEX += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "dex", increment); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_DEX_MAX, "dex", myStats->DEX); break; } case STAT_CON: // CON { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CON, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->CON < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CON, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "con", 1); - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->CON + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "con", 1); + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->CON += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "con", increment); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CON_MAX, "con", myStats->CON); } - myStats->CON += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "con", increment); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CON_MAX, "con", myStats->CON); break; } case STAT_INT: // INT { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->INT, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->INT < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->INT, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "int", 1); - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->INT + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "int", 1); + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->INT += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "int", increment); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_INT_MAX, "int", myStats->INT); } - myStats->INT += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "int", increment); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_INT_MAX, "int", myStats->INT); break; } case STAT_PER: // PER { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->PER, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->PER < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->PER, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->PER + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->PER += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "per", increment); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_PER_MAX, "per", myStats->PER); } - myStats->PER += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "per", increment); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_PER_MAX, "per", myStats->PER); break; } case STAT_CHR: // CHR { - StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CHR, 1)); - int increment = 1; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; - if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) + if ( myStats->CHR < MAX_PLAYER_STAT_VALUE ) { - if ( local_rng.rand() % 5 == 0 ) + StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(increasestat[i], myStats->CHR, 1)); + int increment = 1; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i]] = statIconTicks; + if ( myStats->PLAYER_LVL_STAT_BONUS[increasestat[i]] >= PRO_LOCKPICKING && !rolledBonusStat ) { - StatUps.at(StatUps.size() - 1).increaseStat += 1; - increment++; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "chr", 1); - rolledBonusStat = true; - myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; - //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + if ( local_rng.rand() % 5 == 0 ) + { + if ( (myStats->CHR + 1) < MAX_PLAYER_STAT_VALUE ) + { + StatUps.at(StatUps.size() - 1).increaseStat += 1; + increment++; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_DOUBLED, "chr", 1); + rolledBonusStat = true; + myStats->PLAYER_LVL_STAT_TIMER[increasestat[i] + NUMSTATS] = statIconTicks; + //messagePlayer(0, "Rolled bonus in %d", increasestat[i]); + } + } } + myStats->CHR += increment; + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "chr", increment); + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CHR_MAX, "chr", myStats->CHR); } - myStats->CHR += increment; - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_STAT_INCREASES, "chr", increment); - Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_STAT_CHR_MAX, "chr", myStats->CHR); break; } default: @@ -3361,8 +3407,8 @@ void Entity::handleEffects(Stat* myStats) net_packet->data[8] = (Sint8)myStats->INT; net_packet->data[9] = (Sint8)myStats->PER; net_packet->data[10] = (Sint8)myStats->CHR; - net_packet->data[11] = (Sint8)myStats->EXP; - net_packet->data[12] = (Sint8)myStats->LVL; + net_packet->data[11] = (Uint8)myStats->EXP; + net_packet->data[12] = (Uint8)myStats->LVL; SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]); @@ -4845,8 +4891,8 @@ void Entity::handleEffects(Stat* myStats) net_packet->data[8] = (Sint8)myStats->INT; net_packet->data[9] = (Sint8)myStats->PER; net_packet->data[10] = (Sint8)myStats->CHR; - net_packet->data[11] = (Sint8)myStats->EXP; - net_packet->data[12] = (Sint8)myStats->LVL; + net_packet->data[11] = (Uint8)myStats->EXP; + net_packet->data[12] = (Uint8)myStats->LVL; SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]); @@ -5029,8 +5075,8 @@ void Entity::handleEffects(Stat* myStats) net_packet->data[8] = (Sint8)myStats->INT; net_packet->data[9] = (Sint8)myStats->PER; net_packet->data[10] = (Sint8)myStats->CHR; - net_packet->data[11] = (Sint8)myStats->EXP; - net_packet->data[12] = (Sint8)myStats->LVL; + net_packet->data[11] = (Uint8)myStats->EXP; + net_packet->data[12] = (Uint8)myStats->LVL; SDLNet_Write16((Sint16)myStats->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)myStats->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)myStats->MP, &net_packet->data[17]); @@ -12647,8 +12693,8 @@ void Entity::awardXP(Entity* src, bool share, bool root) net_packet->data[8] = (Sint8)destStats->INT; net_packet->data[9] = (Sint8)destStats->PER; net_packet->data[10] = (Sint8)destStats->CHR; - net_packet->data[11] = (Sint8)destStats->EXP; - net_packet->data[12] = (Sint8)destStats->LVL; + net_packet->data[11] = (Uint8)destStats->EXP; + net_packet->data[12] = (Uint8)destStats->LVL; SDLNet_Write16((Sint16)destStats->HP, &net_packet->data[13]); SDLNet_Write16((Sint16)destStats->MAXHP, &net_packet->data[15]); SDLNet_Write16((Sint16)destStats->MP, &net_packet->data[17]); diff --git a/src/menu.cpp b/src/menu.cpp index b6fdaf9ed..73c963072 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9595,7 +9595,8 @@ void doEndgame(bool saveHighscore) { } } - Compendium_t::Events_t::onEndgameEvent(clientnum, endTutorial, saveHighscore); + bool died = stats[clientnum] && stats[clientnum]->HP <= 0; + Compendium_t::Events_t::onEndgameEvent(clientnum, endTutorial, saveHighscore, died); // figure out the victory crawl texts... int movieCrawlType = -1; @@ -10135,6 +10136,7 @@ void doEndgame(bool saveHighscore) { players[c]->compendiumProgress.floorEvents.clear(); players[c]->compendiumProgress.playerAliveTimeTotal = 0; players[c]->compendiumProgress.playerGameTimeTotal = 0; + Compendium_t::Events_t::serverPlayerEvents[c].clear(); } #ifdef LOCAL_ACHIEVEMENTS LocalAchievements_t::writeToFile(); diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 86102a85e..f35916e74 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -13645,7 +13645,7 @@ bool Compendium_t::Events_t::EventVal_t::applyValue(const Sint32 val) } } -void onCompendiumLevelExit(const int playernum, const char* level, const bool enteringLvl) +void onCompendiumLevelExit(const int playernum, const char* level, const bool enteringLvl, const bool died) { if ( !level ) { return; } if ( !strcmp(level, "") ) { return; } @@ -13662,7 +13662,7 @@ void onCompendiumLevelExit(const int playernum, const char* level, const bool en Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MIN_COMPLETION, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_MAX_COMPLETION, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); - if ( stats[playernum]->HP <= 0 ) + if ( stats[playernum]->HP <= 0 || died ) { Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS, level, 1); Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, level, players[playernum]->compendiumProgress.playerAliveTimeTotal); @@ -13937,7 +13937,7 @@ const char* Compendium_t::compendiumCurrentLevelToWorldString(const int currentl return ""; } -void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore) +void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore, const bool died) { if ( players[playernum]->isLocalPlayer() ) { @@ -13958,7 +13958,7 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto { if ( currentlevel == 35 ) { - onCompendiumLevelExit(playernum, "citadel sanctum", false); + onCompendiumLevelExit(playernum, "citadel sanctum", false, died); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON, "class", 1); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_MAX, "leveling up", stats[playernum]->LVL); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_MIN, "leveling up", stats[playernum]->LVL); @@ -13966,7 +13966,7 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto } else if ( currentlevel == 24 ) { - onCompendiumLevelExit(playernum, "molten throne", false); + onCompendiumLevelExit(playernum, "molten throne", false, died); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON_HELL, "class", 1); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MAX, "leveling up", stats[playernum]->LVL); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_HELL_MIN, "leveling up", stats[playernum]->LVL); @@ -13974,7 +13974,7 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto } else if ( currentlevel == 20 ) { - onCompendiumLevelExit(playernum, "herx lair", false); + onCompendiumLevelExit(playernum, "herx lair", false, died); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_GAMES_WON_CLASSIC, "class", 1); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_CLASSIC_MAX, "leveling up", stats[playernum]->LVL); eventUpdateCodex(playernum, Compendium_t::CPDM_CLASS_LVL_WON_CLASSIC_MIN, "leveling up", stats[playernum]->LVL); @@ -13986,7 +13986,7 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto const char* currentWorldString = compendiumCurrentLevelToWorldString(currentlevel, secretlevel); if ( strcmp(currentWorldString, "") ) { - if ( stats[playernum]->HP <= 0 ) + if ( stats[playernum]->HP <= 0 || died ) { Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS, currentWorldString, 1); Compendium_t::Events_t::eventUpdateWorld(playernum, Compendium_t::CPDM_LEVELS_DEATHS_FASTEST, currentWorldString, players[playernum]->compendiumProgress.playerAliveTimeTotal); @@ -13997,6 +13997,14 @@ void Compendium_t::Events_t::onEndgameEvent(const int playernum, const bool tuto players[playernum]->compendiumProgress.playerGameTimeTotal); } } + + if ( multiplayer == SERVER && playernum == 0 ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + sendClientDataOverNet(i); + } + } } } } @@ -14027,7 +14035,7 @@ void Player::CompendiumProgress_t::updateFloorEvents() floorEvents.clear(); } -void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname) +void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname, const bool died) { if ( intro ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) @@ -14063,7 +14071,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p const char* currentWorldString = compendiumCurrentLevelToWorldString(currentlevel, secretlevel); if ( strcmp(currentWorldString, "") ) { - onCompendiumLevelExit(playernum, currentWorldString, true); + onCompendiumLevelExit(playernum, currentWorldString, true, died); } if ( stats[playernum] ) @@ -14092,16 +14100,16 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p { if ( prevlevel == 0 ) { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } else if ( prevlevel == 5 || prevlevel == 10 || prevlevel == 15 || prevlevel == 30 ) { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } else if ( prevlevel >= 1 && prevlevel <= 4 ) { - onCompendiumLevelExit(playernum, "mines", false); + onCompendiumLevelExit(playernum, "mines", false, died); bool commitUniqueValue = false; if ( prevlevel == 4 ) { @@ -14115,7 +14123,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel >= 6 && prevlevel <= 9 ) { - onCompendiumLevelExit(playernum, "swamps", false); + onCompendiumLevelExit(playernum, "swamps", false, died); bool commitUniqueValue = false; if ( prevlevel == 9 ) { @@ -14129,7 +14137,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel >= 11 && prevlevel <= 14 ) { - onCompendiumLevelExit(playernum, "labyrinth", false); + onCompendiumLevelExit(playernum, "labyrinth", false, died); bool commitUniqueValue = false; if ( prevlevel == 14 ) { @@ -14143,7 +14151,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel >= 16 && prevlevel <= 19 ) { - onCompendiumLevelExit(playernum, "ruins", false); + onCompendiumLevelExit(playernum, "ruins", false, died); bool commitUniqueValue = false; if ( prevlevel == 19 ) { @@ -14157,11 +14165,11 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel == 20 ) { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } else if ( prevlevel >= 21 && prevlevel <= 23 ) { - onCompendiumLevelExit(playernum, "hell", false); + onCompendiumLevelExit(playernum, "hell", false, died); bool commitUniqueValue = false; if ( prevlevel == 23 ) { @@ -14175,15 +14183,15 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel == 24 ) { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } else if ( prevlevel == 25 ) { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } else if ( prevlevel >= 26 && prevlevel <= 29 ) { - onCompendiumLevelExit(playernum, "crystal caves", false); + onCompendiumLevelExit(playernum, "crystal caves", false, died); bool commitUniqueValue = false; if ( prevlevel == 29 ) { @@ -14197,7 +14205,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else if ( prevlevel >= 31 && prevlevel <= 34 ) { - onCompendiumLevelExit(playernum, "arcane citadel", false); + onCompendiumLevelExit(playernum, "arcane citadel", false, died); bool commitUniqueValue = false; if ( prevlevel == 34 ) { @@ -14212,7 +14220,7 @@ void Compendium_t::Events_t::onLevelChangeEvent(const int playernum, const int p } else { - onCompendiumLevelExit(playernum, prevWorldString, false); + onCompendiumLevelExit(playernum, prevWorldString, false, died); } } } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 844b66bb5..63646aa05 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -4186,8 +4186,8 @@ struct Compendium_t static void eventUpdateCodex(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue = false, const int entryID = -1, const bool floorEvent = false); static std::map> playerEvents; static std::map> serverPlayerEvents[MAXPLAYERS]; - static void onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname); - static void onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore); + static void onLevelChangeEvent(const int playernum, const int prevlevel, const bool prevsecretfloor, const std::string prevmapname, const bool died); + static void onEndgameEvent(const int playernum, const bool tutorialend, const bool saveHighscore, const bool died); static void sendClientDataOverNet(const int playernum); static void updateEventsInMainLoop(const int playernum); static std::map clientDataStrings[MAXPLAYERS]; diff --git a/src/net.cpp b/src/net.cpp index 54d277003..ebf0b1122 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2174,6 +2174,8 @@ static void changeLevel() { FollowerMenu[clientnum].closeFollowerMenuGUI(true); CalloutMenu[clientnum].closeCalloutMenuGUI(); + bool died = stats[clientnum] && stats[clientnum]->HP <= 0; + // load map file loading = true; createLevelLoadScreen(5); @@ -2308,7 +2310,7 @@ static void changeLevel() { Player::Minimap_t::mapDetails.push_back(std::make_pair("map_flag_disable_hunger", "")); } - Compendium_t::Events_t::onLevelChangeEvent(clientnum, prevcurrentlevel, prevsecretfloor, prevmapname); + Compendium_t::Events_t::onLevelChangeEvent(clientnum, prevcurrentlevel, prevsecretfloor, prevmapname, died); for ( int i = 0; i < MAXPLAYERS; ++i ) { players[i]->compendiumProgress.playerAliveTimeTotal = 0; @@ -3962,14 +3964,14 @@ static std::unordered_map clientPacketHandlers = { // update attributes {'ATTR', [](){ - stats[clientnum]->STR = (Sint8)net_packet->data[5]; - stats[clientnum]->DEX = (Sint8)net_packet->data[6]; - stats[clientnum]->CON = (Sint8)net_packet->data[7]; - stats[clientnum]->INT = (Sint8)net_packet->data[8]; - stats[clientnum]->PER = (Sint8)net_packet->data[9]; - stats[clientnum]->CHR = (Sint8)net_packet->data[10]; - stats[clientnum]->EXP = (Sint8)net_packet->data[11]; - stats[clientnum]->LVL = (Sint8)net_packet->data[12]; + stats[clientnum]->STR = ((Sint8)net_packet->data[5] <= -8) ? (Uint8)net_packet->data[5] : (Sint8)net_packet->data[5]; + stats[clientnum]->DEX = ((Sint8)net_packet->data[6] <= -8) ? (Uint8)net_packet->data[6] : (Sint8)net_packet->data[6]; + stats[clientnum]->CON = ((Sint8)net_packet->data[7] <= -8) ? (Uint8)net_packet->data[7] : (Sint8)net_packet->data[7]; + stats[clientnum]->INT = ((Sint8)net_packet->data[8] <= -8) ? (Uint8)net_packet->data[8] : (Sint8)net_packet->data[8]; + stats[clientnum]->PER = ((Sint8)net_packet->data[9] <= -8) ? (Uint8)net_packet->data[9] : (Sint8)net_packet->data[9]; + stats[clientnum]->CHR = ((Sint8)net_packet->data[10] <= -8) ? (Uint8)net_packet->data[10] : (Sint8)net_packet->data[10]; + stats[clientnum]->EXP = (Uint8)net_packet->data[11]; + stats[clientnum]->LVL = (Uint8)net_packet->data[12]; stats[clientnum]->HP = (Sint16)SDLNet_Read16(&net_packet->data[13]); stats[clientnum]->MAXHP = (Sint16)SDLNet_Read16(&net_packet->data[15]); stats[clientnum]->MP = (Sint16)SDLNet_Read16(&net_packet->data[17]); @@ -4935,14 +4937,14 @@ static std::unordered_map clientPacketHandlers = { } textSourceScript.playerClearInventory(clearStats); } - stats[clientnum]->STR = (Sint8)net_packet->data[5]; - stats[clientnum]->DEX = (Sint8)net_packet->data[6]; - stats[clientnum]->CON = (Sint8)net_packet->data[7]; - stats[clientnum]->INT = (Sint8)net_packet->data[8]; - stats[clientnum]->PER = (Sint8)net_packet->data[9]; - stats[clientnum]->CHR = (Sint8)net_packet->data[10]; - stats[clientnum]->EXP = (Sint8)net_packet->data[11]; - stats[clientnum]->LVL = (Sint8)net_packet->data[12]; + stats[clientnum]->STR = ((Sint8)net_packet->data[5] <= -8) ? (Uint8)net_packet->data[5] : (Sint8)net_packet->data[5]; + stats[clientnum]->DEX = ((Sint8)net_packet->data[6] <= -8) ? (Uint8)net_packet->data[6] : (Sint8)net_packet->data[6]; + stats[clientnum]->CON = ((Sint8)net_packet->data[7] <= -8) ? (Uint8)net_packet->data[7] : (Sint8)net_packet->data[7]; + stats[clientnum]->INT = ((Sint8)net_packet->data[8] <= -8) ? (Uint8)net_packet->data[8] : (Sint8)net_packet->data[8]; + stats[clientnum]->PER = ((Sint8)net_packet->data[9] <= -8) ? (Uint8)net_packet->data[9] : (Sint8)net_packet->data[9]; + stats[clientnum]->CHR = ((Sint8)net_packet->data[10] <= -8) ? (Uint8)net_packet->data[10] : (Sint8)net_packet->data[10]; + stats[clientnum]->EXP = (Uint8)net_packet->data[11]; + stats[clientnum]->LVL = (Uint8)net_packet->data[12]; stats[clientnum]->HP = (Sint16)SDLNet_Read16(&net_packet->data[13]); stats[clientnum]->MAXHP = (Sint16)SDLNet_Read16(&net_packet->data[15]); stats[clientnum]->MP = (Sint16)SDLNet_Read16(&net_packet->data[17]); diff --git a/src/stat.hpp b/src/stat.hpp index f1a5a787c..64c4485ff 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -372,7 +372,7 @@ inline bool skillCapstoneUnlocked(int player, int proficiency) { return (stats[player]->getModifiedProficiency(proficiency) >= CAPSTONE_UNLOCK_LEVEL[proficiency]); } - +static const int MAX_PLAYER_STAT_VALUE = 248; void setDefaultMonsterStats(Stat* stats, int sprite); bool isMonsterStatsDefault(Stat& myStats); const char* getSkillLangEntry(int skill); From 8abd22281a2c08be715e8460c8b0d424841a057e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 14:01:05 +1000 Subject: [PATCH 063/244] * more player death compendium events fixes --- src/actplayer.cpp | 7 +++++-- src/game.cpp | 11 ++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 19a744ef3..b133cb1f2 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -7577,8 +7577,6 @@ void actPlayer(Entity* my) net_packet->address.host = net_clients[PLAYER_NUM - 1].host; net_packet->address.port = net_clients[PLAYER_NUM - 1].port; sendPacketSafe(net_sock, -1, net_packet, PLAYER_NUM - 1); - - Compendium_t::Events_t::sendClientDataOverNet(PLAYER_NUM); } } @@ -7906,6 +7904,11 @@ void actPlayer(Entity* my) } } + if ( multiplayer == SERVER && !players[PLAYER_NUM]->isLocalPlayer() ) + { + Compendium_t::Events_t::sendClientDataOverNet(PLAYER_NUM); + } + assailant[PLAYER_NUM] = false; assailantTimer[PLAYER_NUM] = 0; diff --git a/src/game.cpp b/src/game.cpp index dd0c4e8fb..f409a1995 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2102,6 +2102,15 @@ void gameLogic(void) loadingSameLevelAsCurrent = false; darkmap = false; + bool playerDied[MAXPLAYERS] = { false }; + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( stats[i] && stats[i]->HP <= 0 ) + { + playerDied[i] = true; + } + } + // load map file loading = true; createLevelLoadScreen(5); @@ -2489,7 +2498,7 @@ void gameLogic(void) for ( c = 0; c < MAXPLAYERS; c++ ) { - Compendium_t::Events_t::onLevelChangeEvent(c, Compendium_t::Events_t::previousCurrentLevel, Compendium_t::Events_t::previousSecretlevel, prevmapname); + Compendium_t::Events_t::onLevelChangeEvent(c, Compendium_t::Events_t::previousCurrentLevel, Compendium_t::Events_t::previousSecretlevel, prevmapname, playerDied[c]); players[c]->compendiumProgress.playerAliveTimeTotal = 0; players[c]->compendiumProgress.playerGameTimeTotal = 0; } From 4ff60d7013e71072a5e3d380a2b558d31968f0f6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 14:01:17 +1000 Subject: [PATCH 064/244] * fix compendium tooltip hanging too low --- src/interface/playerinventory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 258913f4d..4418a07d1 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -6809,7 +6809,7 @@ void Player::HUD_t::finalizeFrameTooltip(Item* item, const int x, const int y, i int lowestY = 0; if ( compendiumTooltip ) { - lowestY = tooltipContainerFrame->getSize().h - *cvar_item_tooltip_lowest_y; + lowestY = tooltipContainerFrame->getSize().h - *cvar_item_tooltip_lowest_y - 38; } else { From 3f45e60f67110fd5ccd9763784858cb99443ebed Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 14:02:09 +1000 Subject: [PATCH 065/244] * character sheet/tooltips fix weapon atk ranges not being displayed correctly --- src/entity.cpp | 8 +++- src/entity.hpp | 2 +- src/interface/updatecharactersheet.cpp | 8 ++-- src/mod_tools.cpp | 13 +++++ src/ui/GameUI.cpp | 66 ++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 058f37153..2fe52b0f2 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -8661,7 +8661,7 @@ void Entity::attack(int pose, int charge, Entity* target) }*/ real_t variance = 20; real_t baseSkillModifier = 50.0; // 40-60 base - Entity::setMeleeDamageSkillModifiers(this, myStats, weaponskill, baseSkillModifier, variance); + Entity::setMeleeDamageSkillModifiers(this, myStats, weaponskill, baseSkillModifier, variance, nullptr); real_t skillModifier = baseSkillModifier - (variance / 2) + (myStats->getModifiedProficiency(weaponskill) / 2.0); skillModifier += (local_rng.rand() % (1 + static_cast(variance))); skillModifier /= 100.0; @@ -14542,7 +14542,7 @@ int getStatForProficiency(int skill) return statForProficiency; } -void Entity::setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, real_t& baseSkillModifier, real_t& variance) +void Entity::setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, real_t& baseSkillModifier, real_t& variance, ItemType* itemType) { bool shapeshifted = (my && my->behavior == &actPlayer && my->effectShapeshift != NOTHING); bool gungnir = false; @@ -14557,6 +14557,10 @@ void Entity::setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, gungnir = true; } } + if ( itemType && (*itemType) == ARTIFACT_SPEAR ) + { + gungnir = true; + } if ( skill == PRO_UNARMED ) { diff --git a/src/entity.hpp b/src/entity.hpp index 9efe0fdf1..729a80855 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -628,7 +628,7 @@ class Entity static Sint32 getAttack(Entity* my, Stat* myStats, bool isPlayer = false); static real_t getACEffectiveness(Entity* my, Stat* myStats, bool isPlayer, Entity* attacker, Stat* attackerStats, int& outNumBlessings); - static void setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, real_t& baseSkillModifier, real_t& variance); + static void setMeleeDamageSkillModifiers(Entity* my, Stat* myStats, int skill, real_t& baseSkillModifier, real_t& variance, ItemType* itemType); Sint32 getBonusAttackOnTarget(Stat& hitstats); Sint32 getRangedAttack(); Sint32 getThrownAttack(); diff --git a/src/interface/updatecharactersheet.cpp b/src/interface/updatecharactersheet.cpp index 8aa6938e6..88e3279d6 100644 --- a/src/interface/updatecharactersheet.cpp +++ b/src/interface/updatecharactersheet.cpp @@ -58,7 +58,7 @@ Sint32 displayAttackPower(const int player, AttackHoverText_t& output) { real_t variance = 20; real_t baseSkillModifier = 50.0; // 40-60 base - Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], PRO_UNARMED, baseSkillModifier, variance); + Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], PRO_UNARMED, baseSkillModifier, variance, nullptr); real_t skillModifierMin = baseSkillModifier - (variance / 2) + (stats[player]->getModifiedProficiency(PRO_UNARMED) / 2.0); real_t skillModifierMax = skillModifierMin + variance; skillModifierMin /= 100.0; @@ -100,7 +100,7 @@ Sint32 displayAttackPower(const int player, AttackHoverText_t& output) { real_t variance = 20; real_t baseSkillModifier = 50.0; // 40-60 base - Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance); + Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance, nullptr); real_t skillModifierMin = baseSkillModifier - (variance / 2) + (stats[player]->getModifiedProficiency(weaponskill) / 2.0); real_t skillModifierMax = skillModifierMin + variance; skillModifierMin /= 100.0; @@ -133,7 +133,7 @@ Sint32 displayAttackPower(const int player, AttackHoverText_t& output) { real_t variance = 20; real_t baseSkillModifier = 50.0; // 40-60 base - Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance); + Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance, nullptr); real_t skillModifierMin = baseSkillModifier - (variance / 2) + (stats[player]->getModifiedProficiency(weaponskill) / 2.0); real_t skillModifierMax = skillModifierMin + variance; skillModifierMin /= 100.0; @@ -236,7 +236,7 @@ Sint32 displayAttackPower(const int player, AttackHoverText_t& output) { real_t variance = 20; real_t baseSkillModifier = 50.0; // 40-60 base - Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance); + Entity::setMeleeDamageSkillModifiers(players[player]->entity, stats[player], weaponskill, baseSkillModifier, variance, nullptr); real_t skillModifierMin = baseSkillModifier - (variance / 2) + (stats[player]->getModifiedProficiency(weaponskill) / 2.0); real_t skillModifierMax = skillModifierMin + variance; skillModifierMin /= 100.0; diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index f35916e74..b3146d43e 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -4187,6 +4187,19 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType snprintf(buf, sizeof(buf), str.c_str(), weaponEffectiveness, getItemProficiencyName(proficiency).c_str()); } } + else if ( detailTag.compare("weapon_skill_modifier_range") == 0 ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + ItemType itemType = item.type; + Entity::setMeleeDamageSkillModifiers(compendiumTooltipIntro ? nullptr : players[player]->entity, + nullptr, proficiency, baseSkillModifier, variance, &itemType); + real_t lowest = baseSkillModifier - (variance / 2) + (compendiumTooltipIntro ? 0 : stats[player]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + + snprintf(buf, sizeof(buf), str.c_str(), (int)lowest, (int)highest, getItemProficiencyName(proficiency).c_str()); + } else if ( detailTag.compare("weapon_atk_from_player_stat") == 0 ) { if ( item.type == TOOL_WHIP ) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 5b81c80f3..e4fc827c9 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -32350,6 +32350,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "RANGED_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) @@ -32457,6 +32468,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "UNARMED_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) @@ -32531,6 +32553,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "SWORD_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) @@ -32595,6 +32628,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "POLEARM_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) @@ -32659,6 +32703,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "AXE_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) @@ -32723,6 +32778,17 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "WEAPON_DMG_EFFECTIVENESS" ) + { + real_t variance = 20; + real_t baseSkillModifier = 50.0; // 40-60 base + Entity::setMeleeDamageSkillModifiers(nullptr, nullptr, proficiency, baseSkillModifier, variance, nullptr); + + real_t lowest = baseSkillModifier - (variance / 2) + (stats[playernum]->getModifiedProficiency(proficiency) / 2.0); + lowest = std::min(100.0, std::max(0.0, lowest)); + real_t highest = std::min(100.0, lowest + variance); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)lowest, (int)highest); + } else if ( tag == "MACE_DMG_EFFECTIVENESS" ) { if ( proficiency == PRO_POLEARM ) From a6f4c13dfda3ff3f2271ebbd89035593c4da6bc9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 22:32:41 +1000 Subject: [PATCH 066/244] * adjust new unlock button compendium --- src/ui/Button.hpp | 1 + src/ui/MainMenu.cpp | 78 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/ui/Button.hpp b/src/ui/Button.hpp index a39a52277..001647b47 100644 --- a/src/ui/Button.hpp +++ b/src/ui/Button.hpp @@ -97,6 +97,7 @@ class Button : public Widget { const char* getBackgroundActivated() const { return backgroundActivated.c_str(); } SDL_Rect getTextOffset() const { return textOffset; } Uint32 getColor() const { return color; } + Uint32 getTextColor() const { return textColor; } const bool isOntop() const { return ontop; } void setBorder(int _border) { border = _border; } diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 568b61e34..49f30f03b 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32683,6 +32683,8 @@ namespace MainMenu { constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); constexpr auto compendiumContentsDivColor = makeColorRGB(42, 22, 18); + constexpr auto compendiumLoreCostAvailable = makeColorRGB(255, 255, 255); + constexpr auto compendiumLoreCostInactive = makeColorRGB(156, 156, 156); static void refreshCompendiumEntryWorld(std::string name, Frame* parent, const bool gamepadClick) { @@ -32748,11 +32750,11 @@ namespace MainMenu { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); + unlock_lore_cost->setTextColor(compendiumLoreCostAvailable); } else { - unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(compendiumLoreCostInactive); } } else @@ -34187,11 +34189,11 @@ namespace MainMenu { unlock_lore_cost->setText(std::to_string(compendiumEntry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= compendiumEntry.lorePoints ) { - unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); + unlock_lore_cost->setTextColor(compendiumLoreCostAvailable); } else { - unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(compendiumLoreCostInactive); } } else @@ -35596,11 +35598,11 @@ namespace MainMenu { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); + unlock_lore_cost->setTextColor(compendiumLoreCostAvailable); } else { - unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(compendiumLoreCostInactive); } } else @@ -35829,11 +35831,11 @@ namespace MainMenu { unlock_lore_cost->setText(std::to_string(entry.lorePoints).c_str()); if ( Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent >= entry.lorePoints ) { - unlock_lore_cost->setTextColor(makeColorRGB(255, 255, 255)); + unlock_lore_cost->setTextColor(compendiumLoreCostAvailable); } else { - unlock_lore_cost->setTextColor(makeColorRGB(128, 128, 128)); + unlock_lore_cost->setTextColor(compendiumLoreCostInactive); } } else @@ -40735,7 +40737,7 @@ namespace MainMenu { page_right_gradient_bottom->ontop = true; auto page_right_unlock = window->addFrame("page_right_unlock"); - page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 18, 376, 116 }); + page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 8, 376, 128 }); page_right_unlock->setHollow(true); page_right_unlock->setClickable(false); @@ -40744,7 +40746,7 @@ namespace MainMenu { page_right_to_unlock->setText(""); auto page_right_unlock_btn = page_right_unlock->addButton("page_right_unlock_btn"); - page_right_unlock_btn->setSize(SDL_Rect{ 0, 0, 376, 116 }); + page_right_unlock_btn->setSize(SDL_Rect{ 0, 0, 376, 128 }); page_right_unlock_btn->setHighlightColor(0xFFFFFFFF); page_right_unlock_btn->setColor(0xFFFFFFFF); page_right_unlock_btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); @@ -40760,6 +40762,7 @@ namespace MainMenu { page_right_unlock_btn->addWidgetAction("MenuConfirm", "FraggleMaggleStiggleWortz"); // some garbage so that this glyph isn't auto-bound page_right_unlock_btn->setMenuConfirmControlType(0); page_right_unlock_btn->setButtonsOffset(SDL_Rect{ 0, -20, 0, 0 }); + page_right_unlock_btn->setSelectorOffset(SDL_Rect{ 3, 9, -3, -11 }); page_right_unlock_btn->addWidgetAction("MenuPageLeft", "tab_left"); page_right_unlock_btn->addWidgetAction("MenuPageRight", "tab_right"); page_right_unlock_btn->addWidgetMovement("MenuAlt2", "nav_filter_sort"); @@ -40767,6 +40770,34 @@ namespace MainMenu { page_right_unlock_btn->setCallback([](Button& button) { compendiumRevealSection(&button); }); + page_right_unlock_btn->setTickCallback([](Widget& widget) { + auto btn = static_cast(&widget); + if ( isMouseVisible() ) + { + btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); + btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); + btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-press_00.png"); + } + else + { + btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); + btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); + btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button_00.png"); + if ( auto parent = static_cast(btn->getParent()) ) + { + if ( auto btn_cost = parent->findButton("unlock_lore_cost") ) + { + if ( btn_cost->getTextColor() == compendiumLoreCostAvailable ) + { + // available to buy + btn->setBackground("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); + btn->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); + btn->setBackgroundActivated("*images/ui/Main Menus/AdventureArchives/C_DetailsLocked_Button-high_00.png"); + } + } + } + } + }); /*auto reveal_unlock_badge = page_right_unlock->addImage(SDL_Rect{ 376 - 74, 28, 56, 62 }, 0xFFFFFFFF, @@ -40776,12 +40807,12 @@ namespace MainMenu { auto unlock_research_txt = page_right_unlock->addField("unlock_research_txt", 32); unlock_research_txt->setHJustify(Field::justify_t::LEFT); unlock_research_txt->setVJustify(Field::justify_t::TOP); - unlock_research_txt->setText("RESEARCH"); + unlock_research_txt->setText(Language::get(6211)); unlock_research_txt->setDisabled(false); - unlock_research_txt->setSize(SDL_Rect{ 30 - 4, 44 - 4, 272, 62 }); - unlock_research_txt->setFont("fonts/kongtext.ttf#32#0"); + unlock_research_txt->setSize(SDL_Rect{ 30 - 4, 44, 272, 62 }); + unlock_research_txt->setFont("fonts/kongtext.ttf#32#2"); unlock_research_txt->setOntop(true); - unlock_research_txt->setColor(makeColor(166, 166, 166, 255)); + unlock_research_txt->setColor(makeColor(224, 224, 224, 255)); unlock_research_txt->setInvisible(true); unlock_research_txt->setTickCallback([](Widget& widget) { auto parent = static_cast(widget.getParent()); @@ -40790,6 +40821,25 @@ namespace MainMenu { if ( auto btn = parent->findButton("unlock_lore_cost") ) { widget.setInvisible(btn->isInvisible()); + if ( auto txt = static_cast(&widget) ) + { + if ( btn->getTextColor() == compendiumLoreCostAvailable ) + { + auto unlock_btn = parent->findButton("page_right_unlock_btn"); + if ( !isMouseVisible() || (unlock_btn && unlock_btn->isHighlighted()) ) + { + txt->setColor(btn->getTextColor()); + } + else + { + txt->setColor(makeColorRGB(192, 192, 192)); + } + } + else + { + txt->setColor(btn->getTextColor()); + } + } } } }); From a370d5341e2aec509cee92da1f859cfeeccb80be Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 15 Aug 2024 22:32:50 +1000 Subject: [PATCH 067/244] * update lang --- lang/en.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en.txt b/lang/en.txt index a8b221e0b..4904f07ab 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6437,5 +6437,6 @@ Magic Required: %d (%s)# 6208 ELEMENTAL# 6209 PTS# 6210 TOTAL COMPLETION# +6211 RESEARCH# 6250 END# From 2c9096ad37ba00e9b7a2dce8912ebab935db6a0c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 16 Aug 2024 03:24:10 +1000 Subject: [PATCH 068/244] * formatting add extra bullet pts for codex/world --- src/mod_tools.cpp | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index b3146d43e..224d9f58a 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11595,9 +11595,27 @@ void Compendium_t::readCodexFromFile() obj.linesToHighlight.clear(); for ( auto& line : obj.details ) { - if ( line.size() > 0 && line[0] == '-' ) + if ( line.size() > 0 ) { - line[0] = '\x1E'; + if ( line[0] == '-' ) + { + line[0] = '\x1E'; + } + else + { + for ( size_t c = 0; c < line.size(); ++c ) + { + if ( line[c] == '-' ) + { + line[c] = '\x1E'; + break; + } + else if ( line[c] != ' ' ) + { + break; + } + } + } } } if ( w.HasMember("feature_img") ) @@ -11771,9 +11789,27 @@ void Compendium_t::readWorldFromFile() obj.linesToHighlight.clear(); for ( auto& line : obj.details ) { - if ( line.size() > 0 && line[0] == '-' ) + if ( line.size() > 0 ) { - line[0] = '\x1E'; + if ( line[0] == '-' ) + { + line[0] = '\x1E'; + } + else + { + for ( size_t c = 0; c < line.size(); ++c ) + { + if ( line[c] == '-' ) + { + line[c] = '\x1E'; + break; + } + else if ( line[c] != ' ' ) + { + break; + } + } + } } } if ( w.HasMember("details_line_highlights") ) From 2f706bd948c754bb4b3d309e77dcf1f346c05f78 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 16 Aug 2024 14:40:40 +1000 Subject: [PATCH 069/244] * sound updates --- src/ui/MainMenu.cpp | 52 +++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 49f30f03b..db84fb374 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -37633,13 +37633,13 @@ namespace MainMenu { { if ( !isMouseVisible() ) { - soundMove(); - playSound(83 + local_rng.rand() % 6, 64); + //soundMove(); + playSound(639, 128); } else { - soundMove(); - playSound(83 + local_rng.rand() % 6, 64); + //soundMove(); + playSound(639, 128); } if ( achDisplay.currentPage - 1 >= 0 ) { @@ -37706,13 +37706,13 @@ namespace MainMenu { { if ( !isMouseVisible() ) { - soundMove(); - playSound(83 + local_rng.rand() % 6, 64); + //soundMove(); + playSound(639, 128); } else { - soundMove(); - playSound(83 + local_rng.rand() % 6, 64); + //soundMove(); + playSound(639, 128); } if ( achDisplay.currentPage + 1 < achDisplay.pages.size() ) { @@ -39247,7 +39247,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "monsters"; @@ -39371,7 +39372,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "items"; @@ -39466,7 +39468,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "magic"; @@ -39561,7 +39564,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "world"; @@ -39657,7 +39661,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "codex"; @@ -39802,7 +39807,8 @@ namespace MainMenu { tab->setCallback([](Button& button) { if ( !contents_activate_from_filter ) { - soundActivate(); + //soundActivate(); + playSound(649 + local_rng.rand() % 8, 156); } contents_activate_from_filter = false; compendium_current = "achievements"; @@ -39971,7 +39977,14 @@ namespace MainMenu { }); nav_filter_sort->setCallback([](Button& button) { - soundCheckmark(); + if ( button.isPressed() ) + { + playSound(646, 128); + } + else + { + playSound(637, 128); + } if ( compendium_current == "achievements" ) { Compendium_t::AchievementData_t::achievementsNeedResort = true; @@ -40059,7 +40072,14 @@ namespace MainMenu { }); nav_filter_sort2->setCallback([](Button& button) { - soundCheckmark(); + if ( button.isPressed() ) + { + playSound(646, 128); + } + else + { + playSound(637, 128); + } Compendium_t::compendium_sorting_hide_undiscovered = button.isPressed(); if ( auto parent = static_cast(button.getParent()) ) { From feb25a57106da8a28ee473ffac50a6df7eb00f66 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 16 Aug 2024 17:27:23 +1000 Subject: [PATCH 070/244] * map hashes, fix cheats disabled --- src/files.cpp | 274 ++++++++++++++++++++++++++++++++++++-------- src/mod_tools.cpp | 8 +- src/ui/MainMenu.cpp | 12 +- 3 files changed, 234 insertions(+), 60 deletions(-) diff --git a/src/files.cpp b/src/files.cpp index 961e352d2..d2f8b3f2b 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -819,42 +819,216 @@ std::unordered_map mapHashes = { { "minetown.lmp", 842756 }, { "minotaur.lmp", 4409895 }, { "mysticlibrary.lmp", 360623 }, - { "ruins.lmp", 11472 }, + { "ruins.lmp", 92624 }, { "ruins00.lmp", 6373 }, - { "ruins01.lmp", 95175 }, - { "ruins02.lmp", 95577 }, - { "ruins03.lmp", 18465 }, - { "ruins04.lmp", 11113 }, - { "ruins05.lmp", 16194 }, - { "ruins06.lmp", 1890 }, - { "ruins07.lmp", 2486 }, - { "ruins08.lmp", 4682 }, - { "ruins09.lmp", 4704 }, - { "ruins10.lmp", 10987 }, - { "ruins11.lmp", 13490 }, - { "ruins12.lmp", 6662 }, - { "ruins13.lmp", 183250 }, - { "ruins14.lmp", 766 }, - { "ruins15.lmp", 772 }, - { "ruins16.lmp", 4681 }, - { "ruins17.lmp", 1848 }, - { "ruins18.lmp", 9066 }, - { "ruins19.lmp", 6897 }, - { "ruins20.lmp", 40374 }, - { "ruins21.lmp", 10664 }, - { "ruins22.lmp", 11523 }, - { "ruins23.lmp", 88 }, - { "ruins24.lmp", 36565 }, - { "ruins25.lmp", 1218 }, - { "ruins26.lmp", 17108 }, - { "ruins27.lmp", 1468 }, - { "ruins28.lmp", 276 }, - { "ruins29.lmp", 13097 }, - { "ruins30.lmp", 39050 }, - { "ruins31.lmp", 46626 }, - { "ruins32.lmp", 32243 }, - { "ruins33.lmp", 1597 }, - { "ruins34.lmp", 381 }, + { "ruins01.lmp", 149157 }, + { "ruins01a.lmp", 103231 }, + { "ruins01b.lmp", 156192 }, + { "ruins01c.lmp", 24298 }, + { "ruins01d.lmp", 15632 }, + { "ruins01e.lmp", 57306 }, + { "ruins02.lmp", 149481 }, + { "ruins02a.lmp", 97513 }, + { "ruins02b.lmp", 38538 }, + { "ruins02c.lmp", 59764 }, + { "ruins02d.lmp", 174860 }, + { "ruins02e.lmp", 74840 }, + { "ruins03.lmp", 79141 }, + { "ruins03a.lmp", 16088 }, + { "ruins03b.lmp", 44743 }, + { "ruins03c.lmp", 46486 }, + { "ruins03d.lmp", 28068 }, + { "ruins03e.lmp", 17496 }, + { "ruins04.lmp", 60569 }, + { "ruins04a.lmp", 11190 }, + { "ruins04b.lmp", 24108 }, + { "ruins04c.lmp", 6640 }, + { "ruins04d.lmp", 9275 }, + { "ruins04e.lmp", 13245 }, + { "ruins05.lmp", 138563 }, + { "ruins05a.lmp", 15194 }, + { "ruins05b.lmp", 16728 }, + { "ruins05c.lmp", 65468 }, + { "ruins05d.lmp", 77080 }, + { "ruins05e.lmp", 39438 }, + { "ruins06.lmp", 52488 }, + { "ruins06a.lmp", 10005 }, + { "ruins06b.lmp", 11882 }, + { "ruins06c.lmp", 10656 }, + { "ruins06d.lmp", 12742 }, + { "ruins06e.lmp", 56033 }, + { "ruins07.lmp", 102161 }, + { "ruins07a.lmp", 2150 }, + { "ruins07b.lmp", 81411 }, + { "ruins07c.lmp", 114854 }, + { "ruins07d.lmp", 45084 }, + { "ruins07e.lmp", 42387 }, + { "ruins08.lmp", 69920 }, + { "ruins08a.lmp", 12627 }, + { "ruins08b.lmp", 16729 }, + { "ruins08c.lmp", 39399 }, + { "ruins08d.lmp", 11756 }, + { "ruins08e.lmp", 11813 }, + { "ruins09.lmp", 69810 }, + { "ruins09a.lmp", 12624 }, + { "ruins09b.lmp", 9891 }, + { "ruins09c.lmp", 12694 }, + { "ruins09d.lmp", 3048 }, + { "ruins09e.lmp", 51819 }, + { "ruins10.lmp", 44004 }, + { "ruins10a.lmp", 10773 }, + { "ruins10b.lmp", 9221 }, + { "ruins10c.lmp", 9242 }, + { "ruins10d.lmp", 32823 }, + { "ruins10e.lmp", 18179 }, + { "ruins11.lmp", 55530 }, + { "ruins11a.lmp", 11046 }, + { "ruins11b.lmp", 8791 }, + { "ruins11c.lmp", 39211 }, + { "ruins11d.lmp", 25593 }, + { "ruins11e.lmp", 13047 }, + { "ruins12.lmp", 24121 }, + { "ruins12a.lmp", 6262 }, + { "ruins12b.lmp", 4804 }, + { "ruins12c.lmp", 2207 }, + { "ruins12d.lmp", 7859 }, + { "ruins12e.lmp", 4042 }, + { "ruins13.lmp", 213163 }, + { "ruins13a.lmp", 46245 }, + { "ruins13b.lmp", 143588 }, + { "ruins13c.lmp", 176468 }, + { "ruins13d.lmp", 12169 }, + { "ruins13e.lmp", 113050 }, + { "ruins14.lmp", 48408 }, + { "ruins14a.lmp", 608 }, + { "ruins14b.lmp", 891 }, + { "ruins14c.lmp", 2483 }, + { "ruins14d.lmp", 8723 }, + { "ruins14e.lmp", 11032 }, + { "ruins15.lmp", 48408 }, + { "ruins15a.lmp", 614 }, + { "ruins15b.lmp", 893 }, + { "ruins15c.lmp", 4910 }, + { "ruins15d.lmp", 8664 }, + { "ruins15e.lmp", 776 }, + { "ruins16.lmp", 42383 }, + { "ruins16a.lmp", 4493 }, + { "ruins16b.lmp", 4400 }, + { "ruins16c.lmp", 4800 }, + { "ruins16d.lmp", 1228 }, + { "ruins16e.lmp", 3028 }, + { "ruins17.lmp", 36976 }, + { "ruins17a.lmp", 6521 }, + { "ruins17b.lmp", 5539 }, + { "ruins17c.lmp", 5269 }, + { "ruins17d.lmp", 3739 }, + { "ruins17e.lmp", 6023 }, + { "ruins18.lmp", 49405 }, + { "ruins18a.lmp", 12111 }, + { "ruins18b.lmp", 7985 }, + { "ruins18c.lmp", 15650 }, + { "ruins18d.lmp", 2869 }, + { "ruins18e.lmp", 13637 }, + { "ruins19.lmp", 105023 }, + { "ruins19a.lmp", 2469 }, + { "ruins19b.lmp", 42444 }, + { "ruins19c.lmp", 8925 }, + { "ruins19d.lmp", 59478 }, + { "ruins19e.lmp", 359371 }, + { "ruins20.lmp", 239795 }, + { "ruins20a.lmp", 39623 }, + { "ruins20b.lmp", 170787 }, + { "ruins20c.lmp", 406127 }, + { "ruins20d.lmp", 57511 }, + { "ruins20e.lmp", 369845 }, + { "ruins21.lmp", 71662 }, + { "ruins21a.lmp", 4165 }, + { "ruins21b.lmp", 9257 }, + { "ruins21c.lmp", 4264 }, + { "ruins21d.lmp", 27792 }, + { "ruins21e.lmp", 18263 }, + { "ruins22.lmp", 218758 }, + { "ruins22a.lmp", 81278 }, + { "ruins22b.lmp", 192781 }, + { "ruins22c.lmp", 40728 }, + { "ruins22d.lmp", 72781 }, + { "ruins22e.lmp", 60361 }, + { "ruins23.lmp", 2830 }, + { "ruins23a.lmp", 102 }, + { "ruins23b.lmp", 380 }, + { "ruins23c.lmp", 223 }, + { "ruins23d.lmp", 223 }, + { "ruins23e.lmp", 104 }, + { "ruins24.lmp", 118224 }, + { "ruins24a.lmp", 30518 }, + { "ruins24b.lmp", 63399 }, + { "ruins24c.lmp", 62535 }, + { "ruins24d.lmp", 248223 }, + { "ruins24e.lmp", 36120 }, + { "ruins25.lmp", 73445 }, + { "ruins25a.lmp", 4058 }, + { "ruins25b.lmp", 3436 }, + { "ruins25c.lmp", 7994 }, + { "ruins25d.lmp", 33091 }, + { "ruins25e.lmp", 4050 }, + { "ruins26.lmp", 73990 }, + { "ruins26a.lmp", 18063 }, + { "ruins26b.lmp", 14285 }, + { "ruins26c.lmp", 46076 }, + { "ruins26d.lmp", 66885 }, + { "ruins26e.lmp", 50935 }, + { "ruins27.lmp", 9308 }, + { "ruins27a.lmp", 1234 }, + { "ruins27b.lmp", 2634 }, + { "ruins27c.lmp", 1307 }, + { "ruins27d.lmp", 3126 }, + { "ruins27e.lmp", 3019 }, + { "ruins28.lmp", 5501 }, + { "ruins28a.lmp", 483 }, + { "ruins28b.lmp", 697 }, + { "ruins28c.lmp", 10324 }, + { "ruins28d.lmp", 565 }, + { "ruins28e.lmp", 250 }, + { "ruins28f.lmp", 443 }, + { "ruins28g.lmp", 441 }, + { "ruins29.lmp", 46557 }, + { "ruins29a.lmp", 9562 }, + { "ruins29b.lmp", 19612 }, + { "ruins29c.lmp", 7999 }, + { "ruins29d.lmp", 7931 }, + { "ruins29e.lmp", 21346 }, + { "ruins30.lmp", 58106 }, + { "ruins30a.lmp", 46892 }, + { "ruins30b.lmp", 7881 }, + { "ruins30c.lmp", 6702 }, + { "ruins30d.lmp", 23092 }, + { "ruins30e.lmp", 70869 }, + { "ruins31.lmp", 62838 }, + { "ruins31a.lmp", 41977 }, + { "ruins31b.lmp", 12094 }, + { "ruins31c.lmp", 13341 }, + { "ruins31d.lmp", 39298 }, + { "ruins31e.lmp", 24223 }, + { "ruins32.lmp", 29693 }, + { "ruins32a.lmp", 15412 }, + { "ruins32b.lmp", 3907 }, + { "ruins32c.lmp", 8961 }, + { "ruins32d.lmp", 1852 }, + { "ruins32e.lmp", 8710 }, + { "ruins33.lmp", 15907 }, + { "ruins33a.lmp", 2594 }, + { "ruins33b.lmp", 905 }, + { "ruins33c.lmp", 1426 }, + { "ruins33d.lmp", 1786 }, + { "ruins33e.lmp", 1426 }, + { "ruins33f.lmp", 1788 }, + { "ruins33g.lmp", 1426 }, + { "ruins34.lmp", 5437 }, + { "ruins34a.lmp", 1790 }, + { "ruins34b.lmp", 8 }, + { "ruins34c.lmp", 1477 }, + { "ruins34d.lmp", 58 }, + { "ruins34e.lmp", 129 }, { "ruinssecret.lmp", 66694 }, { "sanctum.lmp", 6476194 }, { "shop-roomgen00.lmp", 11844 }, @@ -897,9 +1071,9 @@ std::unordered_map mapHashes = { { "swamp00.lmp", 13629 }, { "swamp01.lmp", 29459 }, { "swamp01a.lmp", 5303 }, - { "swamp01b.lmp", 5811 }, + { "swamp01b.lmp", 6563 }, { "swamp01c.lmp", 10334 }, - { "swamp01d.lmp", 5395 }, + { "swamp01d.lmp", 5571 }, { "swamp01e.lmp", 4060 }, { "swamp02.lmp", 39297 }, { "swamp02a.lmp", 9131 }, @@ -953,7 +1127,7 @@ std::unordered_map mapHashes = { { "swamp10a.lmp", 2322 }, { "swamp10b.lmp", 5975 }, { "swamp10c.lmp", 5260 }, - { "swamp10d.lmp", 6311 }, + { "swamp10d.lmp", 6911 }, { "swamp10e.lmp", 2344 }, { "swamp10f.lmp", 3680 }, { "swamp11.lmp", 64926 }, @@ -965,12 +1139,12 @@ std::unordered_map mapHashes = { { "swamp12.lmp", 52448 }, { "swamp12a.lmp", 7504 }, { "swamp12b.lmp", 14581 }, - { "swamp12c.lmp", 6268 }, - { "swamp12d.lmp", 9487 }, + { "swamp12c.lmp", 8585 }, + { "swamp12d.lmp", 12097 }, { "swamp12e.lmp", 13987 }, { "swamp13.lmp", 30293 }, { "swamp13a.lmp", 6911 }, - { "swamp13b.lmp", 6258 }, + { "swamp13b.lmp", 10109 }, { "swamp13c.lmp", 9103 }, { "swamp13d.lmp", 5933 }, { "swamp13e.lmp", 6334 }, @@ -981,11 +1155,11 @@ std::unordered_map mapHashes = { { "swamp14d.lmp", 19525 }, { "swamp14e.lmp", 6608 }, { "swamp15.lmp", 352 }, - { "swamp16.lmp", 15219 }, - { "swamp16a.lmp", 1414 }, + { "swamp16.lmp", 14028 }, + { "swamp16a.lmp", 1377 }, { "swamp16b.lmp", 1411 }, { "swamp16c.lmp", 856 }, - { "swamp16d.lmp", 1863 }, + { "swamp16d.lmp", 2052 }, { "swamp16e.lmp", 1154 }, { "swamp17.lmp", 31463 }, { "swamp17a.lmp", 5507 }, @@ -1030,7 +1204,7 @@ std::unordered_map mapHashes = { { "swamp23b.lmp", 18498 }, { "swamp23c.lmp", 26586 }, { "swamp23d.lmp", 8888 }, - { "swamp23e.lmp", 11640 }, + { "swamp23e.lmp", 14992 }, { "swamp24.lmp", 36395 }, { "swamp24a.lmp", 5045 }, { "swamp24b.lmp", 4267 }, @@ -1039,18 +1213,18 @@ std::unordered_map mapHashes = { { "swamp24e.lmp", 12981 }, { "swamp25.lmp", 34758 }, { "swamp25a.lmp", 5060 }, - { "swamp25b.lmp", 2489 }, + { "swamp25b.lmp", 6237 }, { "swamp25c.lmp", 12252 }, { "swamp25d.lmp", 5226 }, { "swamp25e.lmp", 5717 }, { "swamp25f.lmp", 10354 }, - { "swamp26.lmp", 41604 }, + { "swamp26.lmp", 40227 }, { "swamp26a.lmp", 4107 }, { "swamp26b.lmp", 2398 }, { "swamp26c.lmp", 1005 }, { "swamp26d.lmp", 3129 }, { "swamp26e.lmp", 1262 }, - { "swamp27.lmp", 46203 }, + { "swamp27.lmp", 42021 }, { "swamp27a.lmp", 4975 }, { "swamp27b.lmp", 3725 }, { "swamp27c.lmp", 2059 }, @@ -1098,9 +1272,9 @@ std::unordered_map mapHashes = { { "swamp34.lmp", 36795 }, { "swamp34a.lmp", 6929 }, { "swamp34b.lmp", 68279 }, - { "swamp34c.lmp", 23952 }, + { "swamp34c.lmp", 12514 }, { "swamp34d.lmp", 5546 }, - { "swamp34e.lmp", 14412 }, + { "swamp34e.lmp", 17278 }, { "swampsecret.lmp", 24027 }, { "swamptolabyrinth.lmp", 213824 }, { "temple.lmp", 6911955 }, diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 224d9f58a..215359475 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -14306,7 +14306,7 @@ static ConsoleVariable cvar_compendiumDebugSave("/compendium_debug_save", void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, const ItemType type, Sint32 value, const bool loadingValue, const int spellID) { - if ( !allowedCompendiumProgress() ) { return; } + if ( !allowedCompendiumProgress() && !loadingValue ) { return; } if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } @@ -14512,7 +14512,7 @@ void Compendium_t::Events_t::eventUpdate(int playernum, const EventTags tag, con void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags tag, const Entity* entity, Sint32 value, const bool loadingValue, const int entryID) { - if ( !allowedCompendiumProgress() ) { return; } + if ( !allowedCompendiumProgress() && !loadingValue ) { return; } if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } @@ -14677,7 +14677,7 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue, const int entryID, const bool commitUniqueValue) { - if ( !allowedCompendiumProgress() ) { return; } + if ( !allowedCompendiumProgress() && !loadingValue ) { return; } if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } @@ -14865,7 +14865,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag, const char* category, Sint32 value, const bool loadingValue, const int entryID, const bool floorEvent) { - if ( !allowedCompendiumProgress() ) { return; } + if ( !allowedCompendiumProgress() && !loadingValue ) { return; } if ( intro && !loadingValue ) { return; } if ( playernum < 0 || playernum >= MAXPLAYERS ) { return; } diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index db84fb374..79cc3f179 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -39248,7 +39248,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "monsters"; @@ -39373,7 +39373,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "items"; @@ -39469,7 +39469,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "magic"; @@ -39565,7 +39565,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "world"; @@ -39662,7 +39662,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "codex"; @@ -39808,7 +39808,7 @@ namespace MainMenu { if ( !contents_activate_from_filter ) { //soundActivate(); - playSound(649 + local_rng.rand() % 8, 156); + playSound(649 + local_rng.rand() % 8, 128); } contents_activate_from_filter = false; compendium_current = "achievements"; From 182652ddc009bb5a38431f8ea7028adcc574c049 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 16 Aug 2024 17:50:58 +1000 Subject: [PATCH 071/244] * support feature imgs in world compendium section --- src/mod_tools.cpp | 4 +++ src/mod_tools.hpp | 1 + src/ui/MainMenu.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 215359475..a2f5efbc0 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11786,6 +11786,10 @@ void Compendium_t::readWorldFromFile() { obj.lorePoints = w["lore_points"].GetInt(); } + if ( w.HasMember("feature_img") ) + { + obj.featureImg = w["feature_img"].GetString(); + } obj.linesToHighlight.clear(); for ( auto& line : obj.details ) { diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 63646aa05..2f473e2b4 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3966,6 +3966,7 @@ struct Compendium_t std::vector details; std::set unlockAchievements; std::set unlockTags; + std::string featureImg = ""; int id = -1; int lorePoints = 0; }; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 79cc3f179..053c77d93 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32785,6 +32785,66 @@ namespace MainMenu { { if ( auto details = page_right->findField("world_details") ) { + auto featureImg = page_right->findImage("feature_img"); + if ( featureImg ) + { + featureImg->disabled = true; + } + auto featureTxt = page_right->findField("feature txt"); + if ( featureTxt ) + { + featureTxt->setDisabled(true); + } + + Field* tipsTxt = page_right->findField("tips txt"); + if ( tipsTxt ) + { + SDL_Rect tipsPos = tipsTxt->getSize(); + if ( featureTxt ) + { + tipsPos.y = featureTxt->getSize().y; + } + tipsTxt->setSize(tipsPos); + + SDL_Rect pos = details->getSize(); + pos.y = tipsPos.y + 27; + details->setSize(pos); + } + + if (entry.featureImg != "") + { + if ( auto imgGet = Image::get(entry.featureImg.c_str()) ) + { + featureImg->pos.w = imgGet->getWidth(); + featureImg->pos.h = imgGet->getHeight(); + featureImg->disabled = false; + featureImg->path = entry.featureImg; + } + } + if ( !featureImg->disabled ) + { + if ( featureTxt ) + { + featureTxt->setDisabled(false); + } + SDL_Rect pos = details->getSize(); + int offset = featureImg->pos.y + featureImg->pos.h + 16; + if ( tipsTxt ) + { + SDL_Rect tipsPos = tipsTxt->getSize(); + tipsPos.y += offset; + tipsTxt->setSize(tipsPos); + + SDL_Rect pos = details->getSize(); + pos.y = tipsPos.y + 27; + details->setSize(pos); + } + } + + if ( tipsTxt ) + { + tipsTxt->setDisabled(false); + } std::string txt = ""; for ( auto& str : entry.details ) { @@ -38107,6 +38167,18 @@ namespace MainMenu { charTxt->setSize(SDL_Rect{ padx, pady, page_right_inner->getSize().w - padx - 26, 24 }); charTxt->setColor(makeColor(198, 190, 179, 255)); + auto featureTxt = page_right_inner->addField("feature txt", 64); + featureTxt->setFont(menu_option_font); + featureTxt->setText(Language::get(6212)); + featureTxt->setHJustify(Field::justify_t::LEFT); + featureTxt->setVJustify(Field::justify_t::TOP); + featureTxt->setSize(charTxt->getSize()); + featureTxt->setColor(makeColor(198, 190, 179, 255)); + featureTxt->setDisabled(true); + auto featureImg = page_right_inner->addImage(SDL_Rect{ 8, featureTxt->getSize().y + 28, 0, 0 }, + 0xFFFFFFFF, "", "feature_img"); + featureImg->disabled = true; + int statx = padx + 4; int staty = pady + 18 + 9; auto statsTxt = page_right_inner->addField("world_details", 1024); From 72562f20e275f76d771b67116c214025d932e604 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 22 Aug 2024 02:57:08 +1000 Subject: [PATCH 072/244] * breakables (including warning effects for items/monsters inside) * breakable dmg calc bit easier to destroy * gold now animates when dropped * fix slime spawning in walls * fix transparency bug in multiplayer --- src/actboulder.cpp | 1 + src/actgeneral.cpp | 307 ++++++++++++++++++++++++++++--- src/actgib.cpp | 4 + src/actgold.cpp | 94 ++++++++++ src/actitem.cpp | 2 +- src/actmonster.cpp | 79 ++++++-- src/collision.cpp | 8 +- src/collision.hpp | 2 +- src/entity.cpp | 95 +++++++--- src/interface/consolecommand.cpp | 8 +- src/interface/drawminimap.cpp | 13 +- src/magic/actmagic.cpp | 10 +- src/mod_tools.cpp | 121 ++++++++++++ src/mod_tools.hpp | 26 +++ src/monster_minotaur.cpp | 1 + src/monster_sentrybot.cpp | 3 +- src/monster_vampire.cpp | 25 +-- src/net.cpp | 47 ++++- src/ui/MainMenu.cpp | 3 + 19 files changed, 751 insertions(+), 98 deletions(-) diff --git a/src/actboulder.cpp b/src/actboulder.cpp index d2f6e45e4..64ece3142 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -621,6 +621,7 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit { playSoundEntity(entity, 28, 64); entity->colliderCurrentHP = 0; + entity->colliderKillerUid = 0; playSoundEntity(my, 181, 128); } } diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index fdbcc27d3..2304a8926 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -604,7 +604,7 @@ bool Entity::isColliderWeakToBoulders() const return colliderDmgType.boulderDestroys; } -bool Entity::isColliderWeakToSkill(int proficiency) const +bool Entity::isColliderWeakToSkill(const int proficiency) const { if ( !isDamageableCollider() ) { return false; } auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; @@ -612,6 +612,14 @@ bool Entity::isColliderWeakToSkill(int proficiency) const return colliderDmgType.proficiencyBonusDamage.find(proficiency) != colliderDmgType.proficiencyBonusDamage.end(); } +bool Entity::isColliderResistToSkill(const int proficiency) const +{ + if ( !isDamageableCollider() ) { return false; } + auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; + auto& colliderDmgType = EditorEntityData_t::colliderDmgTypes[colliderData.damageCalculationType]; + return colliderDmgType.proficiencyResistDamage.find(proficiency) != colliderDmgType.proficiencyResistDamage.end(); +} + bool Entity::isColliderDamageableByMelee() const { if ( !isDamageableCollider() ) { return false; } @@ -641,6 +649,164 @@ bool Entity::isDamageableCollider() const return behavior == &actColliderDecoration && colliderMaxHP > 0; } +bool Entity::isColliderWall() const +{ + if ( !isDamageableCollider() ) { return false; } + auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; + if ( colliderData.hpbarLookupName.find("_wall") != std::string::npos ) + { + return true; + } + return false; +} + +void Entity::colliderOnDestroy() +{ + if ( multiplayer == CLIENT ) { return; } + flags[PASSABLE] = true; + if ( colliderHideMonster != 0 ) + { + int type = colliderHideMonster % 1000; + auto monster = summonMonster((Monster)type, ((int)(x / 16)) * 16 + 8, ((int)(y / 16)) * 16 + 8); + if ( monster ) + { + monster->yaw = yaw; + monster->lookAtEntity(*monster); + monster->monsterLookDir = yaw; + if ( Stat* stats = monster->getStats() ) + { + stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; + if ( stats->type == GHOUL && currentlevel >= 15 ) + { + strcpy(stats->name, "enslaved ghoul"); + stats->setAttribute("special_npc", "enslaved ghoul"); + } + if ( stats->type == AUTOMATON ) + { + monster->monsterStoreType = 1; // damaged + } + if ( colliderKillerUid != 0 ) + { + if ( Entity* killer = uidToEntity(colliderKillerUid) ) + { + if ( killer->behavior == &actPlayer ) + { + messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName(stats->type).c_str(), Language::get(getColliderLangName())); + } + } + } + } + //monster->attack(monster->getAttackPose(), 0, nullptr); + } + } + if ( colliderContainedEntity != 0 ) + { + if ( auto entity = uidToEntity(colliderContainedEntity) ) + { + if ( entity->behavior == &actItem || entity->behavior == &actGoldBag ) + { + if ( entity->flags[INVISIBLE] ) + { + if ( entity->behavior == &actGoldBag ) + { + entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; + entity->z = 0.0; + entity->flags[INVISIBLE] = false; + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "BREK"); + SDLNet_Write32(static_cast(entity->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + + // find other matching gold piles + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(entity, 2); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr; node = node->next ) + { + Entity* ent = (Entity*)node->element; + if ( ent && ent->behavior == &actGoldBag && ent != entity && ent->goldInContainer != 0 + && ent->goldInContainer == entity->goldInContainer ) + { + ent->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(ent->yaw); + ent->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(ent->yaw); + ent->vel_z = (-40 - local_rng.rand() % 5) * .01; + ent->goldBouncing = 0; + ent->goldInContainer = 0; + ent->z = 0.0; + ent->flags[INVISIBLE] = false; + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "BREK"); + SDLNet_Write32(static_cast(ent->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + } + } + } + entity->goldInContainer = 0; + } + else if ( entity->behavior == &actItem ) + { + //entity->flags[UPDATENEEDED] = true; + entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->itemContainer = 0; + entity->z = 0.0; + entity->itemNotMoving = 0; + entity->itemNotMovingClient = 0; + entity->flags[USERFLAG1] = false; // enable collision + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "BREK"); + SDLNet_Write32(static_cast(entity->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + } + } + } + } + } +} + int Entity::getColliderLangName() const { if ( !isDamageableCollider() ) { return 1; } @@ -739,6 +905,8 @@ void actColliderDecoration(Entity* my) } } + + if ( my->isDamageableCollider() ) { if ( my->ticks == 1 ) @@ -746,56 +914,145 @@ void actColliderDecoration(Entity* my) my->createWorldUITooltip(); } - auto& colliderData = EditorEntityData_t::colliderData[my->colliderDamageTypes]; - if ( my->flags[BURNING] && my->flags[BURNABLE] ) + if ( multiplayer != CLIENT ) { - if ( ticks % 30 == 0 ) + auto& colliderData = EditorEntityData_t::colliderData[my->colliderDamageTypes]; + if ( my->flags[BURNING] && my->flags[BURNABLE] ) { - my->colliderCurrentHP--; + if ( ticks % 30 == 0 ) + { + my->colliderCurrentHP--; + if ( my->colliderCurrentHP <= 0 ) + { + my->colliderKillerUid = 0; + } + } } - } - my->colliderOldHP = my->colliderCurrentHP; + my->colliderOldHP = my->colliderCurrentHP; - if ( my->colliderCurrentHP <= 0 ) - { - int sprite = colliderData.gib; - if ( sprite > 0 ) + if ( my->colliderCurrentHP > 0 ) { - createParticleRock(my, sprite); - if ( multiplayer == SERVER ) + if ( my->colliderHideMonster >= 1000 ) // summon a monster when player is near { - serverSpawnMiscParticles(my, PARTICLE_EFFECT_ABILITY_ROCK, sprite); + Entity* found = nullptr; + if ( ticks % TICKS_PER_SECOND == 0 ) + { + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !found; ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( entity && (entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) + { + real_t tangent = atan2(entity->y - my->y, entity->x - my->x); + lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity); + if ( hit.entity == entity ) + { + found = entity; + break; + } + } + } + } + } + + if ( found ) + { + int type = my->colliderHideMonster % 1000; + my->colliderHideMonster = 0; + bool bOldFlag = my->flags[PASSABLE]; + my->flags[PASSABLE] = true; + auto monster = summonMonster((Monster)type, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8); + if ( monster ) + { + monster->yaw = my->yaw; + monster->lookAtEntity(*found); + if ( monster->checkEnemy(found) ) + { + monster->monsterAcquireAttackTarget(*found, MONSTER_STATE_PATH); + } + my->colliderCurrentHP = 0; + my->colliderKillerUid = 0; + + if ( Stat* stats = monster->getStats() ) + { + stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; + if ( stats->type == GHOUL && currentlevel >= 15 ) + { + strcpy(stats->name, "enslaved ghoul"); + stats->setAttribute("special_npc", "enslaved ghoul"); + } + if ( stats->type == AUTOMATON ) + { + monster->monsterStoreType = 1; // damaged + } + if ( found->behavior == &actPlayer ) + { + messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName(stats->type).c_str(), Language::get(my->getColliderLangName())); + } + } + } + my->flags[PASSABLE] = bOldFlag; + } } } - if ( colliderData.sfxBreak > 0 ) + if ( my->colliderCurrentHP <= 0 ) { - playSoundEntity(my, colliderData.sfxBreak, 128); + int sprite = colliderData.gib; + if ( sprite > 0 ) + { + createParticleRock(my, sprite); + if ( multiplayer == SERVER ) + { + serverSpawnMiscParticles(my, PARTICLE_EFFECT_ABILITY_ROCK, sprite); + } + } + if ( colliderData.sfxBreak > 0 ) + { + playSoundEntity(my, colliderData.sfxBreak, 128); + } + my->colliderOnDestroy(); + list_RemoveNode(my->mynode); + return; } - list_RemoveNode(my->mynode); - return; } } } void Entity::colliderHandleDamageMagic(int damage, Entity &magicProjectile, Entity *caster) { + auto oldHP = colliderCurrentHP; colliderCurrentHP -= damage; //Decrease object health. if ( caster ) { + if ( colliderCurrentHP <= 0 ) + { + colliderKillerUid = caster->getUID(); + } if ( caster->behavior == &actPlayer ) { if ( colliderCurrentHP <= 0 ) { - if ( magicProjectile.behavior == &actBomb ) - { - messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName())); - } - else + if ( oldHP > 0 ) { - messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(getColliderLangName())); + if ( magicProjectile.behavior == &actBomb ) + { + messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(3617), items[magicProjectile.skill[21]].getIdentifiedName(), Language::get(getColliderLangName())); + } + else + { + messagePlayer(caster->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(getColliderLangName())); + } + if ( isColliderWall() ) + { + Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + } } - Compendium_t::Events_t::eventUpdateWorld(caster->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); } else { diff --git a/src/actgib.cpp b/src/actgib.cpp index d45340d41..0db72e784 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -371,6 +371,10 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) vel = 0.25; entity->vel_z = -.4; } + if ( parentent->isDamageableCollider() ) + { + entity->z -= 4; + } entity->yaw = (local_rng.rand() % 360) * PI / 180.0; entity->vel_x = vel * cos(entity->yaw); entity->vel_y = vel * sin(entity->yaw); diff --git a/src/actgold.cpp b/src/actgold.cpp index 1472d900b..a35d176f8 100644 --- a/src/actgold.cpp +++ b/src/actgold.cpp @@ -19,6 +19,7 @@ #include "player.hpp" #include "prng.hpp" #include "scores.hpp" +#include "collision.hpp" /*------------------------------------------------------------------------------- @@ -125,4 +126,97 @@ void actGoldBag(Entity* my) { my->flags[NOUPDATE] = true; } + + // gravity + bool onground = false; + real_t groundheight = my->sprite == 1379 ? 7.75 : 6.25; + + my->flags[BURNING] = false; + + if ( my->goldBouncing == 0 ) + { + if ( my->z < groundheight ) + { + // fall + my->vel_z += 0.04; + my->z += my->vel_z; + my->roll += 0.08; + } + else + { + if ( my->x >= 0 && my->y >= 0 && my->x < map.width << 4 && my->y < map.height << 4 ) + { + const int tile = map.tiles[(int)(my->y / 16) * MAPLAYERS + (int)(my->x / 16) * MAPLAYERS * map.height]; + if ( tile ) + { + onground = true; + + my->vel_z *= -.7; // bounce + if ( my->vel_z > -.35 ) + { + my->roll = 0.0; + my->z = groundheight; + my->vel_z = 0.0; + } + else + { + // just bounce off the ground. + my->z = groundheight - .0001; + } + } + else + { + // fall (no ground here) + my->vel_z += 0.04; + my->z += my->vel_z; + my->roll += 0.08; + } + } + else + { + // fall (out of bounds) + my->vel_z += 0.04; + my->z += my->vel_z; + my->roll += 0.08; + } + } + + // falling out of the map + if ( my->z > 128 ) + { + list_RemoveNode(my->mynode); + return; + } + + // don't perform unneeded computations on items that have basically no velocity + if ( onground && + my->z > groundheight - .0001 && my->z < groundheight + .0001 && + fabs(my->vel_x) < 0.02 && fabs(my->vel_y) < 0.02 ) + { + my->goldBouncing = 1; + return; + } + + double result = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my); + my->yaw += result * .05; + if ( result != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y) ) + { + if ( !hit.side ) + { + my->vel_x *= -.5; + my->vel_y *= -.5; + } + else if ( hit.side == HORIZONTAL ) + { + my->vel_x *= -.5; + } + else + { + my->vel_y *= -.5; + } + } + + my->vel_x = my->vel_x * .925; + my->vel_y = my->vel_y * .925; + } } diff --git a/src/actitem.cpp b/src/actitem.cpp index 890153fb6..30af427da 100644 --- a/src/actitem.cpp +++ b/src/actitem.cpp @@ -155,7 +155,7 @@ void actItem(Entity* my) { // select appropriate model my->skill[2] = -5; - if ( my->itemSokobanReward != 1 ) + if ( my->itemSokobanReward != 1 && my->itemContainer == 0 ) { my->flags[INVISIBLE] = false; } diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 24b6b4654..867b241c4 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -3347,24 +3347,19 @@ void actMonster(Entity* my) } if ( myStats->GOLD > 0 && myStats->monsterNoDropItems == 0 ) { - int x = std::min(std::max(0, (int)(my->x / 16)), map.width - 1); - int y = std::min(std::max(0, (int)(my->y / 16)), map.height - 1); - - // check for floor to drop gold... - if ( map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] ) - { - entity = newEntity(130, 0, map.entities, nullptr); // 130 = goldbag model - entity->sizex = 4; - entity->sizey = 4; - entity->x = my->x; - entity->y = my->y; - entity->z = 6; - entity->yaw = (local_rng.rand() % 360) * PI / 180.0; - entity->flags[PASSABLE] = true; - entity->flags[UPDATENEEDED] = true; - entity->behavior = &actGoldBag; - entity->skill[0] = myStats->GOLD; // amount - } + entity = newEntity(myStats->GOLD < 5 ? 1379 : 130, 0, map.entities, nullptr); // 130 = goldbag model + entity->sizex = 4; + entity->sizey = 4; + entity->x = my->x; + entity->y = my->y; + entity->goldAmount = myStats->GOLD; // amount + entity->z = 0; + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->yaw = (local_rng.rand() % 360) * PI / 180.0; + entity->goldBouncing = 0; + entity->flags[PASSABLE] = true; + entity->flags[UPDATENEEDED] = true; + entity->behavior = &actGoldBag; } // die @@ -5467,6 +5462,7 @@ void actMonster(Entity* my) else if ( hit.entity->isDamageableCollider() && myStats->type == MINOTAUR ) { hit.entity->colliderCurrentHP = 0; + hit.entity->colliderKillerUid = 0; playSoundEntity(hit.entity, 28, 64); } else if ( hit.entity->behavior == &actFurniture ) @@ -5487,6 +5483,29 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } + else if ( hit.entity->isDamageableCollider() ) + { + // break it down! + my->monsterHitTime++; + if ( my->monsterHitTime >= HITRATE ) + { + my->monsterAttack = my->getAttackPose(); // random attack motion + my->monsterHitTime = HITRATE / 4; + my->monsterAttackTime = 0; + int damage = 2 + local_rng.rand() % 3; + damage += std::max(0, myStats->STR / 8); + + hit.entity->colliderCurrentHP -= damage; + hit.entity->colliderKillerUid = 0; + + int sound = 28; + if ( hit.entity->getColliderSfxOnHit() > 0 ) + { + sound = hit.entity->getColliderSfxOnHit(); + } + playSoundEntity(hit.entity, sound, 64); + } + } else if ( hit.entity->behavior == &actBoulder && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) { // asplode the rock @@ -6556,6 +6575,29 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } + else if ( hit.entity->isDamageableCollider() ) + { + // break it down! + my->monsterHitTime++; + if ( my->monsterHitTime >= HITRATE ) + { + my->monsterAttack = my->getAttackPose(); // random attack motion + my->monsterHitTime = HITRATE / 4; + my->monsterAttackTime = 0; + int damage = 2 + local_rng.rand() % 3; + damage += std::max(0, myStats->STR / 8); + + hit.entity->colliderCurrentHP -= damage; + hit.entity->colliderKillerUid = 0; + + int sound = 28; + if ( hit.entity->getColliderSfxOnHit() > 0 ) + { + sound = hit.entity->getColliderSfxOnHit(); + } + playSoundEntity(hit.entity, sound, 64); + } + } else if ( hit.entity->behavior == &actChest && myStats->type == MINOTAUR ) { hit.entity->skill[3] = 0; // chestHealth @@ -6598,6 +6640,7 @@ void actMonster(Entity* my) else if ( hit.entity->isDamageableCollider() && myStats->type == MINOTAUR ) { hit.entity->colliderCurrentHP = 0; + hit.entity->colliderKillerUid = 0; } else if ( hit.entity->behavior == &actBoulder && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) { diff --git a/src/collision.cpp b/src/collision.cpp index dc6e829da..82e5a0fc9 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -1067,7 +1067,9 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en { continue; } - if ( entity->behavior == &actFurniture && ignoreFurniture ) + if ( ignoreFurniture && + (entity->behavior == &actFurniture + || entity->isDamageableCollider()) ) { continue; // see through furniture cause we'll bust it down } @@ -1691,7 +1693,7 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r -------------------------------------------------------------------------------*/ -int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList) +int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList, bool checkWalls) { node_t* node; Entity* entity; @@ -1721,7 +1723,7 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity if ( y >= 0 && y < map.height << 4 ) { int index = (y >> 4) * MAPLAYERS + (x >> 4) * MAPLAYERS * map.height; - if (map.tiles[OBSTACLELAYER + index]) // wall + if (checkWalls && map.tiles[OBSTACLELAYER + index]) // wall { return 1; } diff --git a/src/collision.hpp b/src/collision.hpp index 340c4a9c0..8f6137ea8 100644 --- a/src/collision.hpp +++ b/src/collision.hpp @@ -33,4 +33,4 @@ real_t clipMove(real_t* x, real_t* y, real_t vx, real_t vy, Entity* my); Entity* findEntityInLine(Entity* my, real_t x1, real_t y1, real_t angle, int entities, Entity* target); real_t lineTrace(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground); real_t lineTraceTarget(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target); //If the linetrace function encounters the linetrace entity, it returns even if it's invisible or passable. -int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList = true); +int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList = true, bool checkWalls = true); diff --git a/src/entity.cpp b/src/entity.cpp index 2fe52b0f2..f07c18d3b 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -160,6 +160,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli itemReceivedDetailsFromServer(skill[25]), itemAutoSalvageByPlayer(skill[26]), itemSplooshed(skill[27]), + itemContainer(skill[29]), itemWaterBob(fskill[2]), gateInit(skill[1]), gateStatus(skill[3]), @@ -289,6 +290,9 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli colliderCurrentHP(skill[12]), colliderOldHP(skill[13]), colliderInit(skill[14]), + colliderContainedEntity(skill[15]), + colliderHideMonster(skill[16]), + colliderKillerUid(skill[17]), furnitureType(skill[0]), furnitureInit(skill[1]), furnitureDir(skill[3]), @@ -338,6 +342,8 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), + goldBouncing(skill[3]), + goldInContainer(skill[4]), interactedByMonster(skill[47]), highlightForUI(fskill[29]), highlightForUIGlow(fskill[28]), @@ -8071,13 +8077,18 @@ void Entity::attack(int pose, int charge, Entity* target) weaponskill = getWeaponSkill(myStats->weapon); if ( hit.entity->behavior == &actColliderDecoration ) { + if ( weaponskill >= 0 && hit.entity->isColliderResistToSkill(weaponskill) ) + { + damage = 1; + } + else + { + damage = 2 + local_rng.rand() % 3; + } if ( weaponskill >= 0 && hit.entity->isColliderWeakToSkill(weaponskill) ) { - axe = (myStats->getModifiedProficiency(weaponskill) / 20); - if ( myStats->getModifiedProficiency(weaponskill) >= SKILL_LEVEL_LEGENDARY ) - { - axe = 9; - } + axe = 2 * (myStats->getModifiedProficiency(weaponskill) / 20); + axe = std::min(axe, 9); } } else if ( weaponskill == PRO_AXE ) @@ -8087,6 +8098,7 @@ void Entity::attack(int pose, int charge, Entity* target) { axe = 9; } + axe = std::min(axe, 9); } } else @@ -8094,13 +8106,18 @@ void Entity::attack(int pose, int charge, Entity* target) weaponskill = PRO_UNARMED; if ( hit.entity->behavior == &actColliderDecoration ) { + if ( hit.entity->isColliderResistToSkill(weaponskill) ) + { + damage = 1; + } + else + { + damage = 2 + local_rng.rand() % 3; + } if ( hit.entity->isColliderWeakToSkill(weaponskill) ) { - axe = (myStats->getModifiedProficiency(weaponskill) / 20); - if ( myStats->getModifiedProficiency(weaponskill) >= SKILL_LEVEL_LEGENDARY ) - { - axe = 9; - } + axe = 2 * (myStats->getModifiedProficiency(weaponskill) / 20); + axe = std::min(axe, 9); } } else if ( hit.entity->behavior != &::actChest && !mimic ) @@ -8110,6 +8127,7 @@ void Entity::attack(int pose, int charge, Entity* target) { axe = 9; } + axe = std::min(axe, 9); } } if ( pose == PLAYER_POSE_GOLEM_SMASH ) @@ -8242,9 +8260,13 @@ void Entity::attack(int pose, int charge, Entity* target) } else if ( hit.entity->isDamageableCollider() ) { + hit.entity->colliderKillerUid = getUID(); messagePlayer(player, MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()), Language::get(hit.entity->getColliderLangName())); - Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + if ( hit.entity->isColliderWall() ) + { + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + } } else if ( hit.entity->behavior == &::actFurniture ) { @@ -8346,11 +8368,13 @@ void Entity::attack(int pose, int charge, Entity* target) { hit.entity->skill[0]--; //Deplete one usage. - //50% chance spawn a slime. + //50% chance spawn a slime. if ( local_rng.rand() % 2 == 0 ) { // spawn slime - Entity* monster = summonMonster(SLIME, x, y); + int ox = hit.entity->x / 16; + int oy = hit.entity->y / 16; + Entity* monster = summonMonster(SLIME, ox * 16 + 8, oy * 16 + 8); if ( monster ) { auto& rng = hit.entity->entity_rng ? *hit.entity->entity_rng : local_rng; @@ -8359,6 +8383,10 @@ void Entity::attack(int pose, int charge, Entity* target) messagePlayer(player, MESSAGE_HINT, Language::get(582)); Stat* monsterStats = monster->getStats(); monsterStats->LVL = 4; + if ( behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(skill[2], Compendium_t::CPDM_SINKS_SLIMES, "sink", 1); + } } } @@ -12243,27 +12271,38 @@ void Entity::awardXP(Entity* src, bool share, bool root) && (src->monsterAllySummonRank != 0 || src->monsterIsTinkeringCreation()) ) { - if ( root && behavior == &actPlayer ) + if ( root ) { - if ( multiplayer == SINGLE ) + if ( src->monsterIsTinkeringCreation() ) { - if ( splitscreen ) + int compendiumPlayer = behavior == &actPlayer ? skill[2] : -1; + if ( behavior == &actMonster ) { - Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + if ( auto leader = monsterAllyGetPlayerLeader() ) + { + compendiumPlayer = leader->skill[2]; + } } - else + if ( multiplayer == SINGLE ) { - Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_SOLO, src, 1); + if ( splitscreen ) + { + Compendium_t::Events_t::eventUpdateMonster(compendiumPlayer, Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + } + else + { + Compendium_t::Events_t::eventUpdateMonster(compendiumPlayer, Compendium_t::CPDM_KILLED_SOLO, src, 1); + } } - } - else - { - Compendium_t::Events_t::eventUpdateMonster(skill[2], Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); - for ( int i = 0; i < MAXPLAYERS; ++i ) + else { - if ( !client_disconnected[i] ) + Compendium_t::Events_t::eventUpdateMonster(compendiumPlayer, Compendium_t::CPDM_KILLED_MULTIPLAYER, src, 1); + for ( int i = 0; i < MAXPLAYERS; ++i ) { - Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_KILLED_PARTY, src, 1); + if ( !client_disconnected[i] ) + { + Compendium_t::Events_t::eventUpdateMonster(i, Compendium_t::CPDM_KILLED_PARTY, src, 1); + } } } } @@ -12809,7 +12848,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) } leader->awardXP(src, true, false); - if ( leader->behavior == &actPlayer ) + if ( leader->behavior == &actPlayer && root ) { if ( destStats->monsterIsCharmed == 1 ) { @@ -12833,7 +12872,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) } else { - Compendium_t::Events_t::eventUpdateMonster(0, Compendium_t::CPDM_KILLED_SOLO, src, 1); + Compendium_t::Events_t::eventUpdateMonster(leader->skill[2], Compendium_t::CPDM_KILLED_SOLO, src, 1); } } else diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 8018f2068..674fea594 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -2239,17 +2239,19 @@ namespace ConsoleCommands { else { playSoundEntity(players[player]->entity, 242 + local_rng.rand() % 4, 64); - auto entity = newEntity(130, 0, map.entities, nullptr); // 130 = goldbag model + auto entity = newEntity(amount < 5 ? 1379 : 130, 0, map.entities, nullptr); // 130 = goldbag model + entity->goldAmount = amount; // amount entity->sizex = 4; entity->sizey = 4; entity->x = players[player]->entity->x; entity->y = players[player]->entity->y; - entity->z = 6; + entity->z = 0; + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; entity->yaw = (local_rng.rand() % 360) * PI / 180.0; entity->flags[PASSABLE] = true; entity->flags[UPDATENEEDED] = true; entity->behavior = &actGoldBag; - entity->goldAmount = amount; // amount } messagePlayer(player, MESSAGE_INVENTORY, Language::get(2594), amount); } diff --git a/src/interface/drawminimap.cpp b/src/interface/drawminimap.cpp index c543675b6..5c76bd9ff 100644 --- a/src/interface/drawminimap.cpp +++ b/src/interface/drawminimap.cpp @@ -186,6 +186,10 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) } } } + else if ( entity->isDamageableCollider() && entity->colliderHideMonster != 0 ) + { + entityPointsOfInterest.push_back(entity); + } else if ( entity->isBoulderSprite() ) { entityPointsOfInterest.push_back(entity); @@ -485,7 +489,8 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) } } } - if ( entity->behavior == &actMonster && entity->monsterAllyIndex < 0 ) + if ( (entity->behavior == &actMonster && entity->monsterAllyIndex < 0) + || (entity->isDamageableCollider() && entity->colliderHideMonster != 0) ) { bool warningEffect = false; { @@ -611,7 +616,8 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) if ( stats[i]->shoes != NULL ) { - if ( stats[i]->shoes->type == ARTIFACT_BOOTS ) + if ( stats[i]->shoes->type == ARTIFACT_BOOTS + && entity->behavior == &actMonster ) { if ( (abs(entity->vel_x) > 0.1 || abs(entity->vel_y) > 0.1) && players[i] && players[i]->entity @@ -633,7 +639,8 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) const int i = player; if ( stats[i]->shoes != NULL ) { - if ( stats[i]->shoes->type == ARTIFACT_BOOTS ) + if ( stats[i]->shoes->type == ARTIFACT_BOOTS + && entity->behavior == &actMonster ) { if ( (abs(entity->vel_x) > 0.1 || abs(entity->vel_y) > 0.1) && players[i] && players[i]->entity diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 38a86fdbe..6ce763cb5 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -4460,6 +4460,10 @@ void createParticleRock(Entity* parent, int sprite) for ( int c = 0; c < 5; c++ ) { Entity* entity = newEntity(sprite != -1 ? sprite : 78, 1, map.entities, nullptr); //Particle entity. + if ( entity->sprite == 1336 ) + { + entity->sprite = 1336 + local_rng.rand() % 3; + } entity->sizex = 1; entity->sizey = 1; entity->x = parent->x + (-4 + local_rng.rand() % 9); @@ -7113,13 +7117,17 @@ bool magicDig(Entity* parent, Entity* projectile, int numRocks, int randRocks) } } + hit.entity->colliderOnDestroy(); if ( parent ) { if ( parent->behavior == &actPlayer && hit.entity->isDamageableCollider() ) { messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(4337), Language::get(hit.entity->getColliderLangName())); // you destroy the %s! - Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + if ( hit.entity->isColliderWall() ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + } } } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index a2f5efbc0..615bd6f7c 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -8822,6 +8822,7 @@ void GameplayPreferences_t::serverProcessGameConfig() EditorEntityData_t editorEntityData; std::map EditorEntityData_t::colliderData; std::map EditorEntityData_t::colliderDmgTypes; +std::map> EditorEntityData_t::colliderRandomGenPool; void EditorEntityData_t::readFromFile() { const std::string filename = "data/entity_data.json"; @@ -8858,6 +8859,7 @@ void EditorEntityData_t::readFromFile() colliderData.clear(); colliderDmgTypes.clear(); + colliderRandomGenPool.clear(); auto& entityTypes = d["entities"]; if ( entityTypes.HasMember("collider_dmg_calcs") ) { @@ -8907,6 +8909,41 @@ void EditorEntityData_t::readFromFile() } } } + if ( itr->value.HasMember("resist_damage_skills") && itr->value["resist_damage_skills"].IsArray() ) + { + for ( auto itr2 = itr->value["resist_damage_skills"].Begin(); itr2 != itr->value["resist_damage_skills"].End(); ++itr2 ) + { + std::string s = itr2->GetString(); + if ( s == "PRO_AXE" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_AXE); + } + else if ( s == "PRO_SWORD" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_SWORD); + } + else if ( s == "PRO_MACE" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_MACE); + } + else if ( s == "PRO_POLEARM" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_POLEARM); + } + else if ( s == "PRO_UNARMED" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_UNARMED); + } + else if ( s == "PRO_MAGIC" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_MAGIC); + } + else if ( s == "PRO_RANGED" ) + { + colliderDmg.proficiencyResistDamage.insert(PRO_RANGED); + } + } + } } } if ( entityTypes.HasMember("collider_dmg_types") ) @@ -8919,6 +8956,25 @@ void EditorEntityData_t::readFromFile() auto& collider = colliderData[index]; collider.name = itr->value["name"].GetString(); collider.gib = itr->value["gib_model"].GetInt(); + collider.gib_hit.clear(); + if ( itr->value.HasMember("gib_hit_model") ) + { + if ( itr->value["gib_hit_model"].IsInt() ) + { + collider.gib_hit.push_back(itr->value["gib_hit_model"].GetInt()); + } + else if ( itr->value["gib_hit_model"].IsArray() ) + { + for ( auto itr2 = itr->value["gib_hit_model"].Begin(); + itr2 != itr->value["gib_hit_model"].End(); ++itr2 ) + { + if ( itr2->IsInt() ) + { + collider.gib_hit.push_back(itr2->GetInt()); + } + } + } + } collider.sfxBreak = itr->value["sfx_break"].GetInt(); collider.sfxHit = itr->value["sfx_hit"].GetInt(); collider.damageCalculationType = itr->value["damage_calc"].GetString(); @@ -8926,6 +8982,71 @@ void EditorEntityData_t::readFromFile() collider.hitMessageLangEntry = itr->value["hit_message"].GetInt(); collider.breakMessageLangEntry = itr->value["break_message"].GetInt(); collider.hpbarLookupName = itr->value["hp_bar_lookup_name"].GetString(); + collider.hideMonsters.clear(); + if ( itr->value.HasMember("random_gen_pool") ) + { + if ( itr->value["random_gen_pool"].IsObject() ) + { + for ( auto itr2 = itr->value["random_gen_pool"].MemberBegin(); + itr2 != itr->value["random_gen_pool"].MemberEnd(); ++itr2 ) + { + if ( itr2->name.IsString() ) + { + EditorEntityData_t::colliderRandomGenPool[itr2->name.GetString()][index] = + itr2->value.GetInt(); + } + } + } + } + if ( itr->value.HasMember("events") ) + { + for ( auto itr2 = itr->value["events"].MemberBegin(); + itr2 != itr->value["events"].MemberEnd(); ++itr2 ) + { + std::string mapname = itr2->name.GetString(); + for ( auto itr3 = itr2->value.MemberBegin(); + itr3 != itr2->value.MemberEnd(); ++itr3 ) + { + if ( !strcmp(itr3->name.GetString(), "summon") ) + { + auto& data = collider.hideMonsters[mapname]; + if ( itr3->value.IsArray() ) + { + for ( auto val = itr3->value.Begin(); val != itr3->value.End(); ++val ) + { + if ( val->IsString() ) + { + for ( int i = 0; i < NUMMONSTERS; ++i ) + { + if ( !strcmp(val->GetString(), monstertypename[i]) ) + { + data.push_back(i); + break; + } + } + } + } + } + } + } + } + } + + collider.overrideProperties.clear(); + if ( itr->value.HasMember("override_editor_props") ) + { + if ( itr->value["override_editor_props"].IsObject() ) + { + for ( auto itr2 = itr->value["override_editor_props"].MemberBegin(); + itr2 != itr->value["override_editor_props"].MemberEnd(); ++itr2 ) + { + if ( itr2->value.IsInt() ) + { + collider.overrideProperties[itr2->name.GetString()] = itr2->value.GetInt(); + } + } + } + } } } } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 2f473e2b4..fc3da021e 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3326,6 +3326,7 @@ struct EditorEntityData_t struct EntityColliderData_t { int gib = 0; + std::vector gib_hit; int sfxBreak = 0; int sfxHit = 0; std::string damageCalculationType = "default"; @@ -3334,6 +3335,29 @@ struct EditorEntityData_t int entityLangEntry = 4335; int hitMessageLangEntry = 2509; int breakMessageLangEntry = 2510; + std::map> hideMonsters; + std::map overrideProperties; + bool hasOverride(std::string key) + { + auto find = overrideProperties.find(key); + if ( find != overrideProperties.end() ) + { + return true; + } + else + { + return false; + } + } + int getOverride(std::string key) + { + auto find = overrideProperties.find(key); + if ( find != overrideProperties.end() ) + { + return find->second; + } + return 0; + } }; struct ColliderDmgProperties_t { @@ -3345,9 +3369,11 @@ struct EditorEntityData_t bool boulderDestroys = false; bool showAsWallOnMinimap = false; std::unordered_set proficiencyBonusDamage; + std::unordered_set proficiencyResistDamage; }; static std::map colliderDmgTypes; static std::map colliderData; + static std::map> colliderRandomGenPool; static void readFromFile(); }; extern EditorEntityData_t editorEntityData; diff --git a/src/monster_minotaur.cpp b/src/monster_minotaur.cpp index 066c08d30..5846bc1aa 100644 --- a/src/monster_minotaur.cpp +++ b/src/monster_minotaur.cpp @@ -1007,6 +1007,7 @@ void actMinotaurCeilingBuster(Entity* my) if ( multiplayer != CLIENT ) { entity->colliderCurrentHP = 0; + entity->colliderKillerUid = 0; } } else if ( entity->behavior == &actGate ) diff --git a/src/monster_sentrybot.cpp b/src/monster_sentrybot.cpp index 4ce92802c..96ce5fc61 100644 --- a/src/monster_sentrybot.cpp +++ b/src/monster_sentrybot.cpp @@ -1072,7 +1072,8 @@ void gyroBotAnimate(Entity* my, Stat* myStats, double dist) { if ( my->monsterAllyPickupItems == ALLY_GYRO_DETECT_MONSTERS ) { - if ( ent->behavior == &actMonster && ent->monsterAllyIndex < 0 ) + if ( (ent->behavior == &actMonster && ent->monsterAllyIndex < 0) + || (ent->isDamageableCollider() && ent->colliderHideMonster != 0) ) { if ( entityDist(my, ent) < TOUCHRANGE * 5 ) { diff --git a/src/monster_vampire.cpp b/src/monster_vampire.cpp index befc32347..0c67ccec3 100644 --- a/src/monster_vampire.cpp +++ b/src/monster_vampire.cpp @@ -85,22 +85,25 @@ void initVampire(Entity* my, Stat* myStats) myStats->LVL = 18; myStats->GOLD = 50 + rng.rand() % 50; myStats->RANDOM_GOLD = 0; - for ( c = 0; c < 4; ++c ) + if ( !myStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] ) { - if ( rng.rand() % 2 == 0 ) + for ( c = 0; c < 4; ++c ) { - Entity* entity = summonMonster(GHOUL, my->x, my->y); - if ( entity ) + if ( rng.rand() % 2 == 0 ) { - entity->parent = my->getUID(); - Stat* followerStats = entity->getStats(); - if ( followerStats ) + Entity* entity = summonMonster(GHOUL, my->x, my->y); + if ( entity ) { - strcpy(followerStats->name, "enslaved ghoul"); - followerStats->setAttribute("special_npc", "enslaved ghoul"); - followerStats->leader_uid = entity->parent; + entity->parent = my->getUID(); + Stat* followerStats = entity->getStats(); + if ( followerStats ) + { + strcpy(followerStats->name, "enslaved ghoul"); + followerStats->setAttribute("special_npc", "enslaved ghoul"); + followerStats->leader_uid = entity->parent; + } + entity->seedEntityRNG(rng.getU32()); } - entity->seedEntityRNG(rng.getU32()); } } } diff --git a/src/net.cpp b/src/net.cpp index ebf0b1122..8fd075ee2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -521,6 +521,7 @@ void sendEntityUDP(Entity* entity, int c, bool guarantee) SDLNet_Write16((Sint16)(entity->vel_x * 32), &net_packet->data[40]); SDLNet_Write16((Sint16)(entity->vel_y * 32), &net_packet->data[42]); SDLNet_Write16((Sint16)(entity->vel_z * 32), &net_packet->data[44]); + net_packet->data[46] = 0; for ( j = 0; j < 8; j++ ) { if ( entity->flags[j + 16] ) @@ -1897,6 +1898,7 @@ void clientActions(Entity* entity) entity->flags[NOUPDATE] = true; break; case 130: + case 1379: entity->behavior = &actGoldBag; break; case Player::Ghost_t::GHOST_MODEL_P1: @@ -2624,6 +2626,43 @@ static std::unordered_map clientPacketHandlers = { } }}, + // breakable dropped item + { 'BREK', []() { + Uint32 uid = SDLNet_Read32(&net_packet->data[4]); + Entity* entity = uidToEntity(uid); + if ( entity ) + { + if ( entity->behavior == &actItem ) + { + //entity->flags[UPDATENEEDED] = true; + if ( entity->flags[INVISIBLE] ) + { + entity->flags[INVISIBLE] = false; + entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->itemContainer = 0; + entity->z = 0.0; + entity->itemNotMoving = 0; + entity->itemNotMovingClient = 0; + entity->flags[USERFLAG1] = false; // enable collision + } + } + else if ( entity->behavior == &actGoldBag ) + { + if ( entity->flags[INVISIBLE] ) + { + entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; + entity->z = 0.0; + entity->flags[INVISIBLE] = false; + } + } + } + }}, + // ghost interact item { 'GHOI', []() { Uint32 uid = SDLNet_Read32(&net_packet->data[4]); @@ -6842,17 +6881,19 @@ static std::unordered_map serverPacketHandlers = { { //Drop gold. playSoundEntity(players[player]->entity, 242 + local_rng.rand() % 4, 64); - auto entity = newEntity(130, 0, map.entities, nullptr); // 130 = goldbag model + auto entity = newEntity(amount < 5 ? 1379 : 130, 0, map.entities, nullptr); // 130 = goldbag model entity->sizex = 4; entity->sizey = 4; entity->x = players[player]->entity->x; entity->y = players[player]->entity->y; - entity->z = 6; + entity->goldAmount = amount; // amount + entity->z = 0; + entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; entity->yaw = (local_rng.rand() % 360) * PI / 180.0; entity->flags[PASSABLE] = true; entity->flags[UPDATENEEDED] = true; entity->behavior = &actGoldBag; - entity->goldAmount = amount; // amount } }}, diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 053c77d93..2abcb2ef3 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -39018,6 +39018,9 @@ namespace MainMenu { txt->setOntop(true); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); + SDL_Rect pos = txt->getSize(); + pos.x = 958; + txt->setSize(pos); if ( auto parent = static_cast(txt->getParent()) ) { if ( auto lore_points_balance = parent->findFrame("lore_points_balance") ) From c8555acf59b29296635b4c1533a56b9226999f81 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 22 Aug 2024 02:59:30 +1000 Subject: [PATCH 073/244] * breakable map gen * remove kobold from citadel * fix vampire quest book crash when mimic * fix salvage destroying invis items --- src/magic/castSpell.cpp | 2 +- src/maps.cpp | 717 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 657 insertions(+), 62 deletions(-) diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 90489145b..b992287f6 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -1215,7 +1215,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { nextItemNode = itemNode->next; Entity* itemEntity = (Entity*)itemNode->element; - if ( itemEntity && itemEntity->behavior == &actItem && entityDist(itemEntity, caster) < TOUCHRANGE ) + if ( itemEntity && !itemEntity->flags[INVISIBLE] && itemEntity->behavior == &actItem && entityDist(itemEntity, caster) < TOUCHRANGE ) { Item* toSalvage = newItemFromEntity(itemEntity); if ( toSalvage && GenericGUI[i].isItemSalvageable(toSalvage, i) ) diff --git a/src/maps.cpp b/src/maps.cpp index 0412d3cdb..02c2ffdad 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -334,7 +334,6 @@ int monsterCurve(int level) switch ( map_rng.rand() % 15 ) { case 0: - return KOBOLD; case 1: return INCUBUS; case 2: @@ -3332,6 +3331,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int numGenGold = 0; int numGenDecorations = 0; + std::vector itemsGeneratedList; static ConsoleVariable cvar_underworldshrinetest("/underworldshrinetest", false); //printlog("j: %d\n",j); @@ -3721,11 +3721,13 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( map_rng.rand() % 10 == 0 ) // 10% chance { entity = newEntity(9, 1, map.entities, nullptr); // gold + entity->goldAmount = 0; numGenGold++; } else { entity = newEntity(8, 1, map.entities, nullptr); // item + itemsGeneratedList.push_back(entity->getUID()); setSpriteAttributes(entity, nullptr, nullptr); numGenItems++; } @@ -3854,11 +3856,13 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( map_rng.rand() % 10 == 0 ) // 10% chance { entity = newEntity(9, 1, map.entities, nullptr); // gold + entity->goldAmount = 0; numGenGold++; } else { entity = newEntity(8, 1, map.entities, nullptr); // item + itemsGeneratedList.push_back(entity->getUID()); setSpriteAttributes(entity, nullptr, nullptr); numGenItems++; } @@ -4001,6 +4005,406 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } } + int numBreakables = std::min(15, numpossiblelocations / 10); + struct BreakableNode_t + { + BreakableNode_t(int _walls, int _x, int _y, int _dir) + { + walls = _walls; + x = _x; + y = _y; + dir = _dir; + }; + int walls; + int x; + int y; + int dir; + }; + auto compFuncBreakable = [](BreakableNode_t& lhs, BreakableNode_t& rhs) + { + return lhs.walls < rhs.walls; + }; + auto findBreakables = EditorEntityData_t::colliderRandomGenPool.find(map.name); + if ( findBreakables == EditorEntityData_t::colliderRandomGenPool.end() ) + { + numBreakables = 0; + } + std::priority_queue, decltype(compFuncBreakable)> breakableLocations(compFuncBreakable); + for ( c = 0; c < std::min(numBreakables, numpossiblelocations); ++c ) + { + // choose a random location from those available + pickedlocation = map_rng.rand() % numpossiblelocations; + i = -1; + int x = 0; + int y = 0; + bool skipPossibleLocationsDecrement = false; + while ( 1 ) + { + if ( possiblelocations[y + x * map.height] == true ) + { + ++i; + if ( i == pickedlocation ) + { + break; + } + } + ++x; + if ( x >= map.width ) + { + x = 0; + ++y; + if ( y >= map.height ) + { + y = 0; + } + } + } + + std::set walls; + std::set corners; + for ( int x2 = -1; x2 <= 1; x2++ ) + { + for ( int y2 = -1; y2 <= 1; y2++ ) + { + if ( x2 == 0 && y2 == 0 ) + { + continue; + } + + int checkx = x + x2; + int checky = y + y2; + if ( checkx >= 0 && checkx < map.width ) + { + if ( checky >= 0 && checky < map.height ) + { + int index = (checky) * MAPLAYERS + (checkx) * MAPLAYERS * map.height; + if ( map.tiles[OBSTACLELAYER + index] ) + { + if ( (x2 == -1 && y2 == -1) || (x2 == 1 && y2 == 1) + || (x2 == -1 && y2 == 1) || (x2 == 1 && y2 == -1) ) + { + corners.insert(checkx + checky * 1000); + } + else + { + walls.insert(checkx + checky * 1000); + } + } + } + } + } + } + + possiblelocations[y + x * map.height] = false; + numpossiblelocations--; + + if ( walls.size() == 0 || walls.size() >= 4 ) + { + // try again + --c; + continue; + } + + std::set freespaces; + for ( int x2 = -1; x2 <= 1; x2++ ) + { + for ( int y2 = -1; y2 <= 1; y2++ ) + { + if ( x2 == 0 && y2 == 0 ) { continue; } + int checkx = x + x2; + int checky = y + y2; + if ( walls.find(checkx + checky * 1000) != walls.end() + || corners.find(checkx + checky * 1000) != corners.end() ) + { + continue; + } + if ( checkx >= 0 && checkx < map.width ) + { + if ( checky >= 0 && checky < map.height ) + { + int index = (checky) * MAPLAYERS + (checkx) * MAPLAYERS * map.height; + if ( swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]] ) + { + continue; + } + if ( !checkObstacle((checkx) * 16, (checky) * 16, NULL, NULL, false, false) ) + { + freespaces.insert(checkx + checky * 1000); + } + } + } + } + } + + bool foundSpace = false; + if ( (walls.size() == 1 && freespaces.size() >= 5) + || (walls.size() == 2 && freespaces.size() >= 3) + || (walls.size() == 3 && freespaces.size() >= 1) ) + { + int numIslands = 0; + std::set reachedTiles; + std::map> islands; + for ( auto it = freespaces.begin(); it != freespaces.end(); ++it ) + { + if ( reachedTiles.find(*it) == reachedTiles.end() ) + { + // new island + std::queue frontier; + frontier.push(*it); + reachedTiles.insert(*it); + while ( !frontier.empty() ) + { + auto currentKey = frontier.front(); + frontier.pop(); + + const int ix = (currentKey) % 1000; + const int iy = (currentKey) / 1000; + + islands[numIslands].insert(currentKey); + + int checkKey = (ix + 1) + ((iy) * 1000); + if ( freespaces.find(checkKey) != freespaces.end() + && reachedTiles.find(checkKey) == reachedTiles.end() ) + { + frontier.push(checkKey); + reachedTiles.insert(checkKey); + } + checkKey = (ix - 1) + ((iy) * 1000); + if ( freespaces.find(checkKey) != freespaces.end() + && reachedTiles.find(checkKey) == reachedTiles.end() ) + { + frontier.push(checkKey); + reachedTiles.insert(checkKey); + } + checkKey = (ix) + ((iy + 1) * 1000); + if ( freespaces.find(checkKey) != freespaces.end() + && reachedTiles.find(checkKey) == reachedTiles.end() ) + { + frontier.push(checkKey); + reachedTiles.insert(checkKey); + } + checkKey = (ix) + ((iy - 1) * 1000); + if ( freespaces.find(checkKey) != freespaces.end() + && reachedTiles.find(checkKey) == reachedTiles.end() ) + { + frontier.push(checkKey); + reachedTiles.insert(checkKey); + } + } + if ( !islands[numIslands].empty() ) + { + ++numIslands; + } + } + } + + for ( auto& island : islands ) + { + if ( (walls.size() == 1 && island.second.size() >= 5) + || (walls.size() == 2 && island.second.size() >= 3) + || (walls.size() == 3 && island.second.size() >= 1) ) + { + std::vector dirs; + if ( walls.size() == 3 ) + { + if ( walls.find((x + 1) + (y + 0) * 1000) == walls.end() ) + { + dirs.push_back(0); + } + else if ( walls.find((x - 1) + (y + 0) * 1000) == walls.end() ) + { + dirs.push_back(4); + } + else if ( walls.find((x + 0) + (y + 1) * 1000) == walls.end() ) + { + dirs.push_back(2); + } + else if ( walls.find((x + 0) + (y - 1) * 1000) == walls.end() ) + { + dirs.push_back(6); + } + } + else + { + if ( walls.find((x + 1) + (y + 0) * 1000) != walls.end() ) + { + dirs.push_back(4); + } + else if ( walls.find((x - 1) + (y + 0) * 1000) != walls.end() ) + { + dirs.push_back(0); + } + else if ( walls.find((x + 0) + (y + 1) * 1000) != walls.end() ) + { + dirs.push_back(6); + } + else if ( walls.find((x + 0) + (y - 1) * 1000) != walls.end() ) + { + dirs.push_back(2); + } + } + int picked = dirs[map_rng.rand() % dirs.size()]; + breakableLocations.push(BreakableNode_t(walls.size(), x, y, picked)); + foundSpace = true; + break; + } + } + } + + if ( !foundSpace ) + { + --c; + } + } + + int breakableGoodies = breakableLocations.size() * 80 / 100; + int breakableMonsters = 0; + if ( findBreakables != EditorEntityData_t::colliderRandomGenPool.end() && findBreakables->second.size() > 0 && breakableGoodies > 0 ) + { + int breakableItemsFromGround = 0; + std::vector chances; + std::vector ids; + for ( auto& pair : findBreakables->second ) + { + ids.push_back(pair.first); + chances.push_back(pair.second); + } + while ( !breakableLocations.empty() ) + { + auto& top = breakableLocations.top(); + int x = top.x; + int y = top.y; + + Entity* breakable = newEntity(179, 1, map.entities, nullptr); + breakable->x = x * 16.0; + breakable->y = y * 16.0; + breakable->colliderDecorationRotation = top.dir; + + int picked = map_rng.discrete(chances.data(), chances.size()); + breakable->colliderDamageTypes = ids[picked]; + + Monster monsterEvent = NOTHING; + auto findData = EditorEntityData_t::colliderData.find(breakable->colliderDamageTypes); + if ( findData != EditorEntityData_t::colliderData.end() ) + { + auto findMap = findData->second.hideMonsters.find(map.name); + if ( findMap != findData->second.hideMonsters.end() ) + { + if ( findMap->second.size() > 0 ) + { + int picked = findMap->second[map_rng.rand() % findMap->second.size()]; + if ( picked > NOTHING && picked < NUMMONSTERS ) + { + monsterEvent = (Monster)picked; + } + } + } + } + + if ( breakableGoodies > 0 ) + { + --breakableGoodies; + if ( (breakableMonsters < 2 && monsterEvent != NOTHING && map_rng.rand() % 10 == 0) ) // 10% monster inside + { + if ( map_rng.rand() % 2 == 0 ) + { + breakable->colliderHideMonster = monsterEvent; + } + else + { + breakable->colliderHideMonster = 1000 + monsterEvent; + } + ++breakableMonsters; + } + else if ( map_rng.rand() % 2 == 1 ) // 50% chance + { + int totalGold = 12 + map_rng.rand() % 9; // 12 - 20 gold + + { + Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold + entity->x = breakable->x; + entity->y = breakable->y; + int amount = (totalGold / 4) + map_rng.rand() % std::max(1, totalGold / 4); + totalGold -= amount; + entity->goldAmount = amount; + entity->flags[INVISIBLE] = true; + entity->yaw = breakable->yaw; + entity->goldInContainer = breakable->getUID(); + breakable->colliderContainedEntity = entity->getUID(); + numGenGold++; + } + { + Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold + entity->x = breakable->x; + entity->y = breakable->y; + int amount = (totalGold / 4) + map_rng.rand() % std::max(1, totalGold / 4); + totalGold -= amount; + entity->goldAmount = amount; + totalGold -= entity->goldAmount; + entity->flags[INVISIBLE] = true; + entity->yaw = breakable->yaw + PI / 3; + entity->goldInContainer = breakable->getUID(); + numGenGold++; + } + { + Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold + entity->x = breakable->x; + entity->y = breakable->y; + entity->goldAmount = std::max(1, totalGold); + entity->flags[INVISIBLE] = true; + entity->yaw = breakable->yaw + 2 * PI / 3; + entity->goldInContainer = breakable->getUID(); + numGenGold++; + } + } + else + { + if ( itemsGeneratedList.size() > 10 && breakableItemsFromGround < 6 ) + { + // steal an item from the ground + size_t index = map_rng.rand() % itemsGeneratedList.size(); + Uint32 uid = itemsGeneratedList.at(index); + itemsGeneratedList.erase(itemsGeneratedList.begin() + index); + if ( Entity* entity = uidToEntity(uid) ) + { + entity->x = breakable->x; + entity->y = breakable->y; + entity->flags[INVISIBLE] = true; + entity->itemContainer = breakable->getUID(); + entity->yaw = breakable->yaw; + breakable->colliderContainedEntity = entity->getUID(); + ++breakableItemsFromGround; + } + } + else + { + Entity* entity = newEntity(8, 1, map.entities, nullptr); // item + setSpriteAttributes(entity, nullptr, nullptr); + entity->x = breakable->x; + entity->y = breakable->y; + entity->flags[INVISIBLE] = true; + entity->itemContainer = breakable->getUID(); + entity->yaw = breakable->yaw; + breakable->colliderContainedEntity = entity->getUID(); + numGenItems++; + } + } + } + + if ( false ) + { + messagePlayer(0, MESSAGE_DEBUG, "pick: %d | x: %d y: %d", picked, x, y); + Entity* ent = newEntity(245, 0, map.entities, nullptr); + //ent->behavior = &actBoulder; + ent->x = x * 16.0 + 8; + ent->y = y * 16.0 + 8; + ent->z = 24.0; + ent->flags[PASSABLE] = true; + } + breakableLocations.pop(); + } + } + // on hell levels, lava doesn't bubble. helps performance /*if( !strcmp(map.name,"Hell") ) { for( node=map.entities->first; node!=NULL; node=node->next ) { @@ -4018,6 +4422,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple list_FreeAll(&subRoomMapList); list_FreeAll(&mapList); list_FreeAll(&doorList); + printlog("successfully generated a dungeon with %d rooms, %d monsters, %d gold, %d items, %d decorations.\n", roomcount, nummonsters, numGenGold, numGenItems, numGenDecorations); //messagePlayer(0, "successfully generated a dungeon with %d rooms, %d monsters, %d gold, %d items, %d decorations.", roomcount, nummonsters, numGenGold, numGenItems, numGenDecorations); return secretlevelexit; @@ -4047,6 +4452,111 @@ bool allowedGenerateMimicOnChest(int x, int y, map_t& map) return true; } +void debugMap(map_t* map) +{ + return; + if ( !map ) + { + return; + } + + std::set takenSlots; + for ( auto node = map->entities->first; node != nullptr; ) + { + Entity* postProcessEntity = (Entity*)node->element; + node = node->next; + if ( postProcessEntity ) + { + if ( postProcessEntity->behavior == &actItem && postProcessEntity->z > 4 ) + { + int x = (int)postProcessEntity->x >> 4; + int y = (int)postProcessEntity->y >> 4; + takenSlots.insert(x + y * 10000); + } + } + } + + for ( int x = 0; x < map->width; ++x ) + { + for ( int y = 0; y < map->height; ++y ) + { + if ( takenSlots.find(x + y * 10000) != takenSlots.end() ) + { + int numWalls = 0; + std::vector> coords = { + {x + 1, y}, + {x - 1, y}, + {x, y + 1}, + {x, y - 1} + }; + for ( auto& pair : coords ) + { + if ( pair.first >= 0 && pair.first < map->width ) + { + if ( pair.second >= 0 && pair.second < map->height ) + { + if ( map->tiles[pair.second * MAPLAYERS + pair.first * MAPLAYERS * map->height] ) // floor + { + numWalls += map->tiles[OBSTACLELAYER + pair.second * MAPLAYERS + pair.first * MAPLAYERS * map->height] != 0 ? 1 : 0; + } + } + } + } + if ( numWalls > 0 ) + { + //Entity* ent = newEntity(245, 0, map->entities, nullptr); + ////ent->behavior = &actBoulder; + //ent->x = x * 16.0 + 8; + //ent->y = y * 16.0 + 8; + //ent->z = 24.0; + } + //int numObstacles = checkObstacle((checkx) * 16, (checky) * 16, NULL, NULL, true); + } + } + } + mapLevel2(0); + + /*int num5x5s = 0; + // open area debugging tool + for ( int x = 0; x < map->width; ++x ) + { + for ( int y = 0; y < map->height; ++y ) + { + if ( takenSlots.find(x + y * 10000) == takenSlots.end() ) + { + if ( !map->tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map->height] + && !map->tiles[2 + y * MAPLAYERS + x * MAPLAYERS * map->height] ) + { + int numTiles = 0; + for ( int x1 = x; x1 < map->width && x1 < x + 5; ++x1 ) + { + for ( int y1 = y; y1 < map->height && y1 < y + 5; ++y1 ) + { + if ( takenSlots.find(x1 + y1 * 10000) == takenSlots.end() ) + { + if ( !map->tiles[OBSTACLELAYER + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] + && !map->tiles[2 + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] ) + { + ++numTiles; + } + } + } + } + if ( numTiles == 25 ) + { + ++num5x5s; + Entity* ent = newEntity(245, 0, map->entities, nullptr); + ent->behavior == &actBoulder; + ent->x = x * 16.0 + 8; + ent->y = y * 16.0 + 8; + } + } + } + } + } + messagePlayer(0, MESSAGE_DEBUG, "%d 5x5s", num5x5s);*/ +} + /*------------------------------------------------------------------------------- assignActions @@ -4442,7 +4952,10 @@ void assignActions(map_t* map) entity->x += 8; entity->y += 8; entity->roll = PI / 2.0; - entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + if ( entity->itemContainer == 0 ) + { + entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + } entity->flags[PASSABLE] = true; entity->behavior = &actItem; if ( entity->sprite == 68 ) // magic_bow.png @@ -4753,12 +5266,27 @@ void assignActions(map_t* map) entity->sizey = 4; entity->x += 8; entity->y += 8; - entity->z = 6.5; - entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + if ( entity->goldInContainer == 0 ) + { + entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + } entity->flags[PASSABLE] = true; entity->behavior = &actGoldBag; - entity->skill[0] = 10 + map_rng.rand() % 100 + (currentlevel); // amount - entity->sprite = 130; // gold bag model + entity->goldBouncing = 1; + if ( entity->goldAmount == 0 ) + { + entity->goldAmount = 10 + map_rng.rand() % 100 + (currentlevel); // amount + } + if ( entity->goldAmount < 5 ) + { + entity->sprite = 1379; + entity->z = 7.75; + } + else + { + entity->sprite = 130; // gold bag model + entity->z = 6.25; + } if ( !strcmp(map->name, "Sokoban") ) { entity->flags[INVISIBLE] = true; @@ -6858,7 +7386,10 @@ void assignActions(map_t* map) entity->x += 8; entity->y += 8; entity->roll = PI / 2.0; - entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + if ( entity->itemContainer == 0 ) + { + entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + } entity->flags[PASSABLE] = true; entity->behavior = &actItem; entity->skill[10] = READABLE_BOOK; @@ -6991,23 +7522,108 @@ void assignActions(map_t* map) entity->yaw = entity->shrineDir * PI / 2; break; case 179: + { // collider decoration entity->x += 8; entity->y += 8; + int dir = entity->colliderDecorationRotation; + if ( dir == -1 ) + { + dir = map_rng.rand() % 8; + entity->colliderDecorationRotation = dir; + } + entity->yaw = dir * (PI / 4); + /*static ConsoleVariable debugColliderType("/collider_type", 14); + entity->colliderDamageTypes = *debugColliderType;*/ + auto find = EditorEntityData_t::colliderData.find(entity->colliderDamageTypes); + if ( find != EditorEntityData_t::colliderData.end() ) + { + auto& data = find->second; + if ( data.hasOverride("dir_offset") ) + { + entity->yaw = ((dir + data.getOverride("dir_offset")) * (PI / 4)); + } + if ( data.hasOverride("model") ) + { + entity->colliderDecorationModel = data.getOverride("model"); + } + if ( data.hasOverride("height") ) + { + entity->colliderDecorationHeightOffset = data.getOverride("height"); + } + if ( dir == 0 ) + { + if ( data.hasOverride("east_x") ) + { + entity->colliderDecorationXOffset = data.getOverride("east_x"); + } + if ( data.hasOverride("east_y") ) + { + entity->colliderDecorationYOffset = data.getOverride("east_y"); + } + } + else if ( dir == 2 ) + { + if ( data.hasOverride("south_x") ) + { + entity->colliderDecorationXOffset = data.getOverride("south_x"); + } + if ( data.hasOverride("south_y") ) + { + entity->colliderDecorationYOffset = data.getOverride("south_y"); + } + } + else if ( dir == 4 ) + { + if ( data.hasOverride("west_x") ) + { + entity->colliderDecorationXOffset = data.getOverride("west_x"); + } + if ( data.hasOverride("west_y") ) + { + entity->colliderDecorationYOffset = data.getOverride("west_y"); + } + } + else if ( dir == 6 ) + { + if ( data.hasOverride("north_x") ) + { + entity->colliderDecorationXOffset = data.getOverride("north_x"); + } + if ( data.hasOverride("north_y") ) + { + entity->colliderDecorationYOffset = data.getOverride("north_y"); + } + } + if ( data.hasOverride("collision") ) + { + entity->colliderHasCollision = data.getOverride("collision"); + } + if ( data.hasOverride("collision_x") ) + { + entity->colliderSizeX = data.getOverride("collision_x"); + } + if ( data.hasOverride("collision_y") ) + { + entity->colliderSizeY = data.getOverride("collision_y"); + } + if ( data.hasOverride("hp") ) + { + entity->colliderMaxHP = data.getOverride("hp"); + } + if ( data.hasOverride("diggable") ) + { + entity->colliderDiggable = data.getOverride("diggable"); + } + } + entity->sprite = entity->colliderDecorationModel; entity->sizex = entity->colliderSizeX; entity->sizey = entity->colliderSizeY; - entity->z = 7.5 - entity->colliderDecorationHeightOffset * 0.25; entity->x += entity->colliderDecorationXOffset * 0.25; entity->y += entity->colliderDecorationYOffset * 0.25; - if ( entity->colliderDecorationRotation == -1 ) - { - entity->yaw = (map_rng.rand() % 8) * (PI / 4); - } - else - { - entity->yaw = entity->colliderDecorationRotation * (PI / 4); - } + entity->z = 7.5 - entity->colliderDecorationHeightOffset * 0.25; + entity->flags[PASSABLE] = entity->colliderHasCollision == 0; entity->flags[BLOCKSIGHT] = false; entity->behavior = &actColliderDecoration; @@ -7027,6 +7643,7 @@ void assignActions(map_t* map) } entity->setUID(-3);*/ break; + } default: break; } @@ -7043,8 +7660,28 @@ void assignActions(map_t* map) node = node->next; if ( postProcessEntity ) { + if ( postProcessEntity->behavior == &actGoldBag ) + { + if ( postProcessEntity->goldInContainer != 0 && postProcessEntity->flags[INVISIBLE] == true ) + { + if ( auto parent = uidToEntity(postProcessEntity->itemContainer) ) + { + postProcessEntity->x = parent->x; + postProcessEntity->y = parent->y; + } + } + } if ( postProcessEntity->behavior == &actItem ) { + if ( postProcessEntity->itemContainer != 0 && postProcessEntity->flags[INVISIBLE] == true ) + { + if ( auto parent = uidToEntity(postProcessEntity->itemContainer) ) + { + postProcessEntity->x = parent->x; + postProcessEntity->y = parent->y; + } + } + // see if there's any platforms to set items upon. for ( node_t* tmpnode = map->entities->first; tmpnode != nullptr; tmpnode = tmpnode->next ) { @@ -7121,7 +7758,6 @@ void assignActions(map_t* map) } std::vector chests; - //std::set takenSlots; for ( auto node = map->entities->first; node != nullptr; ) { Entity* postProcessEntity = (Entity*)node->element; @@ -7136,52 +7772,10 @@ void assignActions(map_t* map) { chests.push_back(postProcessEntity); } - - //int x = (int)postProcessEntity->x >> 4; - //int y = (int)postProcessEntity->y >> 4; - //takenSlots.insert(x + y * 10000); } } - /*int num5x5s = 0; - // open area debugging tool - for ( int x = 0; x < map->width; ++x ) - { - for ( int y = 0; y < map->height; ++y ) - { - if ( takenSlots.find(x + y * 10000) == takenSlots.end() ) - { - if ( !map->tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map->height] - && !map->tiles[2 + y * MAPLAYERS + x * MAPLAYERS * map->height] ) - { - int numTiles = 0; - for ( int x1 = x; x1 < map->width && x1 < x + 5; ++x1 ) - { - for ( int y1 = y; y1 < map->height && y1 < y + 5; ++y1 ) - { - if ( takenSlots.find(x1 + y1 * 10000) == takenSlots.end() ) - { - if ( !map->tiles[OBSTACLELAYER + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] - && !map->tiles[2 + y1 * MAPLAYERS + x1 * MAPLAYERS * map->height] ) - { - ++numTiles; - } - } - } - } - if ( numTiles == 25 ) - { - ++num5x5s; - Entity* ent = newEntity(245, 0, map->entities, nullptr); - ent->behavior == &actBoulder; - ent->x = x * 16.0 + 8; - ent->y = y * 16.0 + 8; - } - } - } - } - } - messagePlayer(0, MESSAGE_DEBUG, "%d 5x5s", num5x5s);*/ + debugMap(map); if ( true /*currentlevel == 0*/ ) { @@ -7240,8 +7834,9 @@ void assignActions(map_t* map) for ( auto chest : mimics ) { - if ( chest == vampireQuestChest ) + if ( vampireQuestChest && chest == vampireQuestChest ) { + createChestInventory(chest, chest->chestType); continue; } From 792df7a4dd9fb1b1cad25098c47f82fe5fc59306 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 22 Aug 2024 02:59:54 +1000 Subject: [PATCH 074/244] * tooltips for editor - collider/floor shows model mesh name --- src/draw.cpp | 72 ++++++++++++++++++++++++++++++++++++++++--- src/editor.cpp | 49 +++++++++++++++++++++++++++++ src/editor.hpp | 1 + src/entity.hpp | 9 ++++++ src/entity_editor.cpp | 6 ++++ src/init.cpp | 13 ++++++++ 6 files changed, 146 insertions(+), 4 deletions(-) diff --git a/src/draw.cpp b/src/draw.cpp index bcc3e6891..2e03e885e 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -18,6 +18,7 @@ #include "ui/Frame.hpp" #ifdef EDITOR #include "editor.hpp" +#include "mod_tools.hpp" #endif #include "items.hpp" #include "ui/Image.hpp" @@ -2324,6 +2325,9 @@ void drawEntities3D(view_t* camera, int mode) void drawEntities2D(long camx, long camy) { + // editor only +#ifndef EDITOR +#else node_t* node; Entity* entity; SDL_Rect pos, box; @@ -2448,10 +2452,8 @@ void drawEntities2D(long camx, long camy) // draw hover text for entities over the top of sprites. for ( node = map.entities->first; node != nullptr -#ifdef EDITOR && (openwindow == 0 && savewindow == 0) -#endif ; node = node->next ) @@ -2557,7 +2559,37 @@ void drawEntities2D(long camx, long camy) } ttfPrintText(ttf8, padx, pady + 20, tmpStr); break; + case 27: // collider + { + pady += 5; + strcpy(tmpStr, spriteEditorNameStrings[selectedEntity[0]->sprite]); + ttfPrintText(ttf8, padx, pady - 10, tmpStr); + int model = selectedEntity[0]->colliderDecorationModel; + if ( EditorEntityData_t::colliderData.find(selectedEntity[0]->colliderDamageTypes) + != EditorEntityData_t::colliderData.end() ) + { + if ( EditorEntityData_t::colliderData[selectedEntity[0]->colliderDamageTypes].hasOverride("model") ) + { + model = EditorEntityData_t::colliderData[selectedEntity[0]->colliderDamageTypes].getOverride("model"); + } + } + snprintf(tmpStr, sizeof(tmpStr), "Model: %s", modelFileNames[model].c_str()); + ttfPrintTextColor(ttf8, padx, pady, makeColorRGB(0, 255, 0), true, tmpStr); + if ( EditorEntityData_t::colliderData.find(selectedEntity[0]->colliderDamageTypes) + != EditorEntityData_t::colliderData.end() ) + { + + auto& colliderData = EditorEntityData_t::colliderData[selectedEntity[0]->colliderDamageTypes]; + snprintf(tmpStr, sizeof(tmpStr), "Collider Type: %s", colliderData.name.c_str()); + } + else + { + snprintf(tmpStr, sizeof(tmpStr), "Collider Type: ???"); + } + ttfPrintTextColor(ttf8, padx, pady + 10, makeColorRGB(255, 255, 0), true, tmpStr); + break; + } case 3: //Items pady += 5; strcpy(tmpStr, itemNameStrings[selectedEntity[0]->skill[10]]); @@ -2848,6 +2880,39 @@ void drawEntities2D(long camx, long camy) tooltip.w = TTF8_WIDTH * (int)longestLine + 8; tooltip.y = pady + offsety - 4; tooltip.h = (int)lines.size() * TTF8_HEIGHT + 8; + + if ( lines.size() <= 1 ) + { + offsety += 20; + } + + if ( spriteType == 13 ) + { + if ( modelFileNames.find(selectedEntity[0]->floorDecorationModel) != modelFileNames.end() ) + { + snprintf(tmpStr, sizeof(tmpStr), "Model: %s", modelFileNames[selectedEntity[0]->floorDecorationModel].c_str()); + if ( lines.size() > 1 ) + { + ttfPrintTextColor(ttf8, padx + offsetx, tooltip.y - 16, makeColorRGB(0, 255, 0), true, tmpStr); + } + else + { + ttfPrintTextColor(ttf8, padx + offsetx, pady + offsety - 10, makeColorRGB(0, 255, 0), true, tmpStr); + } + } + else + { + if ( lines.size() > 1 ) + { + ttfPrintText(ttf8, padx + offsetx, tooltip.y - 16, "Model: Invalid Index"); + } + else + { + ttfPrintText(ttf8, padx + offsetx, pady + offsety - 10, "Model: Invalid Index"); + } + } + } + if ( lines.size() > 1 ) { drawTooltip(&tooltip); @@ -2869,9 +2934,7 @@ void drawEntities2D(long camx, long camy) else if ( (omousex / TEXTURESIZE) * 32 == pos.x && (omousey / TEXTURESIZE) * 32 == pos.y && selectedEntity[0] == NULL -#ifdef EDITOR && hovertext -#endif ) { // handle mouseover sprite name tooltip in main editor screen @@ -2908,6 +2971,7 @@ void drawEntities2D(long camx, long camy) } } } +#endif } /*------------------------------------------------------------------------------- diff --git a/src/editor.cpp b/src/editor.cpp index 4fba79b44..54d316575 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -32,6 +32,7 @@ //#include "player.hpp" +std::map modelFileNames; Entity* selectedEntity[MAXPLAYERS] = { nullptr }; Entity* lastSelectedEntity[MAXPLAYERS] = { nullptr }; Sint32 mousex = 0, mousey = 0; @@ -5544,6 +5545,18 @@ int main(int argc, char** argv) { propertyPageError(i, 0); // reset to default 0. } + else + { + if ( modelFileNames.find(propertyInt) != modelFileNames.end() ) + { + std::string tmpStr = modelFileNames[propertyInt]; + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr.c_str()); + } + else + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, makeColorRGB(255, 0, 0), "Unknown Model!"); + } + } } else if ( i == 1 ) { @@ -5973,6 +5986,18 @@ int main(int argc, char** argv) { propertyPageError(i, 0); // reset to default 0. } + else + { + if ( modelFileNames.find(propertyInt) != modelFileNames.end() ) + { + std::string tmpStr = modelFileNames[propertyInt]; + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr.c_str()); + } + else + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, makeColorRGB(255, 0, 0), "Unknown Model!"); + } + } } else if ( i == 1 ) { @@ -6937,6 +6962,18 @@ int main(int argc, char** argv) { propertyPageError(i, 0); // reset to default 0. } + else + { + if ( modelFileNames.find(propertyInt) != modelFileNames.end() ) + { + std::string tmpStr = modelFileNames[propertyInt]; + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr.c_str()); + } + else + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, makeColorRGB(255, 0, 0), "Unknown Model!"); + } + } } else if ( i == 1 ) { @@ -8009,6 +8046,18 @@ int main(int argc, char** argv) { propertyPageError(i, 0); // reset to default 0. } + else if ( i == 0 ) + { + if ( modelFileNames.find(propertyInt) != modelFileNames.end() ) + { + std::string tmpStr = modelFileNames[propertyInt]; + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr.c_str()); + } + else + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, makeColorRGB(255, 0, 0), "Unknown Model!"); + } + } } else if ( i == 1 ) { diff --git a/src/editor.hpp b/src/editor.hpp index 616e0ad3a..0afdced1e 100644 --- a/src/editor.hpp +++ b/src/editor.hpp @@ -21,6 +21,7 @@ static const unsigned int MAXHEIGHT = 2000; static const unsigned int MINWIDTH = 1; static const unsigned int MINHEIGHT = 1; +extern std::map modelFileNames; extern int drawlayer, drawx, drawy, odrawx, odrawy; extern int alllayers; extern int scroll; diff --git a/src/entity.hpp b/src/entity.hpp index 729a80855..7df7b35ba 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -416,6 +416,9 @@ class Entity Sint32& colliderCurrentHP; //skill[12] Sint32& colliderOldHP; //skill[13] Sint32& colliderInit; //skill[14] + Sint32& colliderContainedEntity; //skill[15] + Sint32& colliderHideMonster; //skill[16] + Sint32& colliderKillerUid; //skill[17] //--PUBLIC SPELL TRAP SKILLS-- Sint32& spellTrapType; //skill[0] @@ -484,6 +487,7 @@ class Entity Sint32& itemReceivedDetailsFromServer; //skill[25] Sint32& itemAutoSalvageByPlayer; //skill[26] Sint32& itemSplooshed; //skill[27] + Sint32& itemContainer; //skill[29] real_t& itemWaterBob; //fskill[2] //--PUBLIC ACTMAGIC SKILLS (Standard projectiles)-- @@ -517,6 +521,8 @@ class Entity Sint32& goldAmount; //skill[0] Sint32& goldAmbience; //skill[1] Sint32& goldSokoban; //skill[2] + Sint32& goldBouncing; //skill[3] + Sint32& goldInContainer; //skill[4] //--PUBLIC SOUND SOURCE SKILLS-- Sint32& soundSourceFired; //skill[0] @@ -1018,10 +1024,13 @@ class Entity bool isDamageableCollider() const; bool isColliderDamageableByMelee() const; bool isColliderWeakToSkill(const int proficiency) const; + bool isColliderResistToSkill(const int proficiency) const; bool isColliderWeakToBoulders() const; bool isColliderShownAsWallOnMinimap() const; bool isColliderDamageableByMagic() const; bool isColliderAttachableToBombs() const; + bool isColliderWall() const; + void colliderOnDestroy(); int getColliderOnHitLangEntry() const; int getColliderOnBreakLangEntry() const; int getColliderSfxOnHit() const; diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index ae2f45c23..2d7c3d680 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -130,6 +130,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli itemReceivedDetailsFromServer(skill[25]), itemAutoSalvageByPlayer(skill[26]), itemSplooshed(skill[27]), + itemContainer(skill[29]), itemWaterBob(fskill[2]), gateInit(skill[1]), gateStatus(skill[3]), @@ -259,6 +260,9 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli colliderCurrentHP(skill[12]), colliderOldHP(skill[13]), colliderInit(skill[14]), + colliderContainedEntity(skill[15]), + colliderHideMonster(skill[16]), + colliderKillerUid(skill[17]), furnitureType(skill[0]), furnitureInit(skill[1]), furnitureDir(skill[3]), @@ -308,6 +312,8 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), + goldBouncing(skill[3]), + goldInContainer(skill[4]), interactedByMonster(skill[47]), highlightForUI(fskill[29]), highlightForUIGlow(fskill[28]), diff --git a/src/init.cpp b/src/init.cpp index 601aa50d7..cc8e6de5b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -700,6 +700,11 @@ int initApp(char const * const title, int fullscreen) loading_done = true; return 11; } + +#ifdef EDITOR + modelFileNames.clear(); +#endif + models = (voxel_t**) malloc(sizeof(voxel_t*)*nummodels); fp = openDataFile(modelsDirectory.c_str(), "rb"); for ( int c = 0; !fp->eof(); c++ ) @@ -731,6 +736,14 @@ int initApp(char const * const title, int fullscreen) models[c] = model; } } +#ifdef EDITOR + std::string filename = name; + if ( filename.find("models/") != std::string::npos ) + { + filename = filename.substr(strlen("models/")); + } + modelFileNames[c] = filename; +#endif } updateLoadingScreen(30); generatePolyModels(0, nummodels, false); From 25d5fa95081c66ed627889c299723533c6574822 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 22 Aug 2024 02:59:59 +1000 Subject: [PATCH 075/244] * lang update --- lang/en.txt | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lang/en.txt b/lang/en.txt index 4904f07ab..90a98882a 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6391,8 +6391,7 @@ your One-Shot score attempt.# 6168 Quitting now will end this run and submit it as your One-Shot score.# 6169 New Seed Events Available!# -6170 -Visit the Seed Events menu +6170 Visit the Seed Events menu to participate.# 6171 Unlock the Achievement: "%s" @@ -6438,5 +6437,29 @@ Magic Required: %d (%s)# 6209 PTS# 6210 TOTAL COMPLETION# 6211 RESEARCH# +6212 SHOP SIGNS# +# breakables +6213 jar# +6214 basket# +6215 casket# +6216 cage# +6217 urn# +6218 sack# +6219 sarcophagus# +6220 pot# +6221 cart# +6222 pallet# +6223 crate# +6224 tent# +6225 reliquary# +6226 bulb# +6227 iron maiden# +6228 wheelbarrow# +6229 crystal# +6230 cabinet# +6231 capsule# +6232 barrel# +6233 stump# +6234 A %s jumps out from the %s!# 6250 END# From 05b34ed4a9f051f16b4049e583a102eabf8f42b2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 23 Aug 2024 22:20:17 +1000 Subject: [PATCH 076/244] * fix local highscore not being create when host kicks everyone after deaths? --- src/menu.cpp | 229 ++++++++++++++++++++------------------------ src/menu.hpp | 3 +- src/ui/MainMenu.cpp | 77 +++++++++++---- src/ui/MainMenu.hpp | 1 + 4 files changed, 167 insertions(+), 143 deletions(-) diff --git a/src/menu.cpp b/src/menu.cpp index 73c963072..6a5fdb8fc 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -275,8 +275,6 @@ int fourthendmoviestage = 0; int fourthendmovietime = 0; int fourthEndNumLines = 13; bool losingConnection[MAXPLAYERS] = { false }; -bool subtitleVisible = false; -int subtitleCurrent = 0; // new text crawls... int DLCendmoviealpha[8][30] = { 0 }; @@ -293,9 +291,6 @@ int DLCendmovieNumLines[8] = 13, // MOVIE_WIN_DEMONS_UNDEAD, 13 // MOVIE_WIN_BEASTS }; -char epilogueHostName[128] = ""; -int epilogueHostRace = 0; -int epilogueMultiplayerType = SINGLE; //Confirm resolution window stuff. bool resolutionChanged = false; @@ -9538,7 +9533,50 @@ void doCredits() { } } -void doEndgame(bool saveHighscore) { + +void doEndgameOnDisconnect() +{ + client_disconnected[0] = false; + + introstage = 1; + intro = true; + + // load menu level + int menuMapType = 0; + if ( victory == 3 || victory == 4 || victory == 5 ) + { + menuMapType = loadMainMenuMap(true, true); + } + else + { + switch ( local_rng.rand() % 2 ) + { + case 0: + menuMapType = loadMainMenuMap(true, false); + break; + case 1: + menuMapType = loadMainMenuMap(false, false); + break; + default: + break; + } + } + for ( int c = 0; c < MAXPLAYERS; ++c ) { + cameras[c].vang = 0; + } + numplayers = 0; + assignActions(&map); + generatePathMaps(); + + gamePaused = false; + if ( !victory ) + { + fadefinished = false; + fadeout = false; + } +} + +void doEndgame(bool saveHighscore, bool onServerDisconnect) { int c, x; bool endTutorial = false; bool localScores = gameModeManager.allowsHiscores(); @@ -9598,56 +9636,6 @@ void doEndgame(bool saveHighscore) { bool died = stats[clientnum] && stats[clientnum]->HP <= 0; Compendium_t::Events_t::onEndgameEvent(clientnum, endTutorial, saveHighscore, died); - // figure out the victory crawl texts... - int movieCrawlType = -1; - if ( victory ) - { - if ( stats[0] ) - { - strcpy(epilogueHostName, stats[0]->name); - epilogueHostRace = RACE_HUMAN; - if ( stats[0]->playerRace > 0 && stats[0]->appearance == 0 ) - { - epilogueHostRace = stats[0]->playerRace; - } - epilogueMultiplayerType = multiplayer; - if ( victory == 1 && epilogueHostRace > 0 && epilogueHostRace != RACE_AUTOMATON ) - { - // herx defeat by monsters. - movieCrawlType = MOVIE_CLASSIC_WIN_MONSTERS; - } - else if ( victory == 2 && epilogueHostRace > 0 && epilogueHostRace != RACE_AUTOMATON ) - { - // baphomet defeat by monsters. - movieCrawlType = MOVIE_CLASSIC_WIN_BAPHOMET_MONSTERS; - } - else if ( victory == 3 || victory == 4 || victory == 5 ) - { - switch ( epilogueHostRace ) - { - case RACE_AUTOMATON: - movieCrawlType = MOVIE_WIN_AUTOMATON; - break; - case RACE_SKELETON: - case RACE_VAMPIRE: - case RACE_SUCCUBUS: - case RACE_INCUBUS: - movieCrawlType = MOVIE_WIN_DEMONS_UNDEAD; - break; - case RACE_GOATMAN: - case RACE_GOBLIN: - case RACE_INSECTOID: - movieCrawlType = MOVIE_WIN_BEASTS; - break; - case RACE_HUMAN: - break; - default: - break; - } - } - } - } - // make a highscore! if ( !endTutorial && saveHighscore ) { @@ -9685,10 +9673,6 @@ void doEndgame(bool saveHighscore) { saveAllScores(SCORESFILE_MULTIPLAYER); } - // pick a new subtitle :) - subtitleCurrent = local_rng.rand() % NUMSUBTITLES; - subtitleVisible = true; - for ( c = 0; c < NUMMONSTERS; c++ ) { kills[c] = 0; @@ -9727,32 +9711,35 @@ void doEndgame(bool saveHighscore) { } #endif - // send disconnect messages - if (multiplayer == CLIENT) - { - strcpy((char*)net_packet->data, "DISC"); - net_packet->data[4] = clientnum; - net_packet->address.host = net_server.host; - net_packet->address.port = net_server.port; - net_packet->len = 5; - sendPacketSafe(net_sock, -1, net_packet, 0); - printlog("disconnected from server.\n"); - } - else if (multiplayer == SERVER) + if ( net_packet ) { - for (x = 1; x < MAXPLAYERS; x++) + // send disconnect messages + if (multiplayer == CLIENT) { - if ( client_disconnected[x] == true ) - { - continue; - } strcpy((char*)net_packet->data, "DISC"); net_packet->data[4] = clientnum; - net_packet->address.host = net_clients[x - 1].host; - net_packet->address.port = net_clients[x - 1].port; + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; net_packet->len = 5; - sendPacketSafe(net_sock, -1, net_packet, x - 1); - client_disconnected[x] = true; + sendPacketSafe(net_sock, -1, net_packet, 0); + printlog("disconnected from server.\n"); + } + else if (multiplayer == SERVER) + { + for (x = 1; x < MAXPLAYERS; x++) + { + if ( client_disconnected[x] == true ) + { + continue; + } + strcpy((char*)net_packet->data, "DISC"); + net_packet->data[4] = clientnum; + net_packet->address.host = net_clients[x - 1].host; + net_packet->address.port = net_clients[x - 1].port; + net_packet->len = 5; + sendPacketSafe(net_sock, -1, net_packet, x - 1); + client_disconnected[x] = true; + } } } @@ -9954,8 +9941,11 @@ void doEndgame(bool saveHighscore) { currentlevel = 0; secretlevel = false; clientnum = 0; - introstage = 1; - intro = true; + if ( !onServerDisconnect ) + { + introstage = 1; + intro = true; + } splitscreen = false; #ifdef NINTENDO @@ -10032,7 +10022,10 @@ void doEndgame(bool saveHighscore) { } else { - client_disconnected[c] = false; + if ( !onServerDisconnect ) + { + client_disconnected[c] = false; + } } players[c]->entity = nullptr; //TODO: PLAYERSWAP VERIFY. Need to do anything else? players[c]->cleanUpOnEntityRemoval(); @@ -10065,56 +10058,40 @@ void doEndgame(bool saveHighscore) { CalloutMenu[i].callouts.clear(); } - // load menu level - int menuMapType = 0; - if ( victory == 3 || victory == 4 || victory == 5 ) - { - menuMapType = loadMainMenuMap(true, true); - } - else - { - switch ( local_rng.rand() % 2 ) - { - case 0: - menuMapType = loadMainMenuMap(true, false); - break; - case 1: - menuMapType = loadMainMenuMap(false, false); - break; - default: - break; - } - } - for (int c = 0; c < MAXPLAYERS; ++c) { - cameras[c].vang = 0; - } - numplayers = 0; - assignActions(&map); - generatePathMaps(); - gamePaused = false; - if ( !victory ) - { - fadefinished = false; - fadeout = false; - } - else + if ( !onServerDisconnect ) { - if ( victory == 1 ) + // load menu level + int menuMapType = 0; + if ( victory == 3 || victory == 4 || victory == 5 ) { - introstage = 7; + menuMapType = loadMainMenuMap(true, true); } - else if ( victory == 2 ) + else { - introstage = 8; + switch ( local_rng.rand() % 2 ) + { + case 0: + menuMapType = loadMainMenuMap(true, false); + break; + case 1: + menuMapType = loadMainMenuMap(false, false); + break; + default: + break; + } } - else if ( victory == 3 || victory == 4 || victory == 5 ) - { - introstage = 10; + for (int c = 0; c < MAXPLAYERS; ++c) { + cameras[c].vang = 0; } + numplayers = 0; + assignActions(&map); + generatePathMaps(); - if ( movieCrawlType >= 0 ) // overrides the introstage 7,8,10 sequences for DLC monsters. + gamePaused = false; + if ( !victory ) { - introstage = 11 + movieCrawlType; + fadefinished = false; + fadeout = false; } } diff --git a/src/menu.hpp b/src/menu.hpp index b4c8a168b..88b0699bc 100644 --- a/src/menu.hpp +++ b/src/menu.hpp @@ -297,7 +297,8 @@ int isCharacterValidFromDLC(Stat& myStats, int characterClass); void doQuitGame(); void doNewGame(bool makeHighscore); void doCredits(); -void doEndgame(bool saveHighscore); +void doEndgame(bool saveHighscore, bool onServerDisconnect); +void doEndgameOnDisconnect(); void doIntro(); void doEndgameHerx(); void doEndgameDevil(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 2abcb2ef3..ce9020a68 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -1905,16 +1905,16 @@ namespace MainMenu { #endif } - static void disconnectPrompt(const char* text) { - errorPrompt( - text, - "Okay", - [](Button& button){ - soundCancel(); - beginFade(FadeDestination::RootMainMenu); - closeMono(); - } - ); + static void disconnectPromptFromServer(const char* text, FadeDestination dest) { + errorPrompt( + text, + "Okay", + [](Button& button){ + soundCancel(); + beginFade(MainMenu::FadeDestination::RootMainMenuNoEndGame); + closeMono(); + } + ); } static void openDLCPrompt(int which) { @@ -24867,7 +24867,7 @@ namespace MainMenu { // this is because we are coming here from some unknown place. // presumably the current game has been saved, so we're not ending it. // just return to the main menu but don't save a score - doEndgame(false); + doEndgame(false, false); } // return to title screen @@ -24888,7 +24888,44 @@ namespace MainMenu { } #endif } - else if (main_menu_fade_destination == FadeDestination::RootMainMenu) { + else if ( main_menu_fade_destination == FadeDestination::RootMainMenuNoEndGame ) + { + if ( ingame ) { + // end current game +#ifdef NINTENDO + if ( !nxIsHandheldMode() ) { + nxAssignControllers(1, 1, true, false, true, false, nullptr); + } +#endif + // skip doEndGame, do some stuff we skipped + doEndgameOnDisconnect(); + } + + // return to menu + destroyMainMenu(); +#ifdef SOUND + const int music = RNG.uniform(0, NUMINTROMUSIC - 2); + playMusic(intromusic[music], true, false, false); +#endif + createMainMenu(false); + + // join lobby we've been invited to + if ( saved_invite_lobby ) { + connectToServer(nullptr, saved_invite_lobby, LobbyType::LobbyOnline); + saved_invite_lobby = nullptr; + } + +#ifndef NINTENDO + // unbind controllers + for ( int c = 1; c < MAX_SPLITSCREEN; ++c ) { + if ( inputs.hasController(c) ) { + inputs.removeControllerWithDeviceID(inputs.getControllerID(c)); + Input::inputs[c].refresh(); + } + } +#endif + } + else if (main_menu_fade_destination == FadeDestination::RootMainMenu ) { if (ingame) { // end current game #ifdef NINTENDO @@ -24900,7 +24937,7 @@ namespace MainMenu { // this is because we are coming here from some unknown place. // presumably the current game has been saved, so we're not ending it. // just return to the main menu but don't save a score - doEndgame(false); + doEndgame(false, false); } // return to menu @@ -24938,7 +24975,7 @@ namespace MainMenu { // end the game AND create a highscore! // this is because the game is well and truly done. There is no save file. // create a highscore as token of remembrance. - doEndgame(true); + doEndgame(true, false); } #ifdef SOUND const int music = RNG.uniform(0, NUMINTROMUSIC - 2); @@ -24968,7 +25005,7 @@ namespace MainMenu { // end the game AND create a highscore! // this is because the game is well and truly done. There is no save file. // create a highscore as token of remembrance. - doEndgame(true); + doEndgame(true, false); destroyMainMenu(); createDummyMainMenu(); createCreditsScreen(true); @@ -26258,6 +26295,14 @@ namespace MainMenu { void disconnectedFromServer(const char* text) { // when a player is disconnected from the server if (multiplayer != SINGLE) { + if ( saveGameExists(multiplayer == SINGLE) ) { + doEndgame(false, true); + } + else + { + doEndgame(true, true); + } + multiplayer = SINGLE; disconnectFromLobby(); destroyMainMenu(); @@ -26265,7 +26310,7 @@ namespace MainMenu { #ifndef NINTENDO inputs.setPlayerIDAllowedKeyboard(clientnum); #endif - disconnectPrompt(text); + disconnectPromptFromServer(text, FadeDestination::RootMainMenuNoEndGame); if (!intro) { pauseGame(2, 0); } diff --git a/src/ui/MainMenu.hpp b/src/ui/MainMenu.hpp index c5a4de71e..c766257d6 100644 --- a/src/ui/MainMenu.hpp +++ b/src/ui/MainMenu.hpp @@ -38,6 +38,7 @@ namespace MainMenu { None, // don't fade anywhere (???) TitleScreen, // fade to the title screen RootMainMenu, // return to main menu, save no score if ingame + RootMainMenuNoEndGame, // return to main menu, doEndGame already done Endgame, // save a highscore and return to main menu Victory, // save a highscore and roll credits From 035ca3a11aa4f2a89d67d79a33b8363d98248672 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 03:58:52 +1000 Subject: [PATCH 077/244] * arrows/thrown hurt breakables * breakable new sfx * monsters path through breakables (gyro/ghost fly over) * adjust breakable gold spawn amounts * breakable dont block tooltip --- src/actarrow.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++ src/actgeneral.cpp | 22 ++++++++------- src/actmonster.cpp | 14 +++++++--- src/actthrown.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++++ src/collision.cpp | 18 ++++++++++--- src/maps.cpp | 36 +++++++------------------ src/mod_tools.cpp | 20 +++++++++++++- src/mod_tools.hpp | 5 +++- src/paths.cpp | 35 +++++++++++++++++++++--- src/player.cpp | 5 ++++ 10 files changed, 237 insertions(+), 48 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index eb80c3c9f..c0e6b12de 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -428,6 +428,69 @@ void actArrow(Entity* my) } } } + else if ( hit.entity->isDamageableCollider() ) + { + int damage = 1; + int axe = 0; + if ( hit.entity->isColliderResistToSkill(PRO_RANGED) || hit.entity->isColliderWall() ) + { + damage = 1; + } + else + { + damage = 2 + local_rng.rand() % 3; + } + if ( hit.entity->isColliderWeakToSkill(PRO_RANGED) ) + { + if ( parent && parent->getStats() ) + { + axe = 2 * (parent->getStats()->getModifiedProficiency(PRO_RANGED) / 20); + } + axe = std::min(axe, 9); + } + damage += axe; + + int& entityHP = hit.entity->colliderCurrentHP; + int oldHP = entityHP; + entityHP -= damage; + + int sound = 28; //damage.ogg + if ( hit.entity->getColliderSfxOnHit() > 0 ) + { + sound = hit.entity->getColliderSfxOnHit(); + } + playSoundEntity(hit.entity, sound, 64); + + if ( entityHP > 0 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT_BASIC, Language::get(hit.entity->getColliderOnHitLangEntry()), + Language::get(hit.entity->getColliderLangName())); + } + } + else + { + entityHP = 0; + + hit.entity->colliderKillerUid = parent ? parent->getUID() : 0; + if ( parent && parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()), + Language::get(hit.entity->getColliderLangName())); + if ( hit.entity->isColliderWall() ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + } + } + } + + if ( parent ) + { + updateEnemyBar(parent, hit.entity, Language::get(hit.entity->getColliderLangName()), entityHP, hit.entity->colliderMaxHP, false, + DamageGib::DMG_DEFAULT); + } + } else if ( hitstats != NULL && hit.entity != parent ) { if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) ) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 2304a8926..bea898481 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -712,9 +712,9 @@ void Entity::colliderOnDestroy() { entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); - entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->vel_z = (-40 - local_rng.rand() % 10) * .01; entity->goldBouncing = 0; - entity->z = 0.0; + entity->z = 0.0 - (local_rng.rand() % 3); entity->flags[INVISIBLE] = false; if ( multiplayer == SERVER ) @@ -747,10 +747,10 @@ void Entity::colliderOnDestroy() { ent->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(ent->yaw); ent->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(ent->yaw); - ent->vel_z = (-40 - local_rng.rand() % 5) * .01; + ent->vel_z = (-40 - local_rng.rand() % 10) * .01; ent->goldBouncing = 0; ent->goldInContainer = 0; - ent->z = 0.0; + ent->z = 0.0 - (local_rng.rand() % 3); ent->flags[INVISIBLE] = false; if ( multiplayer == SERVER ) @@ -839,7 +839,8 @@ int Entity::getColliderSfxOnBreak() const { if ( !isDamageableCollider() ) { return 0; } auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; - return colliderData.sfxBreak; + if ( colliderData.sfxBreak.size() == 0 ) { return 0; } + return colliderData.sfxBreak[local_rng.rand() % colliderData.sfxBreak.size()]; } void actColliderDecoration(Entity* my) @@ -866,7 +867,11 @@ void actColliderDecoration(Entity* my) } if ( colliderDmgType.minotaurPathThroughAndBreak ) { - my->colliderHasCollision = 2; + my->colliderHasCollision |= EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO; + } + if ( colliderDmgType.allowNPCPathing ) + { + my->colliderHasCollision |= EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC; } } } @@ -1012,10 +1017,7 @@ void actColliderDecoration(Entity* my) serverSpawnMiscParticles(my, PARTICLE_EFFECT_ABILITY_ROCK, sprite); } } - if ( colliderData.sfxBreak > 0 ) - { - playSoundEntity(my, colliderData.sfxBreak, 128); - } + playSoundEntity(my, my->getColliderSfxOnBreak(), 128); my->colliderOnDestroy(); list_RemoveNode(my->mynode); return; diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 867b241c4..e492dcc35 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -5483,13 +5483,16 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT ) { // break it down! my->monsterHitTime++; if ( my->monsterHitTime >= HITRATE ) { - my->monsterAttack = my->getAttackPose(); // random attack motion + if ( !hasrangedweapon ) + { + my->monsterAttack = my->getAttackPose(); // random attack motion + } my->monsterHitTime = HITRATE / 4; my->monsterAttackTime = 0; int damage = 2 + local_rng.rand() % 3; @@ -6575,13 +6578,16 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT ) { // break it down! my->monsterHitTime++; if ( my->monsterHitTime >= HITRATE ) { - my->monsterAttack = my->getAttackPose(); // random attack motion + if ( !hasrangedweapon ) + { + my->monsterAttack = my->getAttackPose(); // random attack motion + } my->monsterHitTime = HITRATE / 4; my->monsterAttackTime = 0; int damage = 2 + local_rng.rand() % 3; diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 422de1b56..7b6b3b7f5 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -1558,6 +1558,73 @@ void actThrown(Entity* my) } } } + else if ( hit.entity->isDamageableCollider() ) + { + int damage = 1; + int axe = 0; + if ( hit.entity->isColliderResistToSkill(PRO_RANGED) || hit.entity->isColliderWall() ) + { + damage = 1; + } + else + { + damage = 2 + local_rng.rand() % 3; + } + if ( hit.entity->isColliderWeakToSkill(PRO_RANGED) && cat == THROWN ) + { + if ( parent && parent->getStats() ) + { + axe = 2 * (parent->getStats()->getModifiedProficiency(PRO_RANGED) / 20); + } + axe = std::min(axe, 9); + } + damage += axe; + if ( my->thrownProjectileCharge >= 1 ) + { + damage += my->thrownProjectileCharge / 5; + } + + int& entityHP = hit.entity->colliderCurrentHP; + int oldHP = entityHP; + entityHP -= damage; + + int sound = 28; //damage.ogg + if ( hit.entity->getColliderSfxOnHit() > 0 ) + { + sound = hit.entity->getColliderSfxOnHit(); + } + playSoundEntity(hit.entity, sound, 64); + + if ( entityHP > 0 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT_BASIC, Language::get(hit.entity->getColliderOnHitLangEntry()), + Language::get(hit.entity->getColliderLangName())); + } + } + else + { + entityHP = 0; + + hit.entity->colliderKillerUid = parent ? parent->getUID() : 0; + if ( parent && parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(hit.entity->getColliderOnBreakLangEntry()), + Language::get(hit.entity->getColliderLangName())); + if ( hit.entity->isColliderWall() ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); + } + } + } + + if ( parent ) + { + updateEnemyBar(parent, hit.entity, Language::get(hit.entity->getColliderLangName()), entityHP, hit.entity->colliderMaxHP, false, + DamageGib::DMG_DEFAULT); + } + } break; } } diff --git a/src/collision.cpp b/src/collision.cpp index 82e5a0fc9..437c8948f 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -23,6 +23,7 @@ #include "collision.hpp" #include "prng.hpp" #include "player.hpp" +#include "mod_tools.hpp" #ifdef __ARM_NEON__ #include #endif @@ -460,7 +461,8 @@ bool entityInsideSomething(Entity* entity) } if ( entity->behavior == &actDeathGhost ) { - if ( testEntity->behavior == &actMonster || testEntity->behavior == &actPlayer ) + if ( testEntity->behavior == &actMonster || testEntity->behavior == &actPlayer + || (testEntity->isDamageableCollider() && (testEntity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC)) ) { continue; } @@ -654,11 +656,16 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { continue; } - if ( entity->isDamageableCollider() && entity->colliderHasCollision == 2 + if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO) && my->behavior == &actMonster && my->getMonsterTypeFromSprite() == MINOTAUR ) { continue; } + if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC) + && ((my->behavior == &actMonster && my->getMonsterTypeFromSprite() == GYROBOT) || my->behavior == &actDeathGhost) ) + { + continue; + } if ( (my->behavior == &actMonster || my->behavior == &actBoulder) && entity->behavior == &actDoorFrame ) { continue; // monsters don't have hard collision with door frames @@ -1793,7 +1800,12 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity continue; } if ( isMonster && my->getMonsterTypeFromSprite() == MINOTAUR && entity->isDamageableCollider() - && entity->colliderHasCollision == 2 ) + && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO) ) + { + continue; + } + else if ( isMonster && my->getMonsterTypeFromSprite() == GYROBOT && entity->isDamageableCollider() + && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC) ) { continue; } diff --git a/src/maps.cpp b/src/maps.cpp index 02c2ffdad..552590072 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4318,43 +4318,27 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } else if ( map_rng.rand() % 2 == 1 ) // 50% chance { - int totalGold = 12 + map_rng.rand() % 9; // 12 - 20 gold - + std::vector genGold; + int numGold = 3 + map_rng.rand() % 3; + while ( numGold > 0 ) { + --numGold; Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold + genGold.push_back(entity); entity->x = breakable->x; entity->y = breakable->y; - int amount = (totalGold / 4) + map_rng.rand() % std::max(1, totalGold / 4); - totalGold -= amount; - entity->goldAmount = amount; + entity->goldAmount = 2 + map_rng.rand() % 3; entity->flags[INVISIBLE] = true; entity->yaw = breakable->yaw; entity->goldInContainer = breakable->getUID(); breakable->colliderContainedEntity = entity->getUID(); numGenGold++; } + int index = -1; + for ( auto gold : genGold ) { - Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold - entity->x = breakable->x; - entity->y = breakable->y; - int amount = (totalGold / 4) + map_rng.rand() % std::max(1, totalGold / 4); - totalGold -= amount; - entity->goldAmount = amount; - totalGold -= entity->goldAmount; - entity->flags[INVISIBLE] = true; - entity->yaw = breakable->yaw + PI / 3; - entity->goldInContainer = breakable->getUID(); - numGenGold++; - } - { - Entity* entity = newEntity(9, 1, map.entities, nullptr); // gold - entity->x = breakable->x; - entity->y = breakable->y; - entity->goldAmount = std::max(1, totalGold); - entity->flags[INVISIBLE] = true; - entity->yaw = breakable->yaw + 2 * PI / 3; - entity->goldInContainer = breakable->getUID(); - numGenGold++; + ++index; + gold->yaw += (index * PI) / genGold.size(); } } else diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 615bd6f7c..c690f440d 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -8874,6 +8874,10 @@ void EditorEntityData_t::readFromFile() colliderDmg.bombsAttach = itr->value["bombs_attach"].GetBool(); colliderDmg.boulderDestroys = itr->value["boulder_destroy"].GetBool(); colliderDmg.showAsWallOnMinimap = itr->value["minimap_appear_as_wall"].GetBool(); + if ( itr->value.HasMember("allow_npc_pathing") ) + { + colliderDmg.allowNPCPathing = itr->value["allow_npc_pathing"].GetBool(); + } if ( itr->value.HasMember("bonus_damage_skills") && itr->value["bonus_damage_skills"].IsArray() ) { for ( auto itr2 = itr->value["bonus_damage_skills"].Begin(); itr2 != itr->value["bonus_damage_skills"].End(); ++itr2 ) @@ -8975,7 +8979,21 @@ void EditorEntityData_t::readFromFile() } } } - collider.sfxBreak = itr->value["sfx_break"].GetInt(); + if ( itr->value["sfx_break"].IsInt() ) + { + collider.sfxBreak.push_back(itr->value["sfx_break"].GetInt()); + } + else if ( itr->value["sfx_break"].IsArray() ) + { + for ( auto itr2 = itr->value["sfx_break"].Begin(); + itr2 != itr->value["sfx_break"].End(); ++itr2 ) + { + if ( itr2->IsInt() ) + { + collider.sfxBreak.push_back(itr2->GetInt()); + } + } + } collider.sfxHit = itr->value["sfx_hit"].GetInt(); collider.damageCalculationType = itr->value["damage_calc"].GetString(); collider.entityLangEntry = itr->value["entity_lang_entry"].GetInt(); diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index fc3da021e..216e75ba6 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3327,7 +3327,7 @@ struct EditorEntityData_t { int gib = 0; std::vector gib_hit; - int sfxBreak = 0; + std::vector sfxBreak; int sfxHit = 0; std::string damageCalculationType = "default"; std::string name = ""; @@ -3368,9 +3368,12 @@ struct EditorEntityData_t bool bombsAttach = false; bool boulderDestroys = false; bool showAsWallOnMinimap = false; + bool allowNPCPathing = false; std::unordered_set proficiencyBonusDamage; std::unordered_set proficiencyResistDamage; }; + static const int COLLIDER_COLLISION_FLAG_MINO = 2; + static const int COLLIDER_COLLISION_FLAG_NPC = 4; static std::map colliderDmgTypes; static std::map colliderData; static std::map> colliderRandomGenPool; diff --git a/src/paths.cpp b/src/paths.cpp index 845089d9a..1afbb7423 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -19,6 +19,7 @@ #include "items.hpp" #include "net.hpp" #include "magic/magic.hpp" +#include "mod_tools.hpp" int* pathMapFlying = NULL; int* pathMapGrounded = NULL; @@ -336,7 +337,26 @@ int pathCheckObstacle(int x, int y, Entity* my, Entity* target) { continue; } - if ( entity->sprite == 14 // fountain + if ( entity->sprite == 179 ) // collider + { + if ( (int)floor(entity->x / 16) == u && (int)floor(entity->y / 16) == v ) + { + if ( entity->colliderHasCollision != 0 || entity->colliderDiggable != 0 ) + { + return 1; + } + auto find = EditorEntityData_t::colliderData.find(entity->colliderDamageTypes); + if ( find != EditorEntityData_t::colliderData.end() ) + { + auto& colliderDmgType = EditorEntityData_t::colliderDmgTypes[find->second.damageCalculationType]; + if ( !colliderDmgType.allowNPCPathing ) + { + return 1; + } + } + } + } + else if ( entity->sprite == 14 // fountain || entity->sprite == 15 // sink || (entity->sprite == 19 && !entity->flags[PASSABLE]) // gate || (entity->sprite == 20 && !entity->flags[PASSABLE]) // gate 2 @@ -352,7 +372,6 @@ int pathCheckObstacle(int x, int y, Entity* my, Entity* target) || entity->sprite == 169 // statue || entity->sprite == 177 // shrine || entity->sprite == 178 // spell shrine - || (entity->sprite == 179 && (entity->colliderHasCollision != 0 || entity->colliderDiggable != 0)) // collider ) { if ( (int)floor(entity->x / 16) == u && (int)floor(entity->y / 16) == v ) @@ -392,6 +411,7 @@ static std::chrono::microseconds ms(0); static Uint32 updatedOnTick = 0; static ConsoleVariable cvar_pathlimit("/pathlimit", 200); static ConsoleVariable cvar_pathing_debug("/pathing_debug", false); +static ConsoleVariable cvar_pathing_collider_npc("/pathing_collider_npc", true); int lastGeneratePathTries = 0; list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, GeneratePathTypes pathingType, bool lavaIsPassable) { @@ -610,11 +630,20 @@ list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, { continue; } - if (stats && stats->type == MINOTAUR && (entity->behavior == &actBoulder || (entity->isDamageableCollider() && entity->colliderHasCollision == 2))) + if (stats && stats->type == MINOTAUR && (entity->behavior == &actBoulder || (entity->isDamageableCollider() + && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO)))) { // minotaurs bust through boulders, not an obstacle continue; } + else if ( (my && my->behavior == &actMonster) && entity->isDamageableCollider() + && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC)) + { + if ( *cvar_pathing_collider_npc ) + { + continue; + } + } int x = std::min(std::max(0, entity->x / 16), map.width - 1); //TODO: Why are int and double being compared? And why are int and unsigned int being compared? int y = std::min(std::max(0, entity->y / 16), map.height - 1); //TODO: Why are int and double being compared? And why are int and unsigned int being compared? pathMap[y + x * map.height] = 0; diff --git a/src/player.cpp b/src/player.cpp index 4bf3f0ab7..3b93c7f7c 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3386,6 +3386,11 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) return 0.0; } + if ( parent->behavior == &actColliderDecoration && !callout ) + { + return 0.0; + } + if ( !selectInteract && stats[player.playernum] && stats[player.playernum]->defending && player.entity ) { if ( stats[player.playernum]->shield && stats[player.playernum]->shield->type == TOOL_TINKERING_KIT ) From 41ed1010a3cd6468a95a8e61546fdd29cc29c03f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 03:59:11 +1000 Subject: [PATCH 078/244] * another fix for disconnecteds client doing endgame --- src/ui/MainMenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index ce9020a68..ec98350ed 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -26295,7 +26295,7 @@ namespace MainMenu { void disconnectedFromServer(const char* text) { // when a player is disconnected from the server if (multiplayer != SINGLE) { - if ( saveGameExists(multiplayer == SINGLE) ) { + if ( saveGameExists(multiplayer == SINGLE) && gameModeManager.allowsSaves() ) { doEndgame(false, true); } else From 5e31c3cac7a7179b01a46998594fd4809db9d599 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 04:52:30 +1000 Subject: [PATCH 079/244] * reveal boss entry on entering their floors, brimstone boulder * reveal shop when talking, same with mysterious * add missing gold velz for client --- src/actmonster.cpp | 27 ++++++++++++++++++++ src/interface/shopgui.cpp | 26 ++++++++++++++++++++ src/mod_tools.cpp | 52 ++++++++++++++++++++++++++++++++++++++- src/net.cpp | 21 ++++++++++++++-- 4 files changed, 123 insertions(+), 3 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index e492dcc35..c3176bb9a 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -4023,6 +4023,33 @@ void actMonster(Entity* my) } else if ( myStats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 ) // mysterious merchant { + // interacting triggers compendium reveal + auto find = Compendium_t::Events_t::monsterUniqueIDLookup.find("mysterious shop"); + if ( find != Compendium_t::Events_t::monsterUniqueIDLookup.end() ) + { + auto find2 = Compendium_t::Events_t::monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + find->second); + if ( find2 != Compendium_t::Events_t::monsterIDToString.end() ) + { + if ( players[monsterclicked]->isLocalPlayer() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find2->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + else if ( monsterclicked > 0 && multiplayer == SERVER ) + { + strcpy((char*)net_packet->data, "CMPU"); + SDLNet_Write32(Compendium_t::Events_t::kEventMonsterOffset + find->second, &net_packet->data[4]); + net_packet->address.host = net_clients[monsterclicked - 1].host; + net_packet->address.port = net_clients[monsterclicked - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, monsterclicked - 1); + } + } + } + bool hasOrb = false; for ( node_t* node = myStats->inventory.first; node; node = node->next ) { diff --git a/src/interface/shopgui.cpp b/src/interface/shopgui.cpp index 335fcf5d0..c19fd2a18 100644 --- a/src/interface/shopgui.cpp +++ b/src/interface/shopgui.cpp @@ -386,12 +386,38 @@ void Player::ShopGUI_t::openShop() if ( shopkeepertype[player.playernum] == 10 ) // mysterious shopkeep { shopName->setText(Language::get(4129)); // '???'s' + + // opening shop triggers shopkeep compendium reveal + auto find = Compendium_t::Events_t::monsterUniqueIDLookup.find("mysterious shop"); + if ( find != Compendium_t::Events_t::monsterUniqueIDLookup.end() ) + { + auto find2 = Compendium_t::Events_t::monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + find->second); + if ( find2 != Compendium_t::Events_t::monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find2->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } } else { char buf[64]; snprintf(buf, sizeof(buf), Language::get(4127), shopkeepername[player.playernum].c_str()); shopName->setText(buf); + + // opening shop triggers shopkeep compendium reveal + auto find = Compendium_t::Events_t::monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + SHOPKEEPER); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } } SDL_Rect pos = shopName->getSize(); diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index c690f440d..23f70dae9 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -14987,7 +14987,7 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag { unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } - if ( category && !strcmp(category, "shop") ) + if ( find->second == "shop" ) { // buying items triggers shopkeep stuff auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + SHOPKEEPER); @@ -14999,7 +14999,57 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; } } + } + else if ( find->second == "herx lair" ) + { + auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + LICH); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + else if ( find->second == "molten throne" ) + { + auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + DEVIL); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["brimstone boulder"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + else if ( find->second == "citadel sanctum" ) + { + auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + LICH_FIRE); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + LICH_ICE); + if ( find != monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } } } } diff --git a/src/net.cpp b/src/net.cpp index 8fd075ee2..72841b5f3 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2654,9 +2654,9 @@ static std::unordered_map clientPacketHandlers = { { entity->vel_x = (0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); entity->vel_y = (0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); - entity->vel_z = (-40 - local_rng.rand() % 5) * .01; + entity->vel_z = (-40 - local_rng.rand() % 10) * .01; entity->goldBouncing = 0; - entity->z = 0.0; + entity->z = 0.0 - (local_rng.rand() % 3); entity->flags[INVISIBLE] = false; } } @@ -5115,6 +5115,23 @@ static std::unordered_map clientPacketHandlers = { } } }, + // compendium reveal an entry + { 'CMPU', []() { + int eventID = SDLNet_Read32(&net_packet->data[4]); + if ( eventID >= Compendium_t::Events_t::kEventMonsterOffset && eventID < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) + { + auto find = Compendium_t::Events_t::monsterIDToString.find(eventID); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + auto& unlockStatus = Compendium_t::CompendiumMonsters_t::unlocks[find->second]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } + } }, + // compendium data update { 'CMPD', []() { Uint8 clientSequence = net_packet->data[4]; From 13703453a33017e67b6c7e7078a2dd29ea0dcba9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 17:04:20 +1000 Subject: [PATCH 080/244] * fix secret weapon achievement proccing on amplify magic casts --- src/magic/actmagic.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 6ce763cb5..1a74877a4 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -1780,7 +1780,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -1858,7 +1858,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -1881,7 +1881,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -1903,7 +1903,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -1983,7 +1983,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2018,7 +2018,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2192,7 +2192,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2215,7 +2215,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2237,7 +2237,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2317,7 +2317,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2683,7 +2683,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2786,7 +2786,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2806,7 +2806,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2824,7 +2824,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -2900,7 +2900,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } } } @@ -3562,7 +3562,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -3580,7 +3580,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) { @@ -3656,7 +3656,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } } } @@ -3818,7 +3818,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( my->actmagicProjectileArc > 0 ) { Entity* caster = uidToEntity(spell->caster); - spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } if ( !(my->actmagicIsOrbiting == 2) ) From 5cf7a4534e6c439cdeb786d780a671ccbeafc586 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 17:05:58 +1000 Subject: [PATCH 081/244] * add maces to human spawnlist --- src/monster_human.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/monster_human.cpp b/src/monster_human.cpp index 8cd89d590..844e4acdb 100644 --- a/src/monster_human.cpp +++ b/src/monster_human.cpp @@ -456,10 +456,14 @@ void initHuman(Entity* my, Stat* myStats) myStats->weapon = newItem(SHORTBOW, WORN, 0, 1, rng.rand(), false, nullptr); break; case 2: - case 3: myStats->weapon = newItem(BRONZE_AXE, WORN, 0, 1, rng.rand(), false, nullptr); break; + case 3: + myStats->weapon = newItem(BRONZE_MACE, WORN, 0, 1, rng.rand(), false, nullptr); + break; case 4: + myStats->weapon = newItem(IRON_MACE, WORN, 0, 1, rng.rand(), false, nullptr); + break; case 5: myStats->weapon = newItem(BRONZE_SWORD, WORN, 0, 1, rng.rand(), false, nullptr); break; From ac5964d56f904a99632efd86f52b336e7cc9a302 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 24 Aug 2024 17:28:43 +1000 Subject: [PATCH 082/244] * add thrown potion dmg tracking imrovement --- src/item_usage_funcs.cpp | 24 ++++++++++++++++++++++++ src/magic/actmagic.cpp | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 46ffc9599..d0aa3f3f7 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -819,6 +819,7 @@ bool item_PotionSickness(Item*& item, Entity* entity, Entity* usedBy) damage -= (local_rng.rand() % (1 + chance)); } messagePlayer(player, MESSAGE_HINT, Language::get(761)); + int oldHP = stats->HP; entity->modHP(-damage); stats->EFFECTS[EFF_POISONED] = true; if ( usedBy && usedBy != entity ) @@ -828,6 +829,11 @@ bool item_PotionSickness(Item*& item, Entity* entity, Entity* usedBy) { stats->poisonKiller = usedBy->getUID(); } + + if ( usedBy->behavior == &actPlayer && stats->HP < oldHP) + { + Compendium_t::Events_t::eventUpdate(usedBy->skill[2], Compendium_t::CPDM_THROWN_DMG_TOTAL, item->type, oldHP - stats->HP); + } } if ( stats->type == LICH || stats->type == SHOPKEEPER || stats->type == DEVIL || stats->type == MINOTAUR || stats->type == LICH_FIRE || stats->type == LICH_ICE ) @@ -1589,9 +1595,18 @@ bool item_PotionAcid(Item*& item, Entity* entity, Entity* usedBy) damage -= (local_rng.rand() % (1 + chance)); } messagePlayer(player, MESSAGE_HINT, Language::get(770)); + int oldHP = stats->HP; entity->modHP(-damage); playSoundEntity(entity, 28, 64); + if ( usedBy && usedBy != entity ) + { + if ( usedBy->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdate(usedBy->skill[2], Compendium_t::CPDM_THROWN_DMG_TOTAL, item->type, oldHP - stats->HP); + } + } + // set obituary entity->setObituary(Language::get(1535)); stats->killer = KilledBy::FUNNY_POTION; @@ -1699,8 +1714,17 @@ bool item_PotionUnstableStorm(Item*& item, Entity* entity, Entity* usedBy, Entit else { messagePlayer(player, MESSAGE_HINT, Language::get(770)); + int oldHP = stats->HP; entity->modHP(-damage); playSoundEntity(entity, 28, 64); + + if ( usedBy && usedBy != entity ) + { + if ( usedBy->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdate(usedBy->skill[2], Compendium_t::CPDM_THROWN_DMG_TOTAL, item->type, oldHP - stats->HP); + } + } } // set obituary diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 1a74877a4..d43ef5776 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -695,7 +695,7 @@ void magicOnEntityHit(Entity* parent, Entity* particle, Entity* hitentity, Stat* if ( particle->actmagicIsOrbiting == 2 && particle->actmagicOrbitCastFromSpell == 0 ) { // cast by firestorm potion etc - if ( damageTaken > 0 ) + if ( damageTaken > 0 && parent != hitentity ) { auto find = ItemTooltips.spellItems.find(spellID); if ( find != ItemTooltips.spellItems.end() ) From 36bd571fbae4d3093175f20720f951abc38f4fdb Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 25 Aug 2024 21:48:19 +1000 Subject: [PATCH 083/244] * underworld generate cages over pits --- src/collision.cpp | 4 +- src/collision.hpp | 2 +- src/maps.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 437c8948f..01bd9b420 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -1700,7 +1700,7 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r -------------------------------------------------------------------------------*/ -int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList, bool checkWalls) +int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList, bool checkWalls, bool checkFloor) { node_t* node; Entity* entity; @@ -1743,7 +1743,7 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity } } if ( !levitating - && (!map.tiles[index] + && ((!map.tiles[index] && checkFloor) || ( (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) && isMonster) ) ) // no floor { diff --git a/src/collision.hpp b/src/collision.hpp index 8f6137ea8..594d3976b 100644 --- a/src/collision.hpp +++ b/src/collision.hpp @@ -33,4 +33,4 @@ real_t clipMove(real_t* x, real_t* y, real_t vx, real_t vy, Entity* my); Entity* findEntityInLine(Entity* my, real_t x1, real_t y1, real_t angle, int entities, Entity* target); real_t lineTrace(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground); real_t lineTraceTarget(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target); //If the linetrace function encounters the linetrace entity, it returns even if it's invisible or passable. -int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList = true, bool checkWalls = true); +int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList = true, bool checkWalls = true, bool checkFloor = true); diff --git a/src/maps.cpp b/src/maps.cpp index 552590072..fdcac7347 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -3216,13 +3216,15 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple startRoomInfo.checkBorderAccessibility(); } + std::vector underworldEmptyTiles; + // monsters, decorations, and items numpossiblelocations = map.width * map.height; for ( y = 0; y < map.height; y++ ) { for ( x = 0; x < map.width; x++ ) { - if ( checkObstacle( x * 16 + 8, y * 16 + 8, NULL, NULL ) || firstroomtile[y + x * map.height] ) + if ( firstroomtile[y + x * map.height] ) { possiblelocations[y + x * map.height] = false; numpossiblelocations--; @@ -3245,6 +3247,21 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple possiblelocations[y + x * map.height] = false; --numpossiblelocations; } + else if ( checkObstacle(x * 16 + 8, y * 16 + 8, NULL, NULL, false, true, false) ) + { + possiblelocations[y + x * map.height] = false; + --numpossiblelocations; + } + else if ( !map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + possiblelocations[y + x * map.height] = false; + numpossiblelocations--; + + if ( !strncmp(map.name, "Underworld", 10) ) + { + underworldEmptyTiles.push_back(x + y * 1000); + } + } else { possiblelocations[y + x * map.height] = true; @@ -4008,17 +4025,19 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int numBreakables = std::min(15, numpossiblelocations / 10); struct BreakableNode_t { - BreakableNode_t(int _walls, int _x, int _y, int _dir) + BreakableNode_t(int _walls, int _x, int _y, int _dir, int _id = -1) { walls = _walls; x = _x; y = _y; dir = _dir; + id = _id; }; int walls; int x; int y; int dir; + int id = -1; }; auto compFuncBreakable = [](BreakableNode_t& lhs, BreakableNode_t& rhs) { @@ -4029,7 +4048,73 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { numBreakables = 0; } + int numOpenAreaBreakables = 0; std::priority_queue, decltype(compFuncBreakable)> breakableLocations(compFuncBreakable); + if ( findBreakables->first == "Underworld" ) + { + numOpenAreaBreakables = 10; + + std::vector picked; + while ( numOpenAreaBreakables > 0 && underworldEmptyTiles.size() > 0 ) + { + int pick = map_rng.rand() % underworldEmptyTiles.size(); + picked.push_back(underworldEmptyTiles[pick]); + + underworldEmptyTiles.erase(underworldEmptyTiles.begin() + pick); + } + + for ( auto& coord : picked ) + { + int x = (coord) % 1000; + int y = (coord) / 1000; + + if ( numOpenAreaBreakables > 0 ) + { + int obstacles = 0; + // add some hanging cages + for ( int x2 = -1; x2 <= 1; x2++ ) + { + for ( int y2 = -1; y2 <= 1; y2++ ) + { + if ( x2 == 0 && y2 == 0 ) { continue; } + int checkx = x + x2; + int checky = y + y2; + if ( checkx >= 0 && checkx < map.width ) + { + if ( checky >= 0 && checky < map.height ) + { + int index = (checky)*MAPLAYERS + (checkx)*MAPLAYERS * map.height; + if ( map.tiles[index] ) + { + ++obstacles; + break; + } + if ( checkObstacle((checkx) * 16, (checky) * 16, NULL, NULL, false, true, false) ) + { + ++obstacles; + break; + } + } + } + } + } + + if ( obstacles == 0 ) + { + breakableLocations.push(BreakableNode_t(0, x, y, map_rng.rand() % 4, + map_rng.rand() % 2 ? 14 : 40)); // random dir, low prio, hanging cage ids + --numOpenAreaBreakables; + + if ( possiblelocations[y + x * map.height] ) + { + possiblelocations[y + x * map.height] = false; + --numpossiblelocations; + } + } + } + } + } + for ( c = 0; c < std::min(numBreakables, numpossiblelocations); ++c ) { // choose a random location from those available @@ -4098,6 +4183,35 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple possiblelocations[y + x * map.height] = false; numpossiblelocations--; + //if ( walls.size() == 0 && findBreakables->first == "Underworld" && numOpenAreaBreakables > 0 ) + //{ + // int obstacles = 0; + // // add some hanging cages + // for ( int x2 = -1; x2 <= 1; x2++ ) + // { + // for ( int y2 = -1; y2 <= 1; y2++ ) + // { + // if ( x2 == 0 && y2 == 0 ) { continue; } + // int checkx = x + x2; + // int checky = y + y2; + // if ( checkObstacle((checkx) * 16, (checky) * 16, NULL, NULL, false, true, false) ) + // { + // ++obstacles; + // break; + // } + // } + // } + + // if ( obstacles == 0 ) + // { + // breakableLocations.push(BreakableNode_t(1, x, y, map_rng.rand() % 4, + // map_rng.rand() % 2 ? 14 : 40)); // random dir, low prio, hanging cage ids + // --numOpenAreaBreakables; + // } + // --c; + // continue; + //} + if ( walls.size() == 0 || walls.size() >= 4 ) { // try again @@ -4280,8 +4394,15 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple breakable->y = y * 16.0; breakable->colliderDecorationRotation = top.dir; + if ( top.id >= 0 ) + { + breakable->colliderDamageTypes = top.id; + } + else + { int picked = map_rng.discrete(chances.data(), chances.size()); breakable->colliderDamageTypes = ids[picked]; + } Monster monsterEvent = NOTHING; auto findData = EditorEntityData_t::colliderData.find(breakable->colliderDamageTypes); @@ -4304,6 +4425,9 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( breakableGoodies > 0 ) { --breakableGoodies; + + int index = (y) * MAPLAYERS + (x) * MAPLAYERS * map.height; + if ( (breakableMonsters < 2 && monsterEvent != NOTHING && map_rng.rand() % 10 == 0) ) // 10% monster inside { if ( map_rng.rand() % 2 == 0 ) @@ -4316,7 +4440,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } ++breakableMonsters; } - else if ( map_rng.rand() % 2 == 1 ) // 50% chance + else if ( !map.tiles[index] || map_rng.rand() % 2 == 1 ) // 50% chance (or floating over a pit is just gold) { std::vector genGold; int numGold = 3 + map_rng.rand() % 3; @@ -4377,7 +4501,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( false ) { - messagePlayer(0, MESSAGE_DEBUG, "pick: %d | x: %d y: %d", picked, x, y); + //messagePlayer(0, MESSAGE_DEBUG, "pick: %d | x: %d y: %d", picked, x, y); Entity* ent = newEntity(245, 0, map.entities, nullptr); //ent->behavior = &actBoulder; ent->x = x * 16.0 + 8; From b7d4816b592eede1493291f08fb68bf838501655 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 04:51:55 +1000 Subject: [PATCH 084/244] * fix naked npcs - delay bodypart sending as server --- src/entity.cpp | 1 + src/entity.hpp | 3 +- src/monster_automaton.cpp | 226 ++++++++++++++++++++----------- src/monster_gnome.cpp | 140 ++++++++++++------- src/monster_goatman.cpp | 226 ++++++++++++++++++++----------- src/monster_goblin.cpp | 226 ++++++++++++++++++++----------- src/monster_human.cpp | 270 ++++++++++++++++++++++++------------- src/monster_incubus.cpp | 204 ++++++++++++++++++---------- src/monster_insectoid.cpp | 226 ++++++++++++++++++++----------- src/monster_kobold.cpp | 172 ++++++++++++++--------- src/monster_scarab.cpp | 64 +++++---- src/monster_sentrybot.cpp | 22 ++- src/monster_shadow.cpp | 182 ++++++++++++++++--------- src/monster_shopkeeper.cpp | 226 ++++++++++++++++++++----------- src/monster_skeleton.cpp | 270 ++++++++++++++++++++++++------------- src/monster_succubus.cpp | 204 ++++++++++++++++++---------- src/monster_vampire.cpp | 270 ++++++++++++++++++++++++------------- 17 files changed, 1883 insertions(+), 1049 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index f07c18d3b..1f8aff97e 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,6 +45,7 @@ Construct an Entity -------------------------------------------------------------------------------*/ +ConsoleVariable cvar_entity_bodypart_sync_tick("/entity_bodypart_sync_tick", TICKS_PER_SECOND / 4); Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creaturelist) : lightBonus(0.f), chanceToPutOutFire(skill[37]), diff --git a/src/entity.hpp b/src/entity.hpp index 7df7b35ba..9c657429c 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -16,6 +16,7 @@ #include "stat.hpp" #include "light.hpp" #include "monster.hpp" +#include "interface/consolecommand.hpp" // entity flags #define BRIGHT 1 @@ -37,7 +38,7 @@ // number of entity skills and fskills static const int NUMENTITYSKILLS = 60; static const int NUMENTITYFSKILLS = 30; - +extern ConsoleVariable cvar_entity_bodypart_sync_tick; struct spell_t; // entity class diff --git a/src/monster_automaton.cpp b/src/monster_automaton.cpp index edb2627d4..ca69277ca 100644 --- a/src/monster_automaton.cpp +++ b/src/monster_automaton.cpp @@ -907,14 +907,22 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -949,14 +957,22 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -977,14 +993,22 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1078,19 +1102,27 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1131,19 +1163,27 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1172,19 +1212,27 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1221,19 +1269,27 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1291,19 +1347,27 @@ void automatonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index 01872c80b..b2e86d8d1 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -551,14 +551,22 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -586,14 +594,22 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -703,19 +719,27 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -756,19 +780,27 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -853,19 +885,27 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_goatman.cpp b/src/monster_goatman.cpp index 8895bf021..5f350de94 100644 --- a/src/monster_goatman.cpp +++ b/src/monster_goatman.cpp @@ -965,14 +965,22 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -993,14 +1001,22 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1021,14 +1037,22 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1120,19 +1144,27 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1173,19 +1205,27 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1214,19 +1254,27 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1263,19 +1311,27 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1333,19 +1389,27 @@ void goatmanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_goblin.cpp b/src/monster_goblin.cpp index ea6c6fab1..acc96fd80 100644 --- a/src/monster_goblin.cpp +++ b/src/monster_goblin.cpp @@ -703,14 +703,22 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -733,14 +741,22 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -763,14 +779,22 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -862,19 +886,27 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -915,19 +947,27 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -956,19 +996,27 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1005,19 +1053,27 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1075,19 +1131,27 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_human.cpp b/src/monster_human.cpp index 844e4acdb..95fe65579 100644 --- a/src/monster_human.cpp +++ b/src/monster_human.cpp @@ -1154,14 +1154,22 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1222,14 +1230,22 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1295,14 +1311,22 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1372,14 +1396,22 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1472,14 +1504,22 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1571,19 +1611,27 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1624,19 +1672,27 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1665,19 +1721,27 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1714,19 +1778,27 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1784,19 +1856,27 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_incubus.cpp b/src/monster_incubus.cpp index 9765fe677..4dd0473aa 100644 --- a/src/monster_incubus.cpp +++ b/src/monster_incubus.cpp @@ -960,14 +960,22 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -988,14 +996,22 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1102,19 +1118,27 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1157,19 +1181,27 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1198,19 +1230,27 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1247,19 +1287,27 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1317,19 +1365,27 @@ void incubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_insectoid.cpp b/src/monster_insectoid.cpp index 079a37360..409b946b4 100644 --- a/src/monster_insectoid.cpp +++ b/src/monster_insectoid.cpp @@ -1026,14 +1026,22 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1068,14 +1076,22 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1096,14 +1112,22 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1195,19 +1219,27 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1248,19 +1280,27 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1289,19 +1329,27 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1338,19 +1386,27 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1408,19 +1464,27 @@ void insectoidMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_kobold.cpp b/src/monster_kobold.cpp index 22ca027e5..b247e1bc5 100644 --- a/src/monster_kobold.cpp +++ b/src/monster_kobold.cpp @@ -659,14 +659,22 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -694,14 +702,22 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -814,19 +830,27 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -867,19 +891,27 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -975,19 +1007,27 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1023,19 +1063,27 @@ void koboldMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_scarab.cpp b/src/monster_scarab.cpp index f79bd0536..059cb5a36 100644 --- a/src/monster_scarab.cpp +++ b/src/monster_scarab.cpp @@ -393,19 +393,27 @@ void scarabAnimate(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -494,19 +502,27 @@ void scarabAnimate(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_sentrybot.cpp b/src/monster_sentrybot.cpp index 96ce5fc61..68f1a80c3 100644 --- a/src/monster_sentrybot.cpp +++ b/src/monster_sentrybot.cpp @@ -1498,14 +1498,22 @@ void gyroBotAnimate(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } break; diff --git a/src/monster_shadow.cpp b/src/monster_shadow.cpp index 48910d89b..1c3a40c67 100644 --- a/src/monster_shadow.cpp +++ b/src/monster_shadow.cpp @@ -884,14 +884,22 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1037,19 +1045,27 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1086,19 +1102,27 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1179,19 +1203,27 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1227,19 +1259,27 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1297,19 +1337,27 @@ void shadowMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_shopkeeper.cpp b/src/monster_shopkeeper.cpp index f0a6e7a38..28cfb9bd1 100644 --- a/src/monster_shopkeeper.cpp +++ b/src/monster_shopkeeper.cpp @@ -1422,14 +1422,22 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1452,14 +1460,22 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1487,14 +1503,22 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1597,19 +1621,27 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1650,19 +1682,27 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1725,19 +1765,27 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1774,19 +1822,27 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1844,19 +1900,27 @@ void shopkeeperMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_skeleton.cpp b/src/monster_skeleton.cpp index a136a6e0b..c24c8cae6 100644 --- a/src/monster_skeleton.cpp +++ b/src/monster_skeleton.cpp @@ -925,14 +925,22 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -953,14 +961,22 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -981,14 +997,22 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1013,14 +1037,22 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1102,14 +1134,22 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1204,19 +1244,27 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1257,19 +1305,27 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1298,19 +1354,27 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1347,19 +1411,27 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1417,19 +1489,27 @@ void skeletonMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_succubus.cpp b/src/monster_succubus.cpp index 86eaf4fb2..a8a70f10f 100644 --- a/src/monster_succubus.cpp +++ b/src/monster_succubus.cpp @@ -548,14 +548,22 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -576,14 +584,22 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -683,19 +699,27 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -738,19 +762,27 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -779,19 +811,27 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -828,19 +868,27 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -898,19 +946,27 @@ void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } diff --git a/src/monster_vampire.cpp b/src/monster_vampire.cpp index 0c67ccec3..832772153 100644 --- a/src/monster_vampire.cpp +++ b/src/monster_vampire.cpp @@ -801,14 +801,22 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -829,14 +837,22 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -857,14 +873,22 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -889,14 +913,22 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -954,14 +986,22 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1034,19 +1074,27 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1087,19 +1135,27 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1128,19 +1184,27 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1177,19 +1241,27 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -1247,19 +1319,27 @@ void vampireMoveBodyparts(Entity* my, Stat* myStats, double dist) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != entity->flags[INVISIBLE] ) - { - entity->skill[11] = entity->flags[INVISIBLE]; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } From 0f0857a49c23d376286471e0edfddbdfb6ad13b9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 04:53:20 +1000 Subject: [PATCH 085/244] * compendium arachnophobia models * compendium fix ui scaling, invisible blocking tooltip --- src/mod_tools.cpp | 42 +++- src/mod_tools.hpp | 1 + src/ui/MainMenu.cpp | 598 ++++++++++++++++++++++++++++++-------------- 3 files changed, 450 insertions(+), 191 deletions(-) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 23f70dae9..54ac7639a 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -10894,6 +10894,7 @@ SDL_Rect Compendium_t::tooltipPos; std::map>> Compendium_t::CompendiumMonsters_t::contents; std::map Compendium_t::CompendiumMonsters_t::contentsMap; +std::map>> Compendium_t::CompendiumMonsters_t::contents_unfiltered; std::map Compendium_t::CompendiumMonsters_t::unlocks; std::map>> Compendium_t::CompendiumWorld_t::contents; std::map Compendium_t::CompendiumWorld_t::contentsMap; @@ -10973,7 +10974,15 @@ void Compendium_t::readContentsLang(std::string name, std::mapMemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) { contents["default"].push_back(std::make_pair(itr2->value.GetString(), itr2->name.GetString())); - contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + if ( name == "contents_monsters" + && (!strcmp(itr2->value.GetString(), "crab") || !strcmp(itr2->value.GetString(), "bubbles")) ) + { + // dont read into map + } + else + { + contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + } } } @@ -10984,7 +10993,15 @@ void Compendium_t::readContentsLang(std::string name, std::mapMemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) { contents["alphabetical"].push_back(std::make_pair(itr2->value.GetString(), itr2->name.GetString())); - contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + if ( name == "contents_monsters" + && (!strcmp(itr2->value.GetString(), "crab") || !strcmp(itr2->value.GetString(), "bubbles")) ) + { + // dont read into map + } + else + { + contentsMap[itr2->value.GetString()] = itr2->name.GetString(); + } } } } @@ -10992,7 +11009,7 @@ void Compendium_t::readContentsLang(std::string name, std::map>> contents; static std::map contentsMap; + static std::map>> contents_unfiltered; static void readContentsLang(); static std::map unlocks; static int completionPercent; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index ec98350ed..a93ab7afb 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -13077,7 +13077,7 @@ namespace MainMenu { race = getMonsterFromPlayerRace(static_cast(stats)->playerRace); } Monster modifiedRace = static_cast(stats)->type; - if ( arachnophobia_filter ) + if ( ::arachnophobia_filter ) { if ( modifiedRace == SPIDER ) { @@ -34485,6 +34485,7 @@ namespace MainMenu { "*images/ui/Main Menus/AdventureArchives/C_ItemConditions_Panel_01.png", "item_widget_bg"); item_widget->setClickable(false); + item_widget->setHollow(true); item_widget->setInheritParentFrameOpacity(false); item_widget->setOpacity(0.0); item_widget->setDrawCallback([](const Widget& widget, SDL_Rect pos) { @@ -35865,7 +35866,18 @@ namespace MainMenu { { return; } - auto& entry = CompendiumEntries.monsters[name]; + + std::string entryname = name; + if ( name == "spider" && ((!intro && ::arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) + { + entryname = "crab"; + } + else if ( name == "shelob" && ((!intro && ::arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) + { + entryname = "bubbles"; + } + auto& entry = CompendiumEntries.monsters[entryname]; + if ( compendiumMonsterOverride ) { Compendium_t::compendiumEntityCurrent.set( @@ -36516,6 +36528,54 @@ namespace MainMenu { } } + if ( compendium_current == "monsters" ) + { + Compendium_t::CompendiumMonsters_t::contents[Compendium_t::compendium_sorting] = Compendium_t::CompendiumMonsters_t::contents_unfiltered[Compendium_t::compendium_sorting]; + if ( ((!intro && ::arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) + { + // crabs + auto& entries = Compendium_t::CompendiumMonsters_t::contents[Compendium_t::compendium_sorting]; + for ( auto it = entries.begin(); it != entries.end(); ) + { + if ( it->first == "spider" || it->first == "shelob" ) + { + it = entries.erase(it); + } + else + { + ++it; + } + } + for ( auto it = entries.begin(); it != entries.end(); ++it ) + { + if ( it->first == "crab" ) + { + it->first = "spider"; + } + if ( it->first == "bubbles" ) + { + it->first = "shelob"; + } + } + } + else + { + // spiders + auto& entries = Compendium_t::CompendiumMonsters_t::contents[sorting]; + for ( auto it = entries.begin(); it != entries.end(); ) + { + if ( it->first == "crab" || it->first == "bubbles" ) + { + it = entries.erase(it); + } + else + { + ++it; + } + } + } + } + auto* entries = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents[sorting] : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents[sorting] : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents[sorting] @@ -36882,6 +36942,55 @@ namespace MainMenu { if ( auto contents = frame->findFrame("contents") ) { + if ( compendium_current == "monsters" ) + { + Compendium_t::CompendiumMonsters_t::contents[Compendium_t::compendium_sorting] + = Compendium_t::CompendiumMonsters_t::contents_unfiltered[Compendium_t::compendium_sorting]; + if ( ((!intro && ::arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) + { + // crabs + auto& entries = Compendium_t::CompendiumMonsters_t::contents[Compendium_t::compendium_sorting]; + for ( auto it = entries.begin(); it != entries.end(); ) + { + if ( it->first == "spider" || it->first == "shelob" ) + { + it = entries.erase(it); + } + else + { + ++it; + } + } + for ( auto it = entries.begin(); it != entries.end(); ++it ) + { + if ( it->first == "crab" ) + { + it->first = "spider"; + } + if ( it->first == "bubbles" ) + { + it->first = "shelob"; + } + } + } + else + { + // spiders + auto& entries = Compendium_t::CompendiumMonsters_t::contents[Compendium_t::compendium_sorting]; + for ( auto it = entries.begin(); it != entries.end(); ) + { + if ( it->first == "crab" || it->first == "bubbles" ) + { + it = entries.erase(it); + } + else + { + ++it; + } + } + } + } + auto* entriesContents = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::contents : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::contents : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::contents @@ -38968,12 +39077,49 @@ namespace MainMenu { itemTooltipDisplay.scrolledToMax = 0; } + auto background = window->addImage( + SDL_Rect{ + *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, + (Frame::virtualScreenY - 598) / 2 + 1, + 958, + 598 }, + 0xffffffff, + "*images/ui/Main Menus/AdventureArchives/C_Window_BG_01.png", + "background" + ); + + Button* back_button = createBackWidget(window, [](Button& button) { + soundCancel(); + auto frame = static_cast(button.getParent()); + frame = static_cast(frame->getParent()); + frame = static_cast(frame->getParent()); + frame->removeSelf(); + assert(main_menu_frame); + if ( main_menu_frame ) { + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto compendium_button = buttons->findButton("Dungeon Compendium"); assert(compendium_button); + compendium_button->select(); + } + }/*, SDL_Rect{ -4, -4, 0, 0 }*/); + if ( back_button ) + { + if ( auto back = static_cast(back_button->getParent()) ) + { + SDL_Rect pos = back->getSize(); + pos.x = background->pos.x - 244; + pos.x -= 6; + pos.y = background->pos.y - pos.h; + pos.y += 6; + back->setSize(pos); + } + } + { const int offset_y = 14; auto lore_points = window->addFrame("lore_points_balance"); lore_points->setHollow(true); lore_points->setClickable(false); - lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); + lore_points->setSize(SDL_Rect{ background->pos.x + background->pos.w - 360, background->pos.y - 62, 194, 50 + offset_y }); lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/C_LP_Available_00.png", "lore_points_bg"); lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, @@ -38987,62 +39133,62 @@ namespace MainMenu { txt->setHJustify(Field::justify_t::RIGHT); txt->setDrawCallback([](const Widget& widget, SDL_Rect pos) { Compendium_t::PointsAnim_t::tickAnimate(); - auto txt = const_cast((Field*)(&widget)); - SDL_Rect size = txt->getSize(); - size.x = 0; - if ( Compendium_t::PointsAnim_t::noFundsAnimate ) - { - size.x += -2 + 2 * (cos(Compendium_t::PointsAnim_t::animNoFunds * 4 * PI)); - } - txt->setSize(size); - }); + auto txt = const_cast((Field*)(&widget)); + SDL_Rect size = txt->getSize(); + size.x = 0; + if ( Compendium_t::PointsAnim_t::noFundsAnimate ) + { + size.x += -2 + 2 * (cos(Compendium_t::PointsAnim_t::animNoFunds * 4 * PI)); + } + txt->setSize(size); + }); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); - /*if ( keystatus[SDLK_g] ) + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + Compendium_t::PointsAnim_t::noFundsEvent(); + }*/ + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + if ( keystatus[SDLK_LSHIFT] ) { - keystatus[SDLK_g] = 0; - Compendium_t::PointsAnim_t::noFundsEvent(); - }*/ - /*if ( keystatus[SDLK_g] ) + Compendium_t::PointsAnim_t::pointsChangeEvent(-6); + } + else { - keystatus[SDLK_g] = 0; - if ( keystatus[SDLK_LSHIFT] ) - { - Compendium_t::PointsAnim_t::pointsChangeEvent(-6); - } - else - { - Compendium_t::PointsAnim_t::pointsChangeEvent(10); - } - }*/ + Compendium_t::PointsAnim_t::pointsChangeEvent(10); + } + }*/ - if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + { + txt->setText("..."); + } + else + { + if ( !strcmp(txt->getText(), "...") ) { - txt->setText("..."); + Compendium_t::updateLorePointCounts(); + Compendium_t::PointsAnim_t::pointsChangeEvent(0); + } + //int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent); + static ConsoleVariable cvar_lore_point_highlight("/compendium_lore_point_highlight", Vector4{ 63, 64, -48, 0 }); + if ( Compendium_t::PointsAnim_t::noFundsAnimate ) + { + txt->setColor(makeColor(215, 38, 61, 255)); // red } else { - if ( !strcmp(txt->getText(), "...") ) - { - Compendium_t::updateLorePointCounts(); - Compendium_t::PointsAnim_t::pointsChangeEvent(0); - } - //int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements - Compendium_t::lorePointsSpent); - static ConsoleVariable cvar_lore_point_highlight("/compendium_lore_point_highlight", Vector4{ 63, 64, -48, 0 }); - if ( Compendium_t::PointsAnim_t::noFundsAnimate ) - { - txt->setColor(makeColor(215, 38, 61, 255)); // red - } - else - { - txt->setColor(makeColorRGB(158 + cvar_lore_point_highlight->x * Compendium_t::PointsAnim_t::anim, - 146 + cvar_lore_point_highlight->y * Compendium_t::PointsAnim_t::anim, - 132 + cvar_lore_point_highlight->z * Compendium_t::PointsAnim_t::anim)); - } - std::string str = std::to_string(Compendium_t::PointsAnim_t::txtCurrentPoints).c_str(); - txt->setText(str.c_str()); + txt->setColor(makeColorRGB(158 + cvar_lore_point_highlight->x * Compendium_t::PointsAnim_t::anim, + 146 + cvar_lore_point_highlight->y * Compendium_t::PointsAnim_t::anim, + 132 + cvar_lore_point_highlight->z * Compendium_t::PointsAnim_t::anim)); } - }); + std::string str = std::to_string(Compendium_t::PointsAnim_t::txtCurrentPoints).c_str(); + txt->setText(str.c_str()); + } + }); txt = lore_points->addField("lore_points_label", 64); txt->setText(Language::get(6194)); @@ -39056,27 +39202,25 @@ namespace MainMenu { txt->setText(""); txt->setFont("fonts/kongtext.ttf#16#2"); txt->setColor(makeColorRGB(158, 146, 132)); - txt->setSize(SDL_Rect{ lore_points->getSize().x - 32 + 112, lore_points->getSize().y + 16 + offset_y, 92, 24}); + txt->setSize(SDL_Rect{ lore_points->getSize().x - 32 + 112, lore_points->getSize().y + 16 + offset_y, 92, 24 }); txt->setVJustify(Field::justify_t::TOP); txt->setHJustify(Field::justify_t::RIGHT); txt->setText(""); txt->setOntop(true); txt->setTickCallback([](Widget& widget) { Field* txt = static_cast(&widget); - SDL_Rect pos = txt->getSize(); - pos.x = 958; - txt->setSize(pos); if ( auto parent = static_cast(txt->getParent()) ) { if ( auto lore_points_balance = parent->findFrame("lore_points_balance") ) { + SDL_Rect pos = txt->getSize(); + pos.x = lore_points_balance->getSize().x - 32 + 112; + txt->setSize(pos); if ( auto lore_points_current = lore_points_balance->findField("lore_points_current") ) { txt->setColor(lore_points_current->getColor()); - SDL_Rect pos1 = lore_points_current->getSize(); - SDL_Rect pos2 = txt->getSize(); - pos2.x = 958 + pos1.x; - txt->setSize(pos2); + pos.x += lore_points_current->getSize().x; + txt->setSize(pos); } } } @@ -39102,87 +39246,86 @@ namespace MainMenu { txt->setText(Language::get(6209)); } }); - } - //if ( false ) // not in use - //{ - // const int offset_y = 14; - // auto lore_points = window->addFrame("lore_points_total"); - // lore_points->setHollow(true); - // lore_points->setClickable(false); - // lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); - // lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, - // "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); - // lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, - // "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); - // Field* txt = lore_points->addField("lore_points_current", 32); - // txt->setText(""); - // txt->setFont("fonts/kongtext.ttf#16#2"); - // txt->setColor(makeColorRGB(158, 146, 132)); - // txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); - // txt->setVJustify(Field::justify_t::TOP); - // txt->setHJustify(Field::justify_t::RIGHT); - // txt->setTickCallback([](Widget& widget) { - // Field* txt = static_cast(&widget); - // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) - // { - // txt->setText("..."); - // } - // else - // { - // int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements); - // txt->setText(std::to_string(lorePointsCurrent).c_str()); - // } - // }); - - // txt = lore_points->addField("lore_points_total", 32); - // txt->setText("/"); - // txt->setFont("fonts/kongtext.ttf#16#2"); - // txt->setColor(makeColorRGB(255, 255, 255)); - // txt->setSize(SDL_Rect{ 100, 16 + offset_y, 94, 24 }); - // txt->setVJustify(Field::justify_t::TOP); - // txt->setHJustify(Field::justify_t::LEFT); - // txt->setTickCallback([](Widget& widget) { - // Field* txt = static_cast(&widget); - // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) - // { - // txt->setText("..."); - // } - // else - // { - // std::string amt = "/" + std::to_string(Compendium_t::lorePointsAchievementsTotal); - // txt->setText(amt.c_str()); - // } - // }); - - // txt = lore_points->addField("lore_points_label", 64); - // txt->setText("LORE POINTS EARNED"); - // txt->setFont(smallfont_outline); - // txt->setColor(makeColorRGB(192, 192, 192)); - // txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24}); - // txt->setVJustify(Field::justify_t::TOP); - // txt->setHJustify(Field::justify_t::RIGHT); - //} - - { - const int offset_y = 14; - auto completion = window->addFrame("completion"); - completion->setHollow(true); - completion->setClickable(false); - completion->setSize(SDL_Rect{ window->getSize().w - 200, 0, 152, 50 + offset_y }); - completion->addImage(SDL_Rect{ 0, offset_y, 152, 50 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "completion_bg"); - completion->addImage(SDL_Rect{ 10, 10 + offset_y, 38, 28 }, 0xFFFFFFFF, - "*images/ui/Main Menus/AdventureArchives/C_Completion_Icon_00.png", "completion_icon"); - Field* txt = completion->addField("completion_percent", 32); - txt->setText(""); - txt->setFont("fonts/kongtext.ttf#16#2"); - txt->setColor(makeColorRGB(158, 146, 132)); - txt->setSize(SDL_Rect{ 0, 16 + offset_y, 130, 24 }); - txt->setVJustify(Field::justify_t::TOP); - txt->setHJustify(Field::justify_t::RIGHT); - txt->setTickCallback([](Widget& widget) { - Field* txt = static_cast(&widget); + //if ( false ) // not in use + //{ + // const int offset_y = 14; + // auto lore_points = window->addFrame("lore_points_total"); + // lore_points->setHollow(true); + // lore_points->setClickable(false); + // lore_points->setSize(SDL_Rect{ window->getSize().w - 402, 0, 194, 50 + offset_y }); + // lore_points->addImage(SDL_Rect{ 0, offset_y, 194, 50 }, 0xFFFFFFFF, + // "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "lore_points_bg"); + // lore_points->addImage(SDL_Rect{ 10, 10 + offset_y, 28, 28 }, 0xFFFFFFFF, + // "*images/ui/Main Menus/AdventureArchives/C_Lorepoint_Icon_00.png", "lore_points_icon"); + // Field* txt = lore_points->addField("lore_points_current", 32); + // txt->setText(""); + // txt->setFont("fonts/kongtext.ttf#16#2"); + // txt->setColor(makeColorRGB(158, 146, 132)); + // txt->setSize(SDL_Rect{ 0, 16 + offset_y, 100, 24 }); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::RIGHT); + // txt->setTickCallback([](Widget& widget) { + // Field* txt = static_cast(&widget); + // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + // { + // txt->setText("..."); + // } + // else + // { + // int lorePointsCurrent = std::max(0, Compendium_t::lorePointsFromAchievements); + // txt->setText(std::to_string(lorePointsCurrent).c_str()); + // } + // }); + + // txt = lore_points->addField("lore_points_total", 32); + // txt->setText("/"); + // txt->setFont("fonts/kongtext.ttf#16#2"); + // txt->setColor(makeColorRGB(255, 255, 255)); + // txt->setSize(SDL_Rect{ 100, 16 + offset_y, 94, 24 }); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::LEFT); + // txt->setTickCallback([](Widget& widget) { + // Field* txt = static_cast(&widget); + // if ( Compendium_t::AchievementData_t::achievementsNeedFirstData ) + // { + // txt->setText("..."); + // } + // else + // { + // std::string amt = "/" + std::to_string(Compendium_t::lorePointsAchievementsTotal); + // txt->setText(amt.c_str()); + // } + // }); + + // txt = lore_points->addField("lore_points_label", 64); + // txt->setText("LORE POINTS EARNED"); + // txt->setFont(smallfont_outline); + // txt->setColor(makeColorRGB(192, 192, 192)); + // txt->setSize(SDL_Rect{ 0, 1, lore_points->getSize().w - 4, 24}); + // txt->setVJustify(Field::justify_t::TOP); + // txt->setHJustify(Field::justify_t::RIGHT); + //} + + { + const int offset_y = 14; + auto completion = window->addFrame("completion"); + completion->setHollow(true); + completion->setClickable(false); + completion->setSize(SDL_Rect{ lore_points->getSize().x + 202, lore_points->getSize().y, 152, 50 + offset_y}); + completion->addImage(SDL_Rect{ 0, offset_y, 152, 50 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_BG_00.png", "completion_bg"); + completion->addImage(SDL_Rect{ 10, 10 + offset_y, 38, 28 }, 0xFFFFFFFF, + "*images/ui/Main Menus/AdventureArchives/C_Completion_Icon_00.png", "completion_icon"); + Field* txt = completion->addField("completion_percent", 32); + txt->setText(""); + txt->setFont("fonts/kongtext.ttf#16#2"); + txt->setColor(makeColorRGB(158, 146, 132)); + txt->setSize(SDL_Rect{ 0, 16 + offset_y, 130, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); + txt->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); int totalCompletion = Compendium_t::AchievementData_t::completionPercent; totalCompletion += Compendium_t::CompendiumCodex_t::completionPercent; totalCompletion += Compendium_t::CompendiumWorld_t::completionPercent; @@ -39200,42 +39343,17 @@ namespace MainMenu { txt->setColor(makeColorRGB(158 + cvar_completion_point_highlight->x * percent, 146 + cvar_completion_point_highlight->y * percent, 132 + cvar_completion_point_highlight->z * percent)); - }); - - txt = completion->addField("completion_label", 64); - txt->setText(Language::get(6210)); - txt->setFont(smallfont_outline); - txt->setColor(makeColorRGB(192, 192, 192)); - txt->setSize(SDL_Rect{ 0, 1, completion->getSize().w - 4, 24 }); - txt->setVJustify(Field::justify_t::TOP); - txt->setHJustify(Field::justify_t::RIGHT); - } - + }); - (void)createBackWidget(window, [](Button& button) { - soundCancel(); - auto frame = static_cast(button.getParent()); - frame = static_cast(frame->getParent()); - frame = static_cast(frame->getParent()); - frame->removeSelf(); - assert(main_menu_frame); - if ( main_menu_frame ) { - auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); - auto compendium_button = buttons->findButton("Dungeon Compendium"); assert(compendium_button); - compendium_button->select(); + txt = completion->addField("completion_label", 64); + txt->setText(Language::get(6210)); + txt->setFont(smallfont_outline); + txt->setColor(makeColorRGB(192, 192, 192)); + txt->setSize(SDL_Rect{ 0, 1, completion->getSize().w - 4, 24 }); + txt->setVJustify(Field::justify_t::TOP); + txt->setHJustify(Field::justify_t::RIGHT); } - }/*, SDL_Rect{ -4, -4, 0, 0 }*/); - - auto background = window->addImage( - SDL_Rect{ - *cvar_compendium_book_x + (Frame::virtualScreenX - 958) / 2, - (Frame::virtualScreenY - 598) / 2 + 1, - 958, - 598 }, - 0xffffffff, - "*images/ui/Main Menus/AdventureArchives/C_Window_BG_01.png", - "background" - ); + } { static auto constexpr tabTextColorActive = makeColorRGB(220, 178, 113); @@ -39269,6 +39387,17 @@ namespace MainMenu { button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensInactive_00.png"); button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_DenizensInactiveHi_00.png"); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); { @@ -39417,6 +39546,17 @@ namespace MainMenu { button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsInactive_00.png"); button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_ItemsInactiveHi_00.png"); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); //tab->setDrawCallback([](const Widget& widget, SDL_Rect pos) { // if ( !main_menu_frame ) { @@ -39541,6 +39681,17 @@ namespace MainMenu { button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicInactive_00.png"); button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_MagicInactiveHi_00.png"); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); tab_title = window->addField(tab->getName(), 32); @@ -39637,6 +39788,17 @@ namespace MainMenu { button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldInactive_00.png"); button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_WorldInactiveHi_00.png"); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); tab_title = window->addField(tab->getName(), 32); @@ -39734,6 +39896,17 @@ namespace MainMenu { button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexInactive_00.png"); button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_CodexInactiveHi_00.png"); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); tab_title = window->addField(tab->getName(), 32); @@ -39820,16 +39993,27 @@ namespace MainMenu { tab->setTickCallback([](Widget& widget) { auto button = static_cast(&widget); - if ( compendium_current == button->getName() ) - { - button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); - button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); - } - else - { - button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactive_00.png"); - button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactiveHi_00.png"); - } + if ( compendium_current == button->getName() ) + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsHi_00.png"); + } + else + { + button->setBackground("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactive_00.png"); + button->setBackgroundHighlighted("*images/ui/Main Menus/AdventureArchives/A_BMark_AchievementsInactiveHi_00.png"); + } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); { @@ -40266,6 +40450,18 @@ namespace MainMenu { frame->setAllowScrollBinds(false); } + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } + if ( compendium_current == "achievements" ) { frame->addWidgetAction("MenuLeft", "achievements_page_prev"); @@ -40405,6 +40601,18 @@ namespace MainMenu { } } } + + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); } @@ -40901,6 +41109,7 @@ namespace MainMenu { page_right_unlock_btn->setWidgetDown("contents"); page_right_unlock_btn->addWidgetAction("MenuConfirm", "FraggleMaggleStiggleWortz"); // some garbage so that this glyph isn't auto-bound page_right_unlock_btn->setMenuConfirmControlType(0); + page_right_unlock_btn->setHideKeyboardGlyphs(false); page_right_unlock_btn->setButtonsOffset(SDL_Rect{ 0, -20, 0, 0 }); page_right_unlock_btn->setSelectorOffset(SDL_Rect{ 3, 9, -3, -11 }); page_right_unlock_btn->addWidgetAction("MenuPageLeft", "tab_left"); @@ -40909,6 +41118,8 @@ namespace MainMenu { page_right_unlock_btn->addWidgetAction("MenuAlt1", "page_right_unlock_btn"); page_right_unlock_btn->setCallback([](Button& button) { compendiumRevealSection(&button); + Input& input = Input::inputs[getMenuOwner()]; + input.consumeBindingsSharedWithBinding("MenuStart"); }); page_right_unlock_btn->setTickCallback([](Widget& widget) { auto btn = static_cast(&widget); @@ -40937,6 +41148,18 @@ namespace MainMenu { } } } + + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } }); /*auto reveal_unlock_badge = page_right_unlock->addImage(SDL_Rect{ 376 - 74, 28, 56, 62 }, @@ -41094,6 +41317,19 @@ namespace MainMenu { { widget.addWidgetAction("MenuCancel", "right_back_button"); } + + Input& input = Input::inputs[getMenuOwner()]; + if ( input.input("MenuStart").isBindingUsingKeyboard() ) + { + widget.removeWidgetAction("MenuAlt1"); + widget.addWidgetAction("MenuStart", "page_right_unlock_btn"); + } + else + { + widget.removeWidgetAction("MenuStart"); + widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); + } + auto slider = static_cast(&widget); if ( auto frame = static_cast(widget.getParent()) ) { From b27d4daba1abc7812f92e7e9164f194f0c10e57d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 04:54:13 +1000 Subject: [PATCH 086/244] * /cleanfloor command --- src/interface/consolecommand.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 674fea594..4fc0a22c8 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -1417,6 +1417,34 @@ namespace ConsoleCommands { } }); + static ConsoleCommand ccmd_cleanfloor("/cleanfloor", "remove floor items (cheat)", []CCMD{ + if ( !(svFlags & SV_FLAG_CHEATS) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); + return; + } + if ( multiplayer == CLIENT ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(284)); + } + else + { + int c = 0; + node_t* node,* nextnode; + for ( node = map.entities->first; node != NULL; node = nextnode ) + { + nextnode = node->next; + Entity* entity = (Entity*)node->element; + if ( entity->behavior == &actItem ) + { + list_RemoveNode(entity->mynode); + c++; + } + } + messagePlayer(clientnum, MESSAGE_MISC, "Cleared %d items", c); + } + }); + static void suicide(int player) { if (player < 0 || player >= MAXPLAYERS) { return; From 3b7afccacb92a15710c1e3066c43d143ca941890 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 05:58:16 +1000 Subject: [PATCH 087/244] * slime business --- src/actgib.cpp | 26 +- src/actsink.cpp | 3 +- src/collision.cpp | 102 +++++--- src/entity.cpp | 65 ++++- src/entity.hpp | 8 + src/entity_editor.cpp | 4 + src/files.cpp | 6 +- src/game.cpp | 17 +- src/interface/interface.cpp | 8 +- src/menu.cpp | 2 + src/monster.hpp | 11 +- src/monster_slime.cpp | 464 ++++++++++++++++++++++++++++++++---- src/net.cpp | 28 ++- src/paths.cpp | 26 +- src/stat_shared.cpp | 32 +-- 15 files changed, 684 insertions(+), 118 deletions(-) diff --git a/src/actgib.cpp b/src/actgib.cpp index 0db72e784..33672c4c1 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -277,17 +277,35 @@ Entity* spawnGib(Entity* parentent, int customGibSprite) gibsprite = 211; break; case 3: - if ( parentent->sprite == 210 || parentent->sprite >= 1113 ) + { + std::string color = MonsterData_t::getKeyFromSprite(parentent->sprite, SLIME); + if ( color == "slime green" ) { - // green blood + // green blood gibsprite = 211; } - else + else if ( color == "slime blue" ) { - // blue blood + // blue blood gibsprite = 215; } + else if ( color == "slime red" ) + { + // todo blood + gibsprite = 1401; + } + else if ( color == "slime tar" ) + { + // todo blood + gibsprite = 1402; + } + else if ( color == "slime metal" ) + { + // todo blood + gibsprite = 1403; + } break; + } case 4: gibsprite = 683; break; diff --git a/src/actsink.cpp b/src/actsink.cpp index 2938d0618..01d313365 100644 --- a/src/actsink.cpp +++ b/src/actsink.cpp @@ -182,10 +182,9 @@ void actSink(Entity* my) if ( monster ) { monster->seedEntityRNG(rng.getU32()); + slimeSetType(monster, monster->getStats(), true, &rng); Uint32 color = makeColorRGB(255, 128, 0); messagePlayerColor(i, MESSAGE_HINT, color, Language::get(582)); - Stat* monsterStats = monster->getStats(); - monsterStats->LVL = 4; Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_SINKS_SLIMES, "sink", 1); } break; diff --git a/src/collision.cpp b/src/collision.cpp index 01bd9b420..baa1593cd 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -379,8 +379,10 @@ bool entityInsideTile(Entity* entity, int x, int y, int z, bool checkSafeTiles) return true; } if (entity && entity->behavior == &actMonster) { - if (swimmingtiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] || - lavatiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]]) + bool waterWalking = entity->isWaterWalking(); + bool lavaWalking = entity->isLavaWalking(); + if ((swimmingtiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] && !waterWalking) || + (lavatiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] && !lavaWalking)) { return true; } @@ -543,6 +545,9 @@ int barony_clear(real_t tx, real_t ty, Entity* my) } } + bool waterWalking = my && my->isWaterWalking(); + bool lavaWalking = my && my->isLavaWalking(); + bool reduceCollisionSize = false; bool tryReduceCollisionSize = false; Entity* parent = nullptr; @@ -608,7 +613,8 @@ int barony_clear(real_t tx, real_t ty, Entity* my) } if ( !levitating && (!map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] - || ((swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] || lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]]) + || (((swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !waterWalking) + || (lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !lavaWalking)) && isMonster)) ) { // no floor @@ -740,6 +746,22 @@ int barony_clear(real_t tx, real_t ty, Entity* my) } else if ( multiplayer != CLIENT && tryReduceCollisionSize ) { + if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) + { + if ( my->actmagicEmitter > 0 ) + { + auto& emitterHit = particleTimerEmitterHitEntities[my->actmagicEmitter]; + auto find = emitterHit.find(entity->getUID()); + if ( find != emitterHit.end() ) + { + if ( find->second.hits >= 3 || (ticks - find->second.tick) < 5 ) + { + continue; + } + } + } + } + if ( parent && parentStats && yourStats ) { reduceCollisionSize = useSmallCollision(*parent, *parentStats, *entity, *yourStats); @@ -1348,18 +1370,25 @@ real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, d = 0; Stat* stats = nullptr; + bool waterWalking = my && my->isWaterWalking(); + bool lavaWalking = my && my->isLavaWalking(); + bool isMonster = false; if ( my ) { - stats = my->getStats(); - if ( stats ) + if ( my->behavior == &actMonster ) { - if ( stats->type == DEVIL ) - { - ground = false; - } - else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT ) + isMonster = true; + stats = my->getStats(); + if ( stats ) { - ground = false; + if ( stats->type == DEVIL ) + { + ground = false; + } + else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT ) + { + ground = false; + } } } } @@ -1429,14 +1458,9 @@ real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, } if ( ground ) { - bool isMonster = false; - if ( my ) - if ( my->behavior == &actMonster ) - { - isMonster = true; - } if ( !map.tiles[index] - || ((swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) && isMonster) ) + || (((swimmingtiles[map.tiles[index]] && !waterWalking) + || (lavatiles[map.tiles[index]] && !lavaWalking)) && isMonster) ) { hit.x = ix; hit.y = iy; @@ -1511,7 +1535,7 @@ real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, return range; } -real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target ) +real_t lineTraceTarget(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target) { int posx, posy; real_t fracx, fracy; @@ -1534,12 +1558,12 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r inx = posx; iny = posy; arx = 0; - if (rx) + if ( rx ) { arx = 1.0 / fabs(rx); } ary = 0; - if (ry) + if ( ry ) { ary = 1.0 / fabs(ry); } @@ -1547,22 +1571,22 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r dval0 = 1e32; dincy = 0; dval1 = 1e32; - if (rx < 0) + if ( rx < 0 ) { dincx = -1; dval0 = fracx * arx; } - else if (rx > 0) + else if ( rx > 0 ) { dincx = 1; dval0 = (1.0 - fracx) * arx; } - if (ry < 0) + if ( ry < 0 ) { dincy = -1; dval1 = fracy * ary; } - else if (ry > 0) + else if ( ry > 0 ) { dincy = 1; dval1 = (1.0 - fracy) * ary; @@ -1571,6 +1595,16 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r Entity* entity = findEntityInLine(my, x1, y1, angle, entities, target); + bool isMonster = false; + bool waterWalking = my && my->isWaterWalking(); + bool lavaWalking = my && my->isLavaWalking(); + if ( my ) + { + if ( my->behavior == &actMonster ) + { + isMonster = true; + } + } // trace the line while ( d < range ) { @@ -1609,14 +1643,8 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r } if ( ground ) { - bool isMonster = false; - if ( my ) - if ( my->behavior == &actMonster ) - { - isMonster = true; - } if ( !map.tiles[index] - || ((swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) && isMonster) ) + || (((swimmingtiles[map.tiles[index]] && waterWalking) || (lavatiles[map.tiles[index]] && lavaWalking)) && isMonster) ) { hit.x = ix; hit.y = iy; @@ -1702,9 +1730,9 @@ real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t r int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList, bool checkWalls, bool checkFloor) { - node_t* node; - Entity* entity; - Stat* stats; + node_t* node = nullptr; + Entity* entity = nullptr; + Stat* stats = nullptr; bool levitating = false; // get levitation status @@ -1742,9 +1770,11 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity isMonster = true; } } + bool waterWalking = my && my->isWaterWalking(); + bool lavaWalking = my && my->isLavaWalking(); if ( !levitating && ((!map.tiles[index] && checkFloor) - || ( (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) + || ( ((swimmingtiles[map.tiles[index]] && !waterWalking) || (lavatiles[map.tiles[index]] && !lavaWalking)) && isMonster) ) ) // no floor { return 1; // if there's no floor, or either water/lava then a non-levitating monster sees obstacle. diff --git a/src/entity.cpp b/src/entity.cpp index 1f8aff97e..f7c49ca0e 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -109,6 +109,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli monsterWeaponYaw(fskill[5]), monsterShadowInitialMimic(skill[34]), monsterShadowDontChangeName(skill[35]), + monsterSlimeLastAttack(skill[34]), monsterLichFireMeleeSeq(skill[34]), monsterLichFireMeleePrev(skill[35]), monsterLichIceCastSeq(skill[34]), @@ -328,6 +329,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicOrbitStationaryX(fskill[4]), actmagicOrbitStationaryY(fskill[5]), actmagicOrbitStationaryCurrentDist(fskill[6]), + actmagicSprayGravity(fskill[7]), actmagicOrbitStationaryHitTarget(skill[14]), actmagicOrbitHitTargetUID1(skill[15]), actmagicOrbitHitTargetUID2(skill[16]), @@ -340,6 +342,8 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicTinkerTrapFriendlyFire(skill[23]), actmagicReflectionCount(skill[25]), actmagicFromSpellbook(skill[26]), + actmagicSpray(skill[27]), + actmagicEmitter(skill[29]), goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), @@ -6442,6 +6446,57 @@ bool Entity::isBlind() return false; } +bool Entity::isWaterWalking() const +{ + if ( behavior == &actMonster ) + { + if ( Stat* stats = getStats() ) + { + if ( stats->shoes && stats->shoes->type == IRON_BOOTS_WATERWALKING ) + { + return true; + } + if ( stats->type == SLIME ) + { + std::string res = stats->getAttribute("slime_type"); + if ( res == "slime green" + || res == "slime blue" + || res == "slime red" + || res == "slime tar" + || res == "slime metal" ) + { + return true; + } + } + } + } +} +bool Entity::isLavaWalking() const +{ + if ( behavior == &actMonster ) + { + if ( Stat* stats = getStats() ) + { + if ( stats->shoes && stats->shoes->type == IRON_BOOTS_WATERWALKING ) + { + return true; + } + if ( stats->type == SLIME ) + { + std::string res = stats->getAttribute("slime_type"); + if ( res == "slime green" + || res == "slime blue" + || res == "slime red" + || res == "slime tar" + || res == "slime metal" ) + { + return true; + } + } + } + } +} + /*------------------------------------------------------------------------------- Entity::isInvisible @@ -8380,10 +8435,8 @@ void Entity::attack(int pose, int charge, Entity* target) { auto& rng = hit.entity->entity_rng ? *hit.entity->entity_rng : local_rng; monster->seedEntityRNG(rng.getU32()); - + slimeSetType(monster, monster->getStats(), true, &rng); messagePlayer(player, MESSAGE_HINT, Language::get(582)); - Stat* monsterStats = monster->getStats(); - monsterStats->LVL = 4; if ( behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdateWorld(skill[2], Compendium_t::CPDM_SINKS_SLIMES, "sink", 1); @@ -14948,7 +15001,11 @@ int Entity::getAttackPose() const else { const auto type = myStats->type; - if (type == KOBOLD || type == AUTOMATON || + if ( myStats->type == SLIME && this->monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY ) + { + pose = MONSTER_POSE_MAGIC_WINDUP2; + } + else if (type == KOBOLD || type == AUTOMATON || type == GOATMAN || type == INSECTOID || type == INCUBUS || type == VAMPIRE || type == HUMAN || type == GOBLIN || diff --git a/src/entity.hpp b/src/entity.hpp index 9c657429c..90c52edd4 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -263,6 +263,9 @@ class Entity Sint32& monsterShadowInitialMimic; //skill[34]. 0 = false, 1 = true. Sint32& monsterShadowDontChangeName; //skill[35]. 0 = false, 1 = true. Doesn't change name in its mimic if = 1. + //--PUBLIC MONSTER SLIME SKILLS-- + Sint32& monsterSlimeLastAttack; // skill[34] + //--PUBLIC MONSTER LICH SKILLS-- Sint32& monsterLichFireMeleeSeq; //skill[34] Sint32& monsterLichFireMeleePrev; //skill[35] @@ -506,6 +509,7 @@ class Entity real_t& actmagicOrbitStationaryX; // fskill[4] real_t& actmagicOrbitStationaryY; // fskill[5] real_t& actmagicOrbitStationaryCurrentDist; // fskill[6] + real_t& actmagicSprayGravity; // fskill[7] Sint32& actmagicOrbitStationaryHitTarget; // skill[14] Sint32& actmagicOrbitHitTargetUID1; // skill[15] Sint32& actmagicOrbitHitTargetUID2; // skill[16] @@ -517,6 +521,8 @@ class Entity Sint32& actmagicTinkerTrapFriendlyFire; // skill[23] Sint32& actmagicReflectionCount; // skill[25] Sint32& actmagicFromSpellbook; // skill[26] + Sint32& actmagicSpray; // skill[27] + Sint32& actmagicEmitter; // skill[29] //--PUBLIC GOLD SKILLS-- Sint32& goldAmount; //skill[0] @@ -640,6 +646,8 @@ class Entity Sint32 getRangedAttack(); Sint32 getThrownAttack(); bool isBlind(); + bool isWaterWalking() const; + bool isLavaWalking() const; bool isInvisible() const; diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index 2d7c3d680..56fe66323 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -75,6 +75,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli monsterWeaponYaw(fskill[5]), monsterShadowInitialMimic(skill[34]), monsterShadowDontChangeName(skill[35]), + monsterSlimeLastAttack(skill[34]), monsterLichFireMeleeSeq(skill[34]), monsterLichFireMeleePrev(skill[35]), monsterLichIceCastSeq(skill[34]), @@ -297,6 +298,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicOrbitStationaryX(fskill[4]), actmagicOrbitStationaryY(fskill[5]), actmagicOrbitStationaryCurrentDist(fskill[6]), + actmagicSprayGravity(fskill[7]), actmagicOrbitStationaryHitTarget(skill[14]), actmagicOrbitHitTargetUID1(skill[15]), actmagicOrbitHitTargetUID2(skill[16]), @@ -309,6 +311,8 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli actmagicTinkerTrapFriendlyFire(skill[23]), actmagicReflectionCount(skill[25]), actmagicFromSpellbook(skill[26]), + actmagicSpray(skill[27]), + actmagicEmitter(skill[29]), goldAmount(skill[0]), goldAmbience(skill[1]), goldSokoban(skill[2]), diff --git a/src/files.cpp b/src/files.cpp index d2f8b3f2b..4cc2b1107 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -195,7 +195,7 @@ std::unordered_map mapHashes = { { "citadel17.lmp", 112499 }, { "citadel17a.lmp", 7279 }, { "citadel17b.lmp", 1729 }, - { "citadel17c.lmp", 6605 }, + { "citadel17c.lmp", 6786 }, { "citadel17d.lmp", 10055 }, { "citadel18.lmp", 31650 }, { "citadel19.lmp", 13068 }, @@ -1184,7 +1184,7 @@ std::unordered_map mapHashes = { { "swamp20.lmp", 33017 }, { "swamp20a.lmp", 4569 }, { "swamp20b.lmp", 10215 }, - { "swamp20c.lmp", 4208 }, + { "swamp20c.lmp", 5654 }, { "swamp20d.lmp", 26165 }, { "swamp20e.lmp", 6173 }, { "swamp21.lmp", 16295 }, @@ -1219,7 +1219,7 @@ std::unordered_map mapHashes = { { "swamp25e.lmp", 5717 }, { "swamp25f.lmp", 10354 }, { "swamp26.lmp", 40227 }, - { "swamp26a.lmp", 4107 }, + { "swamp26a.lmp", 3747 }, { "swamp26b.lmp", 2398 }, { "swamp26c.lmp", 1005 }, { "swamp26d.lmp", 3129 }, diff --git a/src/game.cpp b/src/game.cpp index f409a1995..6c36c78f7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2169,6 +2169,7 @@ void gameLogic(void) } EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); + particleTimerEmitterHitEntities.clear(); achievementObserver.updateData(); @@ -6329,7 +6330,7 @@ static void doConsoleCommands() { bool confirm = false; if (controlEnabled) { if (input.getPlayerControlType() != Input::playerControlType_t::PLAYER_CONTROLLED_BY_KEYBOARD) { - if (command || !intro) { + if (command || !intro ) { if (keystatus[SDLK_RETURN]) { keystatus[SDLK_RETURN] = 0; confirm = true; @@ -6342,16 +6343,28 @@ static void doConsoleCommands() { } if (input.consumeBinaryToggle("Chat")) { input.consumeBindingsSharedWithBinding("Chat"); - if (command || !intro) { + if (command || !intro ) { confirm = true; } } + if ( confirm && !command ) + { + if ( MainMenu::main_menu_frame ) + { + if ( auto compendium = MainMenu::main_menu_frame->findFrame("compendium") ) + { + confirm = false; // stop menuStart triggering console commands + } + } + } + if (input.consumeBinaryToggle("Console Command")) { input.consumeBindingsSharedWithBinding("Console Command"); if (!command) { confirm = true; } } + } if (confirm && !command) // begin a command diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index fbc219a60..30d066fc1 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -11131,7 +11131,13 @@ void EnemyHPDamageBarHandler::EnemyHPDetails::updateWorldCoordinates() worldX -= 5; } } - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == MIMIC ) + if ( entity->behavior == &actMonster + && entity->monsterAttack == MONSTER_POSE_MAGIC_WINDUP2 + && entity->getMonsterTypeFromSprite() == SLIME ) + { + worldZ += entity->focalz / 2; + } + else if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == MIMIC ) { if ( entity->isInertMimic() ) { diff --git a/src/menu.cpp b/src/menu.cpp index 6a5fdb8fc..2e747c1b0 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8543,6 +8543,7 @@ void doNewGame(bool makeHighscore) { EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); PingNetworkStatus_t::reset(); + particleTimerEmitterHitEntities.clear(); bool bOldSecretLevel = secretlevel; int oldCurrentLevel = currentlevel; @@ -9983,6 +9984,7 @@ void doEndgame(bool saveHighscore, bool onServerDisconnect) { } EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); + particleTimerEmitterHitEntities.clear(); PingNetworkStatus_t::reset(); gameModeManager.currentSession.restoreSavedServerFlags(); client_classes[0] = 0; diff --git a/src/monster.hpp b/src/monster.hpp index 09f136ad4..b0dc5e185 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -100,6 +100,9 @@ static std::vector monsterSprites[NUMMONSTERS] = { { 189, 1108, 1109, 1110, 1111, 1112, // blue 210, 1113, 1114, 1115, 1116, 1117, // green + 1380, 1383, 1384, 1385, 1386, 1387, // red + 1381, 1388, 1389, 1390, 1391, 1392, // tar + 1382, 1393, 1394, 1395, 1396, 1397, // metal }, // TROLL @@ -768,7 +771,9 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist); void humanMoveBodyparts(Entity* my, Stat* myStats, double dist); void ratAnimate(Entity* my, double dist); void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist); -void slimeAnimate(Entity* my, double dist); +void slimeSetType(Entity* my, Stat* myStats, bool sink, BaronyRNG* rng); +void slimeSprayAttack(Entity* my); +void slimeAnimate(Entity* my, Stat* myStats, double dist); void scorpionAnimate(Entity* my, double dist); void succubusMoveBodyparts(Entity* my, Stat* myStats, double dist); void trollMoveBodyparts(Entity* my, Stat* myStats, double dist); @@ -903,6 +908,7 @@ static const int MONSTER_SPECIAL_COOLDOWN_VAMPIRE_AURA = 500; static const int MONSTER_SPECIAL_COOLDOWN_VAMPIRE_DRAIN = 300; static const int MONSTER_SPECIAL_COOLDOWN_SUCCUBUS_CHARM = 400; static const int MONSTER_SPECIAL_COOLDOWN_MIMIC_EAT = 500; +static const int MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY = 250; //--monster target search types static const int MONSTER_TARGET_ENEMY = 0; @@ -1013,6 +1019,9 @@ static const int SUCCUBUS_CHARM = 1; //--Spider-- static const int SPIDER_CAST = 1; +//--Slime-- +static const int SLIME_CAST = 1; + //--Shadow-- static const int SHADOW_SPELLCAST = 1; static const int SHADOW_TELEPORT_ONLY = 2; diff --git a/src/monster_slime.cpp b/src/monster_slime.cpp index 903c5936f..fb435429a 100644 --- a/src/monster_slime.cpp +++ b/src/monster_slime.cpp @@ -20,6 +20,106 @@ #include "collision.hpp" #include "prng.hpp" #include "interface/consolecommand.hpp" +#include "magic/magic.hpp" + +int getSlimeFrame(std::string color, int frame) +{ + auto& data = MonsterData_t::monsterDataEntries[SLIME]; + auto find = data.keyToSpriteLookup.find(color); + if ( find == data.keyToSpriteLookup.end() ) { + if ( frame >= data.keyToSpriteLookup["slime green"].size() ) + { + return 0; + } + return data.keyToSpriteLookup["slime green"][frame]; + } + else + { + if ( frame >= find->second.size() ) + { + return 0; + } + return find->second[frame]; + } +} + +void slimeSetType(Entity* my, Stat* myStats, bool sink, BaronyRNG* rng) +{ + if ( !my || !myStats ) { return; } + + std::vector> possibleTypes = { {"slime green", 1} }; + if ( sink ) + { + if ( currentlevel >= 5 ) + { + possibleTypes.push_back({ "slime blue", 2 }); + } + if ( currentlevel >= 10 ) + { + possibleTypes.push_back({"slime red", 3}); + } + if ( currentlevel >= 15 ) + { + possibleTypes.push_back({"slime tar", 4}); + } + if ( currentlevel >= 15 ) + { + possibleTypes.push_back({"slime metal", 5}); + } + } + else + { + if ( currentlevel >= 5 ) + { + possibleTypes.push_back({"slime blue", 2}); + } + if ( currentlevel >= 10 ) + { + possibleTypes.push_back({"slime red", 3}); + } + if ( currentlevel >= 15 ) + { + possibleTypes.push_back({"slime tar", 4}); + } + if ( currentlevel >= 15 ) + { + possibleTypes.push_back({"slime metal", 5}); + } + } + + int roll = rng ? rng->rand() % possibleTypes.size() : local_rng.rand() % possibleTypes.size(); + myStats->setAttribute("slime_type", possibleTypes[roll].first); +} + +void slimeSetStats(Entity& my, Stat& myStats) +{ + myStats.HP = 60; + myStats.STR = 3; + myStats.DEX = -4; + myStats.CON = 3; + myStats.PER = -2; + myStats.LVL = 4; + + auto color = MonsterData_t::getKeyFromSprite(my.sprite, SLIME); + int level = std::max(currentlevel, 0) / LENGTH_OF_LEVEL_REGION; + myStats.LVL += 3 * level; + myStats.STR += 3 * level; + + if ( color == "slime metal" ) + { + myStats.CON = 20 + 5 * level; + myStats.STR += 2 * level; + } + else + { + myStats.CON += 3 * level; + } + myStats.DEX += 2 * level; + myStats.PER += 1 * level; + + myStats.MAXHP = myStats.HP; + myStats.OLDHP = myStats.HP; +} void initSlime(Entity* my, Stat* myStats) { @@ -32,8 +132,14 @@ void initSlime(Entity* my, Stat* myStats) slimeStand = 0; slimeBob = 0.0; - const bool blue = myStats && myStats->LVL == 7; - my->initMonster(blue ? 1108 : 1113); + if ( myStats && myStats->getAttribute("slime_type") != "" ) + { + my->initMonster(getSlimeFrame(myStats->getAttribute("slime_type"), 1)); + } + else + { + my->initMonster(getSlimeFrame("slime green", 1)); + } if ( multiplayer != CLIENT ) { @@ -53,6 +159,25 @@ void initSlime(Entity* my, Stat* myStats) myStats->leader_uid = 0; } + + std::string color = "slime green"; + if ( myStats->getAttribute("slime_type") == "" ) + { + slimeSetType(my, myStats, false, &rng); + } + + if ( myStats->getAttribute("slime_type") != "" ) + { + color = myStats->getAttribute("slime_type"); + } + my->sprite = getSlimeFrame(color, 1); + + // set slime stats + if ( isMonsterStatsDefault(*myStats) ) + { + slimeSetStats(*my, *myStats); + } + // apply random stat increases if set in stat_shared.cpp or editor setRandomMonsterStats(myStats, rng); @@ -94,15 +219,46 @@ void initSlime(Entity* my, Stat* myStats) } } -void slimeAnimate(Entity* my, double dist) +const int slimeSprayDelay = TICKS_PER_SECOND + ((2 * TICKS_PER_SECOND) / 5); +void slimeSprayAttack(Entity* my) +{ + if ( !my ) { return; } + + auto color = MonsterData_t::getKeyFromSprite(my->sprite, SLIME); + + Entity* spellTimer = createParticleTimer(my, 100, -1); + spellTimer->particleTimerPreDelay = slimeSprayDelay; + spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_MAGIC_SPRAY; + spellTimer->particleTimerCountdownSprite = 180 + local_rng.rand() % 5; +} + +void slimeAnimate(Entity* my, Stat* myStats, double dist) { - const bool green = my->sprite == 210 || my->sprite >= 1113; + //const bool green = my->sprite == 210 || my->sprite >= 1113; + auto color = MonsterData_t::getKeyFromSprite(my->sprite, SLIME); + + if ( multiplayer == CLIENT ) + { + if ( my->monsterSlimeLastAttack != 0 ) + { + if ( MONSTER_ATTACK != my->monsterSlimeLastAttack ) + { + MONSTER_ATTACKTIME = 0; + } + } + my->monsterSlimeLastAttack = MONSTER_ATTACK; + } + + my->z = 6; + my->new_z = my->z; + const int frame = TICKS_PER_SECOND / 10; + auto& slimeStand = my->skill[24]; + auto& slimeBob = my->fskill[24]; + auto& slimeJump = my->fskill[25]; // idle & walk cycle if (!MONSTER_ATTACK) { - auto& slimeStand = my->skill[24]; - auto& slimeBob = my->fskill[24]; const real_t squishRate = dist < 0.1 ? 1.5 : 3.0; const real_t squishFactor = dist < 0.1 ? 0.05 : 0.3; const real_t inc = squishRate * (PI / TICKS_PER_SECOND); @@ -112,63 +268,265 @@ void slimeAnimate(Entity* my, double dist) my->scalez = 1.0 + sin(slimeBob) * squishFactor; if (dist < 0.1) { if (slimeStand < frame) { - my->sprite = green ? 1113 : 1108; + my->sprite = getSlimeFrame(color, 1); my->focalz = -.5; ++slimeStand; } else { - my->sprite = green ? 1114 : 1109; + my->sprite = getSlimeFrame(color, 2); my->focalz = -1.5; } } else { if (slimeStand > 0) { - my->sprite = green ? 1113 : 1108; + my->sprite = getSlimeFrame(color, 1); my->focalz = -.5; --slimeStand; } else { - my->sprite = green ? 210 : 189; + my->sprite = getSlimeFrame(color, 0); my->focalz = 0.0; } } } + if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 ) + { + + } + else + { + if ( MONSTER_ATTACK ) + { + my->scalex = 1.0; + my->scaley = 1.0; + my->scalez = 1.0; + } + if ( slimeJump > 0.0 ) { + slimeJump -= 4 * PI / TICKS_PER_SECOND; + if ( slimeJump <= 0.0 ) { + slimeJump = 0.0; + } + } + } + // attack cycle if (MONSTER_ATTACK) { - if (MONSTER_ATTACKTIME == frame * 0) { // frame 1 - my->sprite = green ? 1113 : 1108; - my->scalex = 1.0; - my->scaley = 1.0; - my->scalez = 1.0; - my->focalz = -.5; - } - if (MONSTER_ATTACKTIME == frame * 2) { // frame 2 - my->sprite = green ? 1114 : 1109; - my->focalz = -1.5; - } - if (MONSTER_ATTACKTIME == frame * 4) { // frame 3 - my->sprite = green ? 1115 : 1110; - my->focalz = -3.0; - } - if (MONSTER_ATTACKTIME == frame * 5) { // frame 4 - my->sprite = green ? 1116 : 1111; - my->focalz = -2.5; - const Sint32 temp = MONSTER_ATTACKTIME; - my->attack(1, 0, nullptr); // slop - MONSTER_ATTACKTIME = temp; - } - if (MONSTER_ATTACKTIME == frame * 7) { // frame 5 - my->sprite = green ? 1117 : 1112; - my->focalz = 0.0; - } - if (MONSTER_ATTACKTIME == frame * 8) { // end - my->sprite = green ? 210 : 189; - my->focalz = 0.0; - MONSTER_ATTACK = 0; - MONSTER_ATTACKTIME = 0; - } - else { + bool tickAttackTime = true; + if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 ) + { + my->sprite = getSlimeFrame(color, 0); + if ( MONSTER_ATTACKTIME == 0 ) + { + my->scalex = 1.0; + my->scaley = 1.0; + my->scalez = 1.0; + slimeBob = 0.0; + createParticleDot(my); + if ( multiplayer != CLIENT ) + { + if ( Stat* myStats = my->getStats() ) + { + myStats->EFFECTS[EFF_STUNNED] = true; + myStats->EFFECTS_TIMERS[EFF_STUNNED] = slimeSprayDelay; + } + } + slimeSprayAttack(my); + } + + const real_t squishRate = 3.0; + real_t squishFactor = 0.3; + + const int interval1 = (TICKS_PER_SECOND); + const int interval2 = (TICKS_PER_SECOND) + 8; + const int interval3 = 2 * TICKS_PER_SECOND - 10; + const int interval4 = 2 * TICKS_PER_SECOND + 25; + + if ( MONSTER_ATTACKTIME < interval1 ) + { + my->sprite = getSlimeFrame(color, 2); + const real_t inc = squishRate * 3.0 * (PI / TICKS_PER_SECOND); + squishFactor = 0.15; + slimeBob = fmod(slimeBob + inc, PI * 2); + } + else if ( MONSTER_ATTACKTIME >= interval1 && MONSTER_ATTACKTIME < interval2 ) + { + if ( MONSTER_ATTACKTIME == interval1 ) + { + slimeBob = 0.0; + } + + const real_t inc = (squishRate) * (PI / TICKS_PER_SECOND); + slimeBob = std::min(slimeBob + inc, PI / 2); + /*if ( my->ticks % 2 == 0 ) + { + slimeBob += PI / 32; + }*/ + } + else if ( MONSTER_ATTACKTIME >= interval2 && MONSTER_ATTACKTIME < interval3 ) + { + const real_t inc = (squishRate) * 2 * (PI / TICKS_PER_SECOND); + slimeBob = fmod(slimeBob + inc, PI * 2); + my->sprite = getSlimeFrame(color, 1); + } + else + { + const real_t inc = (squishRate) * (PI / TICKS_PER_SECOND); + slimeBob = fmod(slimeBob + inc, PI * 2); + my->sprite = getSlimeFrame(color, 1); + } + + if ( MONSTER_ATTACKTIME >= interval3 ) + { + if ( MONSTER_ATTACKTIME >= interval3 + 20 ) + { + my->sprite = getSlimeFrame(color, 0); + if ( slimeJump > 0.0 ) { + slimeJump -= 4 * PI / TICKS_PER_SECOND; + if ( slimeJump <= 0.0 ) { + slimeJump = 0.0; + } + } + } + else + { + my->sprite = getSlimeFrame(color, 1); + } + } + else if ( MONSTER_ATTACKTIME >= interval2 ) + { + if ( slimeJump < PI / 2.0 ) { + slimeJump += (PI / TICKS_PER_SECOND) * 2.0; + if ( slimeJump >= PI / 2.0 ) { + slimeJump = PI / 2.0; + } + } + } + + my->scalex = 1.0 - sin(slimeBob) * squishFactor; + my->scaley = 1.0 - sin(slimeBob) * squishFactor; + my->scalez = 1.0 + sin(slimeBob) * squishFactor; + if ( my->sprite == getSlimeFrame(color, 1) ) + { + my->focalz = -.5; + } + else if ( my->sprite == getSlimeFrame(color, 2) ) + { + my->focalz = -1.5; + } + else + { + my->focalz = 0.0; + } + + if ( MONSTER_ATTACKTIME >= interval4 / (monsterGlobalAnimationMultiplier / 10.0) ) + { + //if ( multiplayer != CLIENT ) + //{ + // // swing the arm after we prepped the spell + // my->attack(1, 0, nullptr); + //} + MONSTER_ATTACK = 0; + MONSTER_ATTACKTIME = 0; + } + } + else + { + if (MONSTER_ATTACKTIME == frame * 0) { // frame 1 + my->sprite = getSlimeFrame(color, 1); + my->scalex = 1.0; + my->scaley = 1.0; + my->scalez = 1.0; + my->focalz = -.5; + } + else if (MONSTER_ATTACKTIME == frame * 2) { // frame 2 + my->sprite = getSlimeFrame(color, 2); + my->focalz = -1.5; + } + else if (MONSTER_ATTACKTIME == frame * 4) { // frame 3 + my->sprite = getSlimeFrame(color, 3); + my->focalz = -3.0; + } + else if (MONSTER_ATTACKTIME == frame * 5) { // frame 4 + my->sprite = getSlimeFrame(color, 4); + my->focalz = -2.5; + const Sint32 temp = MONSTER_ATTACKTIME; + my->attack(1, 0, nullptr); // slop + MONSTER_ATTACKTIME = temp; + } + else if (MONSTER_ATTACKTIME == frame * 7) { // frame 5 + my->sprite = getSlimeFrame(color, 5); + my->focalz = 0.0; + } + else if (MONSTER_ATTACKTIME == frame * 8) { // end + my->sprite = getSlimeFrame(color, 0); + my->focalz = 0.0; + MONSTER_ATTACK = 0; + MONSTER_ATTACKTIME = 0; + tickAttackTime = false; + } + } + + if ( tickAttackTime ) + { ++MONSTER_ATTACKTIME; } } + else + { + MONSTER_ATTACKTIME = 0; + } + + if ( MONSTER_ATTACK != MONSTER_POSE_MAGIC_WINDUP2 ) + { + // base focalz + if ( my->sprite == getSlimeFrame(color, 0) ) + { + my->focalz = 0.0; + } + else if ( my->sprite == getSlimeFrame(color, 1) ) + { + my->focalz = -.5; + } + else if ( my->sprite == getSlimeFrame(color, 2) ) + { + my->focalz = -1.5; + } + else if ( my->sprite == getSlimeFrame(color, 3) ) + { + my->focalz = -3.0; + } + else if ( my->sprite == getSlimeFrame(color, 4) ) + { + my->focalz = -2.5; + } + else if ( my->sprite == getSlimeFrame(color, 5) ) + { + my->focalz = 0.0; + } + } + + my->focalz += -sin(slimeJump) * 6.0; + + auto& slimeWaterBob = my->fskill[23]; + bool swimming = false; + if ( !isLevitating(myStats) ) + { + int x = std::min(std::max(0, floor(my->x / 16)), map.width - 1); + int y = std::min(std::max(0, floor(my->y / 16)), map.height - 1); + int index = y * MAPLAYERS + x * MAPLAYERS * map.height; + if ( map.tiles[index] ) + { + if ( swimmingtiles[map.tiles[index]] + || lavatiles[map.tiles[index]] ) + { + slimeWaterBob = sin(((ticks % (TICKS_PER_SECOND * 2)) / ((real_t)TICKS_PER_SECOND * 2.0)) * (2.0 * PI)) * 0.5; + swimming = true; + } + } + } + else + { + slimeWaterBob = 0.0; + } + my->focalz += (swimming && MONSTER_ATTACK == 0) ? (1.0 + slimeWaterBob) : 0.0; } void slimeDie(Entity* my) @@ -179,16 +537,32 @@ void slimeDie(Entity* my) serverSpawnGibForClient(gib); } - if ( my->sprite == 210 || my->sprite >= 1113 ) + auto color = MonsterData_t::getKeyFromSprite(my->sprite, SLIME); + if ( color == "slime green" ) { // green blood my->spawnBlood(212); } - else + else if ( color == "slime blue" ) { // blue blood my->spawnBlood(214); } + else if ( color == "slime red" ) + { + // todo blood + my->spawnBlood(1398); + } + else if ( color == "slime tar" ) + { + // todo blood + my->spawnBlood(1399); + } + else if ( color == "slime metal" ) + { + // todo blood + my->spawnBlood(1400); + } playSoundEntity(my, 69, 64); diff --git a/src/net.cpp b/src/net.cpp index 72841b5f3..cb52e4343 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1679,8 +1679,8 @@ Entity* receiveEntity(Entity* entity) const bool excludeForAnimation = !newentity && entity->behavior == &actMonster && - (monsterType == RAT || monsterType == SLIME || monsterType == SCARAB) && - entity->skill[8]; // MONSTER_ATTACK + (monsterType == SLIME || ((monsterType == RAT || monsterType == SCARAB) && + entity->skill[8])); // MONSTER_ATTACK //if ( Entity::getMonsterTypeFromSprite(entity->sprite) == SPIDER ) //{ @@ -1721,7 +1721,14 @@ Entity* receiveEntity(Entity* entity) //} if (excludeForAnimation) { - entity->sprite = oldSprite; + if ( monsterType == SLIME && Entity::getMonsterTypeFromSprite(oldSprite) != SLIME ) + { + // take this sprite as we had editor data (e.g sprite 10 or 79) + } + else + { + entity->sprite = oldSprite; + } } entity->lastupdate = ticks; @@ -1819,6 +1826,9 @@ void clientActions(Entity* entity) case 214: case 682: case 681: + case 1398: + case 1399: + case 1400: entity->flags[NOUPDATE] = true; break; case 162: @@ -2171,6 +2181,7 @@ static void changeLevel() { } EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); + particleTimerEmitterHitEntities.clear(); // clear follower menu entities. FollowerMenu[clientnum].closeFollowerMenuGUI(true); @@ -2507,6 +2518,17 @@ static std::unordered_map clientPacketHandlers = { tempEntity->flags[INVISIBLE] = (net_packet->data[13] & (1 << 0)) > 0 ? true : false; tempEntity->flags[INVISIBLE_DITHER] = (net_packet->data[13] & (1 << 1)) > 0 ? true : false; } + /*else + { + if ( entity->behavior == &actPlayer ) + { + messagePlayer(clientnum, MESSAGE_DEBUG, "actPlayer !childNode: %d", net_packet->data[8]); + } + else + { + messagePlayer(clientnum, MESSAGE_DEBUG, "!childNode: %d", net_packet->data[8]); + } + }*/ } }}, diff --git a/src/paths.cpp b/src/paths.cpp index 1afbb7423..59920c0c8 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -466,9 +466,11 @@ list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, int* pathMap = (int*) calloc(map.width * map.height, sizeof(int)); int pathMapType = GateGraph::GATE_GRAPH_GROUNDED; + bool waterWalking = my && my->isWaterWalking(); + bool lavaWalking = my && my->isLavaWalking(); if ( !loading ) { - if ( levitating || playerCheckPathToExit ) + if ( levitating || playerCheckPathToExit || waterWalking || lavaWalking ) { memcpy(pathMap, pathMapFlying, map.width * map.height * sizeof(int)); pathMapType = GateGraph::GATE_GRAPH_FLYING; @@ -543,6 +545,28 @@ list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, } } } + else if ( !levitating && (waterWalking || lavaWalking) ) + { + for ( int y = 0; y < map.height; ++y ) + { + for ( int x = 0; x < map.width; ++x ) + { + int index = y * MAPLAYERS + x * MAPLAYERS * map.height; + if ( !map.tiles[index] ) + { + pathMap[y + x * map.height] = 0; + } + else if ( lavatiles[map.tiles[index]] && !lavaWalking ) + { + pathMap[y + x * map.height] = 0; + } + else if ( swimmingtiles[map.tiles[index]] && !waterWalking ) + { + pathMap[y + x * map.height] = 0; + } + } + } + } Uint32 standingOnTrap = 0; // 0 - not checked. for ( auto entityNode = map.entities->first; entityNode != nullptr; entityNode = entityNode->next ) diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index 09ecdd8e3..d81b4f251 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1115,22 +1115,22 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; - if ( stats->LVL >= 7 ) // blue slime - { - stats->HP = 70; - stats->MAXHP = 70; - stats->MP = 70; - stats->MAXMP = 70; - stats->STR = 10; - } - else // green slime - { - stats->STR = 3; - stats->HP = 60; - stats->MAXHP = 60; - stats->MP = 60; - stats->MAXMP = 60; - } + //if ( stats->LVL >= 7 ) // blue slime + //{ + // stats->HP = 70; + // stats->MAXHP = 70; + // stats->MP = 70; + // stats->MAXMP = 70; + // stats->STR = 10; + //} + //else // green slime + //{ + //} + stats->STR = 3; + stats->HP = 60; + stats->MAXHP = 60; + stats->MP = 60; + stats->MAXMP = 60; stats->OLDHP = stats->HP; stats->DEX = -4; stats->CON = 3; From 986a284dbb6ea46e385b68c797a3bed04272e2ea Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 05:58:31 +1000 Subject: [PATCH 088/244] * lang update --- lang/compendium_lang/contents_items.json | 4 ++-- lang/compendium_lang/contents_monsters.json | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lang/compendium_lang/contents_items.json b/lang/compendium_lang/contents_items.json index a57a57296..0960805bc 100644 --- a/lang/compendium_lang/contents_items.json +++ b/lang/compendium_lang/contents_items.json @@ -91,7 +91,6 @@ {"BOOMERANG": "boomerang"}, {"BOWS": "bows"}, {"CHAKRAMS & SHURIKENS": "chakrams and shurikens"}, - {"CHEESE & APPLES": "cheese and apples"}, {"CLOAKS & SHIRTS": "cloaks & clothing"}, {"CREAM PIE": "cream pie"}, {"CROSSBOW": "crossbow"}, @@ -119,9 +118,10 @@ {"MACES": "maces"}, {"MAIL & LORE BOOKS": "mail & lore books"}, {"MASKS & VISORS": "masks & visors"}, - {"MEAT, FISH, & BREAD": "meat, fish, and bread"}, + {"MEALS": "meat, fish, and bread"}, {"MINING PICK": "mining pick"}, {"MIRRORS": "mirrors"}, + {"MORSELS": "cheese and apples"}, {"MYSTIC ORB": "mystic orb"}, {"NOISEMAKER": "noisemaker"}, {"OFFENSIVE POTIONS": "offensive potions"}, diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json index 49e425250..1630f11f8 100644 --- a/lang/compendium_lang/contents_monsters.json +++ b/lang/compendium_lang/contents_monsters.json @@ -12,7 +12,9 @@ {"RAT": "rat"}, {"ALGERNON": "algernon"}, {"SPIDER": "spider"}, + {"CRAB": "crab"}, {"SHELOB": "shelob"}, + {"BUBBLES": "bubbles"}, {"TROLL": "troll"}, {"THUMPUS": "thumpus"}, {"SCORPION": "scorpion"}, @@ -68,8 +70,10 @@ {"BARATHEON": "baratheon"}, {"BARON HERX": "lich"}, {"BRAM KINDLY": "bram kindly"}, + {"BUBBLES": "bubbles"}, {"COCKATRICE": "cockatrice"}, {"CORAL GRIMES": "coral grimes"}, + {"CRAB": "crab"}, {"CRYSTAL GOLEM": "crystalgolem"}, {"DEMON": "demon"}, {"DEU DE'BREAU": "deudebreau"}, From 950dfd24ad3807b995394d0201dff3212e8753d9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 05:58:43 +1000 Subject: [PATCH 089/244] * faster client tooltip requests --- src/actitem.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/actitem.cpp b/src/actitem.cpp index 30af427da..bce55246f 100644 --- a/src/actitem.cpp +++ b/src/actitem.cpp @@ -128,7 +128,26 @@ void actItem(Entity* my) else if ( my->skill[10] == 0 && my->itemReceivedDetailsFromServer == 0 && players[clientnum] ) { // request itemtype and beatitude - if ( ticks % (TICKS_PER_SECOND * 6) == my->getUID() % (TICKS_PER_SECOND * 6) ) + bool requestUpdate = false; + Uint32 syncTick = my->getUID() % (TICKS_PER_SECOND * 6); + Uint32 currentTick = ticks % (TICKS_PER_SECOND * 6); + if ( ITEM_LIFE == 0 ) + { + if ( currentTick < syncTick) + { + if ( syncTick - currentTick >= TICKS_PER_SECOND * 2 ) + { + // if the cycle would request details more than 2 seconds away, request now + requestUpdate = true; + } + } + else if ( currentTick > syncTick ) + { + requestUpdate = true; // more than 2 seconds away + } + } + + if ( (currentTick == syncTick) || requestUpdate ) { strcpy((char*)net_packet->data, "ITMU"); net_packet->data[4] = clientnum; @@ -137,17 +156,6 @@ void actItem(Entity* my) net_packet->address.port = net_server.port; net_packet->len = 9; sendPacketSafe(net_sock, -1, net_packet, 0); - /*for ( node_t* tmpNode = map.creatures->first; tmpNode != nullptr; tmpNode = tmpNode->next ) - { - Entity* follower = (Entity*)tmpNode->element; - if ( follower && players[clientnum]->entity == follower->monsterAllyGetPlayerLeader() ) - { - if ( follower->getMonsterTypeFromSprite() == GYROBOT ) - { - break; - } - } - }*/ } } } From b951c97a966b9357da0a7088d92af62ca5941192 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 05:59:44 +1000 Subject: [PATCH 090/244] * player bodyparts better loading delay --- src/actplayer.cpp | 334 +++++++++++++++++++++++++++++----------------- 1 file changed, 215 insertions(+), 119 deletions(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index b133cb1f2..8b49aef77 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -8940,14 +8940,22 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -8972,14 +8980,22 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9004,14 +9020,22 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9047,14 +9071,22 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9143,14 +9175,22 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9288,19 +9328,27 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) - { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9351,19 +9399,27 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) - { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9405,19 +9461,27 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0))) - { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9496,19 +9560,27 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -9564,19 +9636,27 @@ void actPlayer(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) - { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } @@ -10814,19 +10894,27 @@ void playerAnimateRat(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) - { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } entity->yaw = my->yaw; @@ -10857,19 +10945,27 @@ void playerAnimateSpider(Entity* my) if ( multiplayer == SERVER ) { // update sprites for clients - if ( entity->skill[10] != entity->sprite ) - { - entity->skill[10] = entity->sprite; - serverUpdateEntityBodypart(my, bodypart); - } - if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); - serverUpdateEntityBodypart(my, bodypart); - } - if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) - { - serverUpdateEntityBodypart(my, bodypart); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)) ) + { + entity->skill[11] = ((entity->flags[INVISIBLE] ? 1 : 0) + (entity->flags[INVISIBLE_DITHER] ? 2 : 0)); + updateBodypart = true; + } + if ( PLAYER_ALIVETIME == TICKS_PER_SECOND + bodypart ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } continue; From 0033132484ab26f15e0f4635390c60aa2d26ef73 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 06:00:40 +1000 Subject: [PATCH 091/244] * invis items no activate pressure plates * breakables over pits sometimes have nothing * underworld gibbets increase prio of pit locations --- src/maps.cpp | 30 +++++++++++++++++++----------- src/mechanisms.cpp | 8 ++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index fdcac7347..7ea855864 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4101,8 +4101,8 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( obstacles == 0 ) { - breakableLocations.push(BreakableNode_t(0, x, y, map_rng.rand() % 4, - map_rng.rand() % 2 ? 14 : 40)); // random dir, low prio, hanging cage ids + breakableLocations.push(BreakableNode_t(1, x, y, map_rng.rand() % 4, + map_rng.rand() % 2 ? 14 : 40)); // random dir, hanging cage ids --numOpenAreaBreakables; if ( possiblelocations[y + x * map.height] ) @@ -4400,8 +4400,8 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } else { - int picked = map_rng.discrete(chances.data(), chances.size()); - breakable->colliderDamageTypes = ids[picked]; + int picked = map_rng.discrete(chances.data(), chances.size()); + breakable->colliderDamageTypes = ids[picked]; } Monster monsterEvent = NOTHING; @@ -4428,15 +4428,23 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int index = (y) * MAPLAYERS + (x) * MAPLAYERS * map.height; - if ( (breakableMonsters < 2 && monsterEvent != NOTHING && map_rng.rand() % 10 == 0) ) // 10% monster inside + if ( !map.tiles[index] && map_rng.rand() % 2 == 1 ) { - if ( map_rng.rand() % 2 == 0 ) - { - breakable->colliderHideMonster = monsterEvent; - } - else + // nothing over pits 50% + } + else if ( (breakableMonsters < 2 && monsterEvent != NOTHING && map_rng.rand() % 10 == 0) + && map.monsterexcludelocations[x + y * map.width] == false ) // 10% monster inside + { + if ( (svFlags & SV_FLAG_TRAPS) ) { - breakable->colliderHideMonster = 1000 + monsterEvent; + if ( map_rng.rand() % 2 == 0 ) + { + breakable->colliderHideMonster = monsterEvent; + } + else + { + breakable->colliderHideMonster = 1000 + monsterEvent; + } } ++breakableMonsters; } diff --git a/src/mechanisms.cpp b/src/mechanisms.cpp index 7e196b3d2..9e5dc45b5 100644 --- a/src/mechanisms.cpp +++ b/src/mechanisms.cpp @@ -421,6 +421,10 @@ void actTrap(Entity* my) for ( node = currentList->first; node != nullptr; node = node->next ) { entity = (Entity*)node->element; + if ( entity->behavior == &actItem && entity->flags[INVISIBLE] ) + { + continue; + } if ( entity->behavior == &actPlayer || entity->behavior == &actItem || (entity->behavior == &actMonster && !entity->isInertMimic()) || entity->behavior == &actBoulder || entity->behavior == &actBomb || entity->behavior == &actDecoyBox ) @@ -539,6 +543,10 @@ void actTrapPermanent(Entity* my) for ( node = currentList->first; node != nullptr; node = node->next ) { entity = (Entity*)node->element; + if ( entity->behavior == &actItem && entity->flags[INVISIBLE] ) + { + continue; + } if ( entity->behavior == &actPlayer || entity->behavior == &actItem || (entity->behavior == &actMonster && !entity->isInertMimic()) || entity->behavior == &actBoulder || entity->behavior == &actBomb || entity->behavior == &actDecoyBox ) From ce941690763f28bce03ed5702eb2a7d123c449c1 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 28 Aug 2024 06:09:47 +1000 Subject: [PATCH 092/244] * slime actMonster code, maigc attack --- src/actmonster.cpp | 44 ++++++++---- src/magic/actmagic.cpp | 157 ++++++++++++++++++++++++++++++++++++++--- src/magic/magic.cpp | 2 + src/magic/magic.hpp | 9 +++ 4 files changed, 189 insertions(+), 23 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index c3176bb9a..ae56c49e4 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -165,7 +165,7 @@ std::string getMonsterLocalizedName(Monster creature) { if ( creature < KOBOLD ) { - if (creature == SPIDER && arachnophobia_filter) { + if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) { return Language::get(102); } else { return Language::get(90 + creature); @@ -182,7 +182,7 @@ std::string getMonsterLocalizedPlural(Monster creature) { if ( creature < KOBOLD ) { - if (creature == SPIDER && arachnophobia_filter) { + if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter))) { return Language::get(123); } else { return Language::get(111 + creature); @@ -198,7 +198,7 @@ std::string getMonsterLocalizedInjury(Monster creature) { if ( creature < KOBOLD ) { - if (creature == SPIDER && arachnophobia_filter) { + if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) { return Language::get(144); } else { return Language::get(132 + creature); @@ -1194,13 +1194,6 @@ Entity* summonMonsterNoSmoke(Monster creature, long x, long y, bool forceLocatio end: - if ( creature == SLIME ) - { - if ( multiplayer != CLIENT ) - { - myStats->LVL = 7; - } - } nummonsters++; @@ -2249,7 +2242,7 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist) case HUMAN: humanMoveBodyparts(my, myStats, dist); break; case RAT: ratAnimate(my, dist); break; case GOBLIN: goblinMoveBodyparts(my, myStats, dist); break; - case SLIME: slimeAnimate(my, dist); break; + case SLIME: slimeAnimate(my, myStats, dist); break; case TROLL: trollMoveBodyparts(my, myStats, dist); break; case SPIDER: spiderMoveBodyparts(my, myStats, dist); break; case GHOUL: ghoulMoveBodyparts(my, myStats, dist); break; @@ -5717,7 +5710,21 @@ void actMonster(Entity* my) { dir += PI * 2; } + if ( myStats->type == SLIME ) + { + if ( my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP2 ) + { + my->yaw -= dir / 8; + } + else + { my->yaw -= dir / 2; + } + } + else + { + my->yaw -= dir / 2; + } while ( my->yaw < 0 ) { my->yaw += 2 * PI; @@ -8803,7 +8810,11 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) // check again for the target in attack range. return the result into hit.entity. double newTangent = atan2(target->y - this->y, target->x - this->x); - if ( lichRangeCheckOverride ) + if ( myStats->type == SLIME && monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY ) + { + hit.entity = uidToEntity(monsterTarget); + } + else if ( lichRangeCheckOverride ) { hit.entity = uidToEntity(monsterTarget); } @@ -9890,6 +9901,15 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di deinitSuccess = true; } break; + case SLIME: + if ( monsterSpecialState == SLIME_CAST || forceDeinit ) + { + monsterSpecialState = 0; + serverUpdateEntitySkill(this, 33); // for clients to handle animation + shouldAttack = false; + deinitSuccess = true; + } + break; case SPIDER: if ( monsterSpecialState == SPIDER_CAST || forceDeinit ) { diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index d43ef5776..c82c92371 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -858,7 +858,49 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Sint32 entityHealth = 0; double dist = 0.f; bool hitFromAbove = false; - if ( (parent && parent->behavior == &actMagicTrapCeiling) || my->actmagicIsVertical == MAGIC_ISVERTICAL_Z ) + if ( my->actmagicSpray == 1 ) + { + my->vel_z += my->actmagicSprayGravity; + my->z += my->vel_z; + my->roll += 0.1; + dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my); //normal flat projectiles + + if ( my->z < 8.0 ) + { + // if we didn't hit the floor, process normal horizontal movement collision if we aren't too high + if ( dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y) ) + { + hitFromAbove = true; + + if ( my->actmagicEmitter > 0 ) + { + if ( hit.entity && (Sint32)(hit.entity->getUID()) >= 0 ) + { + auto& emitterHit = particleTimerEmitterHitEntities[my->actmagicEmitter]; + auto find = emitterHit.find(hit.entity->getUID()); + if ( find != emitterHit.end() ) + { + find->second.hits++; + find->second.tick = ticks; + } + else + { + auto& entry = emitterHit[hit.entity->getUID()]; + entry.hits = 1; + entry.tick = ticks; + } + } + } + } + } + else + { + my->removeLightField(); + list_RemoveNode(my->mynode); + return; + } + } + else if ( (parent && parent->behavior == &actMagicTrapCeiling) || my->actmagicIsVertical == MAGIC_ISVERTICAL_Z ) { // moving vertically. my->z += my->vel_z; @@ -987,7 +1029,8 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } - if ( hitFromAbove || (my->actmagicIsVertical != MAGIC_ISVERTICAL_XYZ && dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y)) ) + if ( hitFromAbove + || (my->actmagicIsVertical != MAGIC_ISVERTICAL_XYZ && my->actmagicSpray != 1 && dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y)) ) { node = element->elements.first; //element = (spellElement_t *) element->elements->first->element; @@ -2000,7 +2043,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } else if (!strcmp(element->element_internal_name, spellElement_fire.element_internal_name)) { - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) && my->actmagicSpray != 1 ) { spawnExplosion(my->x, my->y, my->z); } @@ -2020,7 +2063,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2194,7 +2237,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2217,7 +2260,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2239,7 +2282,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2319,7 +2362,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) ) + if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -3831,6 +3874,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } return; } + + if ( my->actmagicSpray == 1 ) + { + my->vel_x = my->vel_x * .95; + my->vel_y = my->vel_y * .95; + } } //Go down two levels to the next element. This will need to get re-written shortly. @@ -3877,7 +3926,10 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } // spawn particles - if ( *cvar_magic_fx_use_vismap && !intro ) + if ( my->actmagicSpray == 1 ) + { + } + else if ( *cvar_magic_fx_use_vismap && !intro ) { int x = my->x / 16.0; int y = my->y / 16.0; @@ -4954,11 +5006,11 @@ Entity* createParticleTimer(Entity* parent, int duration, int sprite) entity->flags[INVISIBLE] = true; entity->flags[PASSABLE] = true; entity->flags[NOUPDATE] = true; - if ( multiplayer != CLIENT ) + /*if ( multiplayer != CLIENT ) { entity_uids--; } - entity->setUID(-3); + entity->setUID(-3);*/ return entity; } @@ -5524,6 +5576,89 @@ void actParticleTimer(Entity* my) createParticleDropRising(my, my->particleTimerCountdownSprite, 1.0); my->particleTimerCountdownAction = 0; } + else if ( my->particleTimerCountdownAction == PARTICLE_TIMER_ACTION_MAGIC_SPRAY ) + { + Entity* parent = uidToEntity(my->parent); + if ( !parent ) + { + PARTICLE_LIFE = 0; + } + else + { + my->x = parent->x; + my->y = parent->y; + my->yaw = parent->yaw; + + Entity* entity = nullptr; + if ( multiplayer != CLIENT && my->ticks % 2 == 0 ) + { + // damage frames + entity = newEntity(my->particleTimerCountdownSprite, 1, map.entities, nullptr); + entity->behavior = &actMagicMissile; + } + else + { + entity = multiplayer == CLIENT ? spawnGibClient(0, 0, 0, -1) : spawnGib(my); + } + if ( entity ) + { + entity->sprite = my->particleTimerCountdownSprite; + entity->x = parent->x; + entity->y = parent->y; + if ( parent->behavior == &actMonster && parent->getMonsterTypeFromSprite() == SLIME ) + { + entity->z = -2 + parent->z + parent->focalz; + } + else + { + entity->z = parent->z; + } + entity->parent = parent->getUID(); + + entity->ditheringDisabled = true; + entity->flags[SPRITE] = true; + entity->flags[INVISIBLE] = false; + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[UNCLICKABLE] = true; + + entity->sizex = 2; + entity->sizey = 2; + entity->yaw = my->yaw - 0.2 + (local_rng.rand() % 20) * 0.02; + entity->pitch = (local_rng.rand() % 360) * PI / 180.0; + entity->roll = (local_rng.rand() % 360) * PI / 180.0; + double vel = (20 + (local_rng.rand() % 5)) / 10.f; + entity->vel_x = vel * cos(entity->yaw) + parent->vel_x; + entity->vel_y = vel * sin(entity->yaw) + parent->vel_y; + entity->vel_z = -.5; + if ( entity->behavior == &actMagicMissile ) + { + spell_t* spell = getSpellFromID(SPELL_LIGHTNING); + entity->skill[4] = 0; // life start + entity->skill[5] = TICKS_PER_SECOND; //lifetime + entity->actmagicSpray = 1; + entity->actmagicSprayGravity = 0.04; + entity->actmagicEmitter = my->getUID(); + node_t* node = list_AddNodeFirst(&entity->children); + node->element = copySpell(spell); + ((spell_t*)node->element)->caster = parent->getUID(); + node_t* elementNode = ((spell_t*)node->element)->elements.first; + spellElement_t* element = (spellElement_t*)elementNode->element; + { + elementNode = element->elements.first; + element = (spellElement_t*)elementNode->element; + element->damage = 2; + } + + node->deconstructor = &spellDeconstructor; + node->size = sizeof(spell_t); + + --entity_uids; + entity->setUID(-3); + } + } + } + } } else { diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 5fd7c6953..7fce313cb 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -25,6 +25,8 @@ #include "../prng.hpp" #include "../mod_tools.hpp" +std::map> particleTimerEmitterHitEntities; + void freeSpells() { list_FreeAll(&spell_forcebolt.elements); diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index 127450920..059c2c55e 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -147,6 +147,7 @@ static const int PARTICLE_EFFECT_DEVIL_SUMMON_MONSTER = 24; static const int PARTICLE_EFFECT_SHATTERED_GEM = 25; static const int PARTICLE_EFFECT_SHRINE_TELEPORT = 26; static const int PARTICLE_EFFECT_GHOST_TELEPORT = 27; +static const int PARTICLE_EFFECT_MAGIC_SPRAY = 28; // actmagicIsVertical constants static const int MAGIC_ISVERTICAL_NONE = 0; @@ -159,6 +160,14 @@ static const int PARTICLE_TIMER_ACTION_SPAWN_PORTAL = 2; static const int PARTICLE_TIMER_ACTION_SUMMON_MONSTER = 3; static const int PARTICLE_TIMER_ACTION_SPELL_SUMMON = 4; static const int PARTICLE_TIMER_ACTION_DEVIL_SUMMON_MONSTER = 5; +static const int PARTICLE_TIMER_ACTION_MAGIC_SPRAY = 6; + +struct ParticleEmitterHit_t +{ + Uint32 tick = 0; + int hits = 0; +}; +extern std::map> particleTimerEmitterHitEntities; bool addSpell(int spell, int player, bool ignoreSkill = false); //Adds a spell to the client's spell list. Note: Do not use this to add custom spells. From 8f93c70f4328e574bff72ef7456e68b808e5d510 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 30 Aug 2024 16:11:47 +1000 Subject: [PATCH 093/244] * fix updateeffects being wrong for client since numeffects update --- src/net.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index cb52e4343..ad49d5b51 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -950,8 +950,15 @@ void serverUpdateEffects(int player) net_packet->data[9] = 0; net_packet->data[10] = 0; net_packet->data[11] = 0; + net_packet->data[12] = 0; net_packet->data[13] = 0; + net_packet->data[14] = 0; + net_packet->data[15] = 0; + net_packet->data[16] = 0; + net_packet->data[17] = 0; + net_packet->data[18] = 0; + net_packet->data[19] = 0; for (j = 0; j < NUMEFFECTS; j++) { if ( stats[player]->EFFECTS[j] == true ) @@ -961,12 +968,12 @@ void serverUpdateEffects(int player) if ( stats[player]->EFFECTS_TIMERS[j] < TICKS_PER_SECOND * 5 && stats[player]->EFFECTS_TIMERS[j] > 0 ) { // use these bits to denote if duration is low. - net_packet->data[9 + j / 8] |= power(2, j - (j / 8) * 8); + net_packet->data[12 + j / 8] |= power(2, j - (j / 8) * 8); } } net_packet->address.host = net_clients[player - 1].host; net_packet->address.port = net_clients[player - 1].port; - net_packet->len = 14; + net_packet->len = 20; sendPacketSafe(net_sock, -1, net_packet, player - 1); } @@ -3995,7 +4002,7 @@ static std::unordered_map clientPacketHandlers = { if ( net_packet->data[4 + c / 8]&power(2, c - (c / 8) * 8) ) { stats[clientnum]->EFFECTS[c] = true; - if ( net_packet->data[9 + c / 8] & power(2, c - (c / 8) * 8) ) // use these bits to denote if duration is low. + if ( net_packet->data[12 + c / 8] & power(2, c - (c / 8) * 8) ) // use these bits to denote if duration is low. { stats[clientnum]->EFFECTS_TIMERS[c] = 1; } From 71502fb83c9f24bfee430a67b4d363c22cf31714 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 30 Aug 2024 16:12:07 +1000 Subject: [PATCH 094/244] * sound no play if volume = 0 --- src/engine/audio/sound_game.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/audio/sound_game.cpp b/src/engine/audio/sound_game.cpp index 726e95fc0..f92059c98 100644 --- a/src/engine/audio/sound_game.cpp +++ b/src/engine/audio/sound_game.cpp @@ -63,7 +63,7 @@ FMOD::Channel* playSoundPlayer(int player, Uint16 snd, Uint8 vol) { return playSound(snd, vol); } - else if ( multiplayer == SERVER ) + else if ( multiplayer == SERVER && vol > 0 ) { if ( client_disconnected[player] || player <= 0 ) { @@ -98,7 +98,7 @@ FMOD::Channel* playSoundNotificationPlayer(int player, Uint16 snd, Uint8 vol) { return playSoundNotification(snd, vol); } - else if ( multiplayer == SERVER ) + else if ( multiplayer == SERVER && vol > 0 ) { if ( client_disconnected[player] || player <= 0 ) { @@ -130,7 +130,7 @@ FMOD::Channel* playSoundPos(real_t x, real_t y, Uint16 snd, Uint8 vol) { auto result = playSoundPosLocal(x, y, snd, vol); - if (multiplayer == SERVER) + if (multiplayer == SERVER && vol > 0) { for (int c = 1; c < MAXPLAYERS; c++) { @@ -378,7 +378,7 @@ OPENAL_SOUND* playSoundPlayer(int player, Uint16 snd, Uint8 vol) { return playSound(snd, vol); } - else if ( multiplayer == SERVER ) + else if ( multiplayer == SERVER && vol > 0 ) { if ( client_disconnected[player] || player <= 0 ) { @@ -433,7 +433,7 @@ OPENAL_SOUND* playSoundPos(real_t x, real_t y, Uint16 snd, Uint8 vol) return NULL; } - if (multiplayer == SERVER) + if (multiplayer == SERVER && vol > 0 ) { for (c = 1; c < MAXPLAYERS; c++) { From d3bf954afc83261ea7e461696742aaf5e861c4e5 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 30 Aug 2024 16:33:24 +1000 Subject: [PATCH 095/244] * slime spells add * greasy on players now triggers when attacking/defending --- src/acthudweapon.cpp | 24 ++ src/entity.cpp | 48 +-- src/items.cpp | 167 +++++++++- src/items.hpp | 3 +- src/magic/actmagic.cpp | 635 +++++++++++++++++++++++++++++++++++--- src/magic/castSpell.cpp | 40 +++ src/magic/magic.cpp | 78 ++--- src/magic/magic.hpp | 21 +- src/magic/setupSpells.cpp | 158 ++++++++++ src/magic/spell.cpp | 41 +++ src/net.cpp | 53 ++++ 11 files changed, 1131 insertions(+), 137 deletions(-) diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp index c1e531b0e..01e5c87c9 100644 --- a/src/acthudweapon.cpp +++ b/src/acthudweapon.cpp @@ -992,6 +992,16 @@ void actHudWeapon(Entity* my) } else { + if ( playerGreasyDropItem(HUDWEAPON_PLAYERNUM, stats[HUDWEAPON_PLAYERNUM]->weapon) ) + { + my->flags[INVISIBLE] = true; + if ( parent != nullptr ) + { + parent->flags[INVISIBLE] = false; + } + return; + } + if ( conductGameChallenges[CONDUCT_BRAWLER] || achievementBrawlerMode ) { if ( itemCategory(stats[HUDWEAPON_PLAYERNUM]->weapon) == WEAPON @@ -3465,6 +3475,14 @@ void actHudShield(Entity* my) wouldBeDefending = false; } + bool dropShield = false; + if ( wouldBeDefending && defending && playerGreasyDropItem(HUDSHIELD_PLAYERNUM, stats[HUDSHIELD_PLAYERNUM]->shield) ) + { + wouldBeDefending = false; + defending = false; + dropShield = true; + } + if (defending) { stats[HUDSHIELD_PLAYERNUM]->defending = true; @@ -3507,6 +3525,12 @@ void actHudShield(Entity* my) HUDSHIELD_DEFEND = defending; HUDSHIELD_SNEAKING = sneaking; + if ( dropShield ) + { + my->flags[INVISIBLE] = true; + return; + } + bool crossbow = (stats[HUDSHIELD_PLAYERNUM]->weapon && (stats[HUDSHIELD_PLAYERNUM]->weapon->type == CROSSBOW || stats[HUDSHIELD_PLAYERNUM]->weapon->type == HEAVY_CROSSBOW) ); bool doCrossbowReloadAnimation = false; bool doBowReload = false; diff --git a/src/entity.cpp b/src/entity.cpp index f7c49ca0e..02adde642 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -4195,31 +4195,13 @@ void Entity::handleEffects(Stat* myStats) // intended to fix multiplayer duplication. if ( ticks % 70 == 0 || ticks % 130 == 0 ) { - if ( myStats->weapon != NULL - && (myStats->weapon->beatitude == 0 - || !shouldInvertEquipmentBeatitude(myStats) && myStats->weapon->beatitude > 0 - || shouldInvertEquipmentBeatitude(myStats) && myStats->weapon->beatitude < 0) - ) + if ( behavior == &actMonster && myStats->HP > 0 ) { - messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(636)); - if ( player >= 0 ) - { - dropItem(myStats->weapon, player); - if ( player > 0 && multiplayer == SERVER && !players[player]->isLocalPlayer() ) - { - strcpy((char*)net_packet->data, "DROP"); - net_packet->data[4] = 5; - net_packet->address.host = net_clients[player - 1].host; - net_packet->address.port = net_clients[player - 1].port; - net_packet->len = 5; - sendPacketSafe(net_sock, -1, net_packet, player - 1); - } - } - else + if ( myStats->weapon != NULL && itemCategory(myStats->weapon) != SPELLBOOK ) { + //messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(636)); dropItemMonster(myStats->weapon, this, myStats); } - myStats->weapon = NULL; } } } @@ -6458,12 +6440,12 @@ bool Entity::isWaterWalking() const } if ( stats->type == SLIME ) { - std::string res = stats->getAttribute("slime_type"); - if ( res == "slime green" - || res == "slime blue" - || res == "slime red" - || res == "slime tar" - || res == "slime metal" ) + auto color = MonsterData_t::getKeyFromSprite(sprite, SLIME); + if ( color == "slime green" + || color == "slime blue" + || color == "slime red" + || color == "slime tar" + || color == "slime metal" ) { return true; } @@ -6483,12 +6465,12 @@ bool Entity::isLavaWalking() const } if ( stats->type == SLIME ) { - std::string res = stats->getAttribute("slime_type"); - if ( res == "slime green" - || res == "slime blue" - || res == "slime red" - || res == "slime tar" - || res == "slime metal" ) + auto color = MonsterData_t::getKeyFromSprite(sprite, SLIME); + if ( color == "slime green" + || color == "slime blue" + || color == "slime red" + || color == "slime tar" + || color == "slime metal" ) { return true; } diff --git a/src/items.cpp b/src/items.cpp index a5d5938ac..770f76058 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1170,9 +1170,152 @@ int itemCompare(const Item* const item1, const Item* const item2, bool checkAppe Handles the client impulse to drop an item, returns true on free'd item. -------------------------------------------------------------------------------*/ - Uint32 dropItemSfxTicks[MAXPLAYERS] = { 0 }; -bool dropItem(Item* const item, const int player, const bool notifyMessage) + +bool playerGreasyDropItem(const int player, Item* const item) +{ + if ( !item || item->count == 0 ) { return false; } + if ( player < 0 || player >= MAXPLAYERS ) { return false; } + if ( players[player]->isLocalPlayer() ) + { + if ( !stats[player]->EFFECTS[EFF_GREASY] ) { return false; } + if ( itemIsEquipped(item, player) ) + { + Item** slot = itemSlot(stats[player], item); + if ( !(slot == &stats[player]->weapon || slot == &stats[player]->shield) ) + { + return false; + } + if ( !players[player]->entity ) { return false; } + bool canDrop = false; + bool shapeshifted = false; + if ( players[player]->entity->effectShapeshift != NOTHING ) + { + shapeshifted = true; + } + if ( !shapeshifted || + (shapeshifted && slot == &stats[player]->weapon + && stats[player]->type == CREATURE_IMP && itemCategory(item) == MAGICSTAFF) + || (shapeshifted && slot == &stats[player]->shield + && stats[player]->type == CREATURE_IMP && itemCategory(item) == SPELLBOOK) ) + { + if ( item->beatitude == 0 || stats[player]->type == AUTOMATON + || !shouldInvertEquipmentBeatitude(stats[player]) && item->beatitude > 0 + || shouldInvertEquipmentBeatitude(stats[player]) && item->beatitude < 0 ) + { + canDrop = true; + } + } + + if ( !canDrop ) + { + return false; + } + if ( multiplayer == CLIENT ) + { + strcpy((char*)net_packet->data, "GRES"); + SDLNet_Write32(static_cast(item->type), &net_packet->data[4]); + SDLNet_Write32(static_cast(item->status), &net_packet->data[8]); + SDLNet_Write32(static_cast(item->beatitude), &net_packet->data[12]); + SDLNet_Write32(static_cast(item->count), &net_packet->data[16]); + SDLNet_Write32(static_cast(item->appearance), &net_packet->data[20]); + net_packet->data[24] = item->identified; + net_packet->data[25] = clientnum; + net_packet->data[26] = (slot == &stats[player]->weapon) ? 0 : 1; + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 27; + sendPacketSafe(net_sock, -1, net_packet, 0); + + if ( slot == &stats[player]->weapon ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(636)); + } + else if ( slot == &stats[player]->shield ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(6246)); + } + messagePlayer(player, MESSAGE_SPAM_MISC, Language::get(1088), item->description()); + + if ( slot != nullptr ) + { + *slot = nullptr; + } + players[player]->paperDoll.updateSlots(); + + list_RemoveNode(item->node); + + return true; + } + } + } + + if ( multiplayer != CLIENT && players[player]->entity ) + { + Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity. + entity->flags[INVISIBLE] = true; + entity->flags[UPDATENEEDED] = true; + entity->x = players[player]->entity->x; + entity->y = players[player]->entity->y; + entity->sizex = 4; + entity->sizey = 4; + entity->yaw = players[player]->entity->yaw; + entity->vel_x = (1.5 + .025 * (local_rng.rand() % 11)) * cos(players[player]->entity->yaw); + entity->vel_y = (1.5 + .025 * (local_rng.rand() % 11)) * sin(players[player]->entity->yaw); + entity->vel_z = (-10 - local_rng.rand() % 20) * .01; + entity->flags[PASSABLE] = true; + entity->behavior = &actItem; + entity->skill[10] = item->type; + entity->skill[11] = item->status; + entity->skill[12] = item->beatitude; + entity->skill[13] = item->count; + entity->skill[14] = item->appearance; + entity->skill[15] = item->identified; + entity->parent = players[player]->entity->getUID(); + entity->itemOriginalOwner = entity->parent; + + // play sound - not in the same tick + if ( ticks - dropItemSfxTicks[player] > 1 ) + { + playSoundEntity(players[player]->entity, 47 + local_rng.rand() % 3, 64); + } + dropItemSfxTicks[player] = ticks; + + Item** slot = itemSlot(stats[player], item); + if ( players[player]->isLocalPlayer() ) + { + if ( slot == &stats[player]->weapon ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(636)); + } + else if ( slot == &stats[player]->shield ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(6246)); + } + messagePlayer(player, MESSAGE_SPAM_MISC, Language::get(1088), item->description()); + } + + if ( slot != nullptr ) + { + *slot = nullptr; + } + players[player]->paperDoll.updateSlots(); + + if ( item->node != nullptr ) + { + list_RemoveNode(item->node); + } + else + { + free(item); + } + return true; + } + + return false; +} + +bool dropItem(Item* const item, const int player, const bool notifyMessage, const bool dropAll) { if (!item) { @@ -1225,9 +1368,10 @@ bool dropItem(Item* const item, const int player, const bool notifyMessage) oldcount = item->count; if ( item->count >= 10 && (item->type == TOOL_METAL_SCRAP || item->type == TOOL_MAGIC_SCRAP) ) { - item->count = 10; + int qty = dropAll ? item->count : 10; + item->count = qty; messagePlayer(player, MESSAGE_SPAM_MISC, Language::get(1088), item->description()); - item->count = oldcount - 10; + item->count = oldcount - qty; } else if ( itemTypeIsQuiver(item->type) ) { @@ -1253,7 +1397,7 @@ bool dropItem(Item* const item, const int player, const bool notifyMessage) { messagePlayer(player, MESSAGE_SPAM_MISC, Language::get(1088), item->description()); } - item->count = oldcount - 1; + item->count = dropAll ? 0 : (oldcount - 1); } // unequip the item @@ -1289,14 +1433,11 @@ bool dropItem(Item* const item, const int player, const bool notifyMessage) else if ( itemTypeIsQuiver(item->type) ) { qtyToDrop = item->count; - /*if ( item->count >= 10 ) - { - qtyToDrop = 10; - } - else - { - qtyToDrop = item->count; - }*/ + } + + if ( dropAll ) + { + qtyToDrop = item->count; } Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity. diff --git a/src/items.hpp b/src/items.hpp index 8508434ed..07d8a5dbf 100644 --- a/src/items.hpp +++ b/src/items.hpp @@ -654,7 +654,8 @@ Sint32 itemModel(const Item* item); Sint32 itemModelFirstperson(const Item* item); SDL_Surface* itemSprite(Item* item); void consumeItem(Item*& item, int player); //NOTE: Items have to be unequipped before calling this function on them. NOTE: THIS CAN FREE THE ITEM POINTER. Sets item to nullptr if it does. -bool dropItem(Item* item, int player, bool notifyMessage = true); // return true on free'd item +bool dropItem(Item* item, int player, const bool notifyMessage = true, const bool dropAll = false); // return true on free'd item +bool playerGreasyDropItem(const int player, Item* const item); void useItem(Item* item, int player, Entity* usedBy = nullptr, bool unequipForDropping = false); enum EquipItemResult : int { diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index c82c92371..a50c16ebe 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -858,6 +858,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Sint32 entityHealth = 0; double dist = 0.f; bool hitFromAbove = false; + ParticleEmitterHit_t* particleEmitterHitProps = nullptr; if ( my->actmagicSpray == 1 ) { my->vel_z += my->actmagicSprayGravity; @@ -872,24 +873,10 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { hitFromAbove = true; - if ( my->actmagicEmitter > 0 ) + if ( particleEmitterHitProps = getParticleEmitterHitProps(my->actmagicEmitter, hit.entity) ) { - if ( hit.entity && (Sint32)(hit.entity->getUID()) >= 0 ) - { - auto& emitterHit = particleTimerEmitterHitEntities[my->actmagicEmitter]; - auto find = emitterHit.find(hit.entity->getUID()); - if ( find != emitterHit.end() ) - { - find->second.hits++; - find->second.tick = ticks; - } - else - { - auto& entry = emitterHit[hit.entity->getUID()]; - entry.hits = 1; - entry.tick = ticks; - } - } + particleEmitterHitProps->hits++; + particleEmitterHitProps->tick = ticks; } } } @@ -1121,8 +1108,27 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Uint32 color = makeColorRGB(255, 0, 0); if ( reflection == 0 ) { - messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(376)); - youAreHitByASpell = true; + if ( particleEmitterHitProps ) + { + if ( particleEmitterHitProps->hits == 1 ) + { + if ( my->actmagicSpray == 1 ) + { + //messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(6238)); + //youAreHitByASpell = true; + } + else + { + messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(376)); + youAreHitByASpell = true; + } + } + } + else + { + messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(376)); + youAreHitByASpell = true; + } } } if ( hitstats ) @@ -1264,7 +1270,16 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( bShouldEquipmentDegrade ) { // Reflection of 3 does not degrade equipment - if ( local_rng.rand() % 2 == 0 && hitstats && reflection < 3 ) + bool chance = false; + if ( my->actmagicSpray == 1 ) + { + chance = local_rng.rand() % 4 == 0; + } + else + { + chance = local_rng.rand() % 2 == 0; + } + if ( chance && hitstats && reflection < 3 ) { // set armornum to the relevant equipment slot to send to clients int armornum = 5 + reflection; @@ -1444,6 +1459,9 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. trapResist = hit.entity ? hit.entity->getFollowerBonusTrapResist() : 0; } + bool alertTarget = false; + bool alertAllies = false; + // Alerting the hit Entity if ( hit.entity ) { @@ -1522,8 +1540,8 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } else { - bool alertTarget = true; - bool alertAllies = true; + alertTarget = true; + alertAllies = true; if ( parent->behavior == &actMonster && parent->monsterAllyIndex != -1 ) { if ( hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1 ) @@ -1560,11 +1578,15 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } - if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) ) + if ( spell->ID != SPELL_SLIME_TAR ) // processed manually later { - hit.entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true); - } + if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) ) + { + hit.entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true); + } + hit.entity->updateEntityOnHit(parent, alertTarget); + } if ( parent->behavior == &actPlayer || parent->monsterAllyIndex != -1 ) { if ( hit.entity->behavior == &actPlayer || (hit.entity->behavior == &actMonster && hit.entity->monsterAllyIndex != -1) ) @@ -1579,7 +1601,6 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { hit.entity->alertAlliesOnBeingHit(parent); } - hit.entity->updateEntityOnHit(parent, alertTarget); } } } @@ -2043,7 +2064,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } else if (!strcmp(element->element_internal_name, spellElement_fire.element_internal_name)) { - if ( !(my->actmagicIsOrbiting == 2) && my->actmagicSpray != 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { spawnExplosion(my->x, my->y, my->z); } @@ -2063,7 +2084,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2237,7 +2258,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2260,7 +2281,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2282,7 +2303,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2362,7 +2383,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Entity* caster = uidToEntity(spell->caster); spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); } - if ( !(my->actmagicIsOrbiting == 2) || my->actmagicSpray == 1 ) + if ( !(my->actmagicIsOrbiting == 2) ) { my->removeLightField(); list_RemoveNode(my->mynode); @@ -2948,6 +2969,513 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } } + else if ( spell->ID >= SPELL_SLIME_ACID && spell->ID <= SPELL_SLIME_METAL ) + { + if ( hit.entity ) + { + int volume = 128; + static ConsoleVariable cvar_slimehit_sfx("/slimehit_sfx", 173); + int hitsfx = *cvar_slimehit_sfx; + int hitvolume = (particleEmitterHitProps && particleEmitterHitProps->hits == 1) ? 128 : 64; + if ( spell->ID == SPELL_SLIME_WATER || spell->ID == SPELL_SLIME_TAR ) + { + hitsfx = 665; + } + if ( particleEmitterHitProps && particleEmitterHitProps->hits > 1 ) + { + volume = 0; + if ( spell->ID == SPELL_SLIME_WATER || spell->ID == SPELL_SLIME_TAR ) + { + hitvolume = 32; + } + } + + if ( mimic ) + { + playSoundEntity(hit.entity, hitsfx, hitvolume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + damage /= (1 + (int)resistance); + hit.entity->chestHandleDamageMagic(damage, *my, parent); + if ( my->actmagicProjectileArc > 0 ) + { + Entity* caster = uidToEntity(spell->caster); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); + } + if ( !(my->actmagicIsOrbiting == 2) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + } + if ( spell->ID == SPELL_SLIME_FIRE ) + { + hit.entity->SetEntityOnFire(); + } + return; + } + else if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer ) + { + playSoundEntity(hit.entity, hitsfx, hitvolume); + Entity* parent = uidToEntity(my->parent); + playSoundEntity(hit.entity, 28, volume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + + //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; + int oldHP = hitstats->HP; + Sint32 preResistanceDamage = damage; + damage *= damageMultiplier; + damage /= (1 + (int)resistance); + + if ( spell->ID == SPELL_SLIME_FIRE ) + { + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6239), Language::get(6238), MSG_COMBAT); + } + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6237)); + } + } + hit.entity->SetEntityOnFire(); + } + if ( spell->ID == SPELL_SLIME_TAR ) + { + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6244), Language::get(6243), MSG_COMBAT); + } + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6236)); + } + } + + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + if ( local_rng.rand() % 2 == 0 ) + { + int duration = 6 * TICKS_PER_SECOND; + duration /= (1 + (int)resistance); + + bool hasgoggles = false; + if ( hitstats->mask && hitstats->mask->type == MASK_HAZARD_GOGGLES ) + { + if ( !(hit.entity->behavior == &actPlayer && hit.entity->effectShapeshift != NOTHING) ) + { + hasgoggles = true; + } + } + + int status = hit.entity->behavior == &actPlayer ? EFF_MESSY : EFF_BLIND; + + if ( hasgoggles ) + { + if ( hit.entity->behavior == &actPlayer ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6088)); + } + } + else if ( hit.entity->setEffect(status, true, duration / 2, false) ) + { + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(765)); + } + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(3879), Language::get(3878), MSG_COMBAT); + } + } + } + else + { + int duration = 10 * TICKS_PER_SECOND; + duration /= (1 + (int)resistance); + if ( hit.entity->setEffect(EFF_GREASY, true, duration, false) ) + { + Uint32 color = makeColorRGB(255, 0, 0); + if ( hit.entity->behavior == &actPlayer ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6245)); + } + } + } + } + + if ( !hit.entity->isBlind() ) + { + if ( alertTarget && hit.entity->monsterState != MONSTER_STATE_ATTACK && (hitstats->type < LICH || hitstats->type >= SHOPKEEPER) ) + { + hit.entity->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH, true); + } + } + else + { + alertTarget = false; + hit.entity->monsterReleaseAttackTarget(); + } + hit.entity->updateEntityOnHit(parent, alertTarget); + } + if ( spell->ID == SPELL_SLIME_ACID || spell->ID == SPELL_SLIME_METAL ) + { + bool hasamulet = false; + bool hasgoggles = false; + if ( (hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE) || hitstats->type == INSECTOID ) + { + resistance += 2; + hasamulet = true; + } + if ( hitstats->mask && hitstats->mask->type == MASK_HAZARD_GOGGLES ) + { + if ( !(hit.entity->behavior == &actPlayer && hit.entity->effectShapeshift != NOTHING) ) + { + hasgoggles = true; + resistance += 2; + } + } + + int duration = (spell->ID == SPELL_SLIME_METAL ? 10 : 6) * TICKS_PER_SECOND; + duration /= (1 + (int)resistance); + if ( spell->ID == SPELL_SLIME_ACID ) + { + if ( !hasamulet && !hasgoggles ) + { + if ( hit.entity->setEffect(EFF_POISONED, true, duration, false) ) + { + hitstats->poisonKiller = my->parent; + } + } + } + + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + if ( spell->ID == SPELL_SLIME_METAL ) + { + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(6241), Language::get(6240), MSG_COMBAT); + } + else + { + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(2431), Language::get(2430), MSG_COMBAT); + } + } + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + if ( spell->ID == SPELL_SLIME_METAL ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6242)); + } + else + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(2432)); + } + if ( hasgoggles ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6088)); + } + } + } + + if ( spell->ID == SPELL_SLIME_METAL ) + { + if ( hit.entity->setEffect(EFF_SLOW, true, duration, false) ) + { + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + playSoundEntity(hit.entity, 396 + local_rng.rand() % 3, 64); + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(394), Language::get(393), MSG_COMBAT); + } + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(395)); + } + } + } + } + + if ( hitstats->HP > 0 && !hasgoggles ) + { + // damage armor + Item* armor = nullptr; + int armornum = -1; + int acidChance = spell->ID == SPELL_SLIME_METAL ? 2 : 4; + if ( hitstats->defending && (local_rng.rand() % ((acidChance * 2) + resistance) == 0) ) // 1 in 8 to corrode shield + { + armornum = hitstats->pickRandomEquippedItem(&armor, true, false, true, true); + } + else if ( !hitstats->defending && (local_rng.rand() % (acidChance + resistance) == 0) ) // 1 in 4 to corrode armor + { + armornum = hitstats->pickRandomEquippedItem(&armor, true, false, false, false); + } + if ( armornum != -1 && armor != nullptr ) + { + hit.entity->degradeArmor(*hitstats, *armor, armornum); + } + } + } + else if ( spell->ID == SPELL_SLIME_WATER && hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) ) + { + real_t pushbackMultiplier = 0.6; + if ( hit.entity->behavior == &actMonster ) + { + if ( !hit.entity->isMobile() ) + { + pushbackMultiplier += 0.3; + } + if ( parent ) + { + real_t tangent = atan2(hit.entity->y - parent->y, hit.entity->x - parent->x); + hit.entity->vel_x = cos(tangent) * pushbackMultiplier; + hit.entity->vel_y = sin(tangent) * pushbackMultiplier; + hit.entity->monsterKnockbackVelocity = 0.01; + hit.entity->monsterKnockbackUID = my->parent; + hit.entity->monsterKnockbackTangentDir = tangent; + } + else + { + real_t tangent = atan2(hit.entity->y - my->y, hit.entity->x - my->x); + hit.entity->vel_x = cos(tangent) * pushbackMultiplier; + hit.entity->vel_y = sin(tangent) * pushbackMultiplier; + hit.entity->monsterKnockbackVelocity = 0.01; + hit.entity->monsterKnockbackTangentDir = tangent; + } + } + else if ( hit.entity->behavior == &actPlayer ) + { + /*if ( parent ) + { + real_t dist = entityDist(parent, hit.entity); + if ( dist < TOUCHRANGE ) + { + pushbackMultiplier += 0.5; + } + }*/ + if ( !players[hit.entity->skill[2]]->isLocalPlayer() ) + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = my->yaw; + serverUpdateEntityFSkill(hit.entity, 11); + serverUpdateEntityFSkill(hit.entity, 9); + } + else + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = my->yaw; + } + } + + if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) + { + if ( parent && parent->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(0, 255, 0); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(3215), Language::get(3214), MSG_COMBAT); + } + if ( hit.entity->behavior == &actPlayer ) + { + Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + } + } + } + + damage = std::max(2, damage); + hit.entity->modHP(-damage); + magicOnEntityHit(parent, my, hit.entity, hitstats, preResistanceDamage, damage, oldHP, spell ? spell->ID : SPELL_NONE); + magicTrapOnHit(parent, hit.entity, hitstats, oldHP, spell ? spell->ID : SPELL_NONE); + + // write the obituary + if ( parent ) + { + parent->killedByMonsterObituary(hit.entity); + } + + // update enemy bar for attacker + if ( !strcmp(hitstats->name, "") ) + { + updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP, + false, dmgGib); + } + else + { + updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP, + false, dmgGib); + } + if ( oldHP > 0 && hitstats->HP <= 0 && parent ) + { + parent->awardXP(hit.entity, true, true); + spawnBloodVialOnMonsterDeath(hit.entity, hitstats, parent); + } + } + else if ( hit.entity->behavior == &actDoor ) + { + if ( spell->ID == SPELL_SLIME_FIRE ) + { + hit.entity->SetEntityOnFire(); + } + playSoundEntity(hit.entity, hitsfx, hitvolume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; + damage /= (1 + (int)resistance); + + hit.entity->doorHandleDamageMagic(damage, *my, parent); + if ( my->actmagicProjectileArc > 0 ) + { + Entity* caster = uidToEntity(spell->caster); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); + } + if ( !(my->actmagicIsOrbiting == 2) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + } + return; + } + else if ( hit.entity->isDamageableCollider() && hit.entity->isColliderDamageableByMagic() ) + { + if ( spell->ID == SPELL_SLIME_FIRE ) + { + hit.entity->SetEntityOnFire(); + } + playSoundEntity(hit.entity, hitsfx, hitvolume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; + damage /= (1 + (int)resistance); + + hit.entity->colliderHandleDamageMagic(damage, *my, parent); + if ( my->actmagicProjectileArc > 0 ) + { + Entity* caster = uidToEntity(spell->caster); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); + } + if ( !(my->actmagicIsOrbiting == 2) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + } + return; + } + else if ( hit.entity->behavior == &actChest ) + { + if ( spell->ID == SPELL_SLIME_FIRE ) + { + hit.entity->SetEntityOnFire(); + } + playSoundEntity(hit.entity, hitsfx, hitvolume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + damage /= (1 + (int)resistance); + hit.entity->chestHandleDamageMagic(damage, *my, parent); + if ( my->actmagicProjectileArc > 0 ) + { + Entity* caster = uidToEntity(spell->caster); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); + } + if ( !(my->actmagicIsOrbiting == 2) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + } + return; + } + else if ( hit.entity->behavior == &actFurniture ) + { + if ( spell->ID == SPELL_SLIME_FIRE ) + { + hit.entity->SetEntityOnFire(); + } + playSoundEntity(hit.entity, hitsfx, hitvolume); + int damage = element->damage; + damage += (spellbookDamageBonus * damage); + damage /= (1 + (int)resistance); + int oldHP = hit.entity->furnitureHealth; + hit.entity->furnitureHealth -= damage; + if ( parent ) + { + if ( parent->behavior == &actPlayer ) + { + bool destroyed = oldHP > 0 && hit.entity->furnitureHealth <= 0; + if ( destroyed ) + { + gameModeManager.currentSession.challengeRun.updateKillEvent(hit.entity); + } + switch ( hit.entity->furnitureType ) + { + case FURNITURE_CHAIR: + if ( destroyed ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(388)); + } + updateEnemyBar(parent, hit.entity, Language::get(677), hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth, + false, DamageGib::DMG_DEFAULT); + break; + case FURNITURE_TABLE: + if ( destroyed ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(389)); + } + updateEnemyBar(parent, hit.entity, Language::get(676), hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth, + false, DamageGib::DMG_DEFAULT); + break; + case FURNITURE_BED: + if ( destroyed ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2505)); + } + updateEnemyBar(parent, hit.entity, Language::get(2505), hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth, + false, DamageGib::DMG_DEFAULT); + break; + case FURNITURE_BUNKBED: + if ( destroyed ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2506)); + } + updateEnemyBar(parent, hit.entity, Language::get(2506), hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth, + false, DamageGib::DMG_DEFAULT); + break; + case FURNITURE_PODIUM: + if ( destroyed ) + { + messagePlayer(parent->skill[2], MESSAGE_COMBAT, Language::get(2508), Language::get(2507)); + } + updateEnemyBar(parent, hit.entity, Language::get(2507), hit.entity->furnitureHealth, hit.entity->furnitureMaxHealth, + false, DamageGib::DMG_DEFAULT); + break; + default: + break; + } + } + } + playSoundEntity(hit.entity, 28, volume); + if ( my->actmagicProjectileArc > 0 ) + { + Entity* caster = uidToEntity(spell->caster); + spawnMagicTower(caster, my->x, my->y, spell->ID, nullptr, true); + } + } + } + } else if ( !strcmp(element->element_internal_name, spellElement_ghostBolt.element_internal_name) ) { if ( hit.entity ) @@ -4402,7 +4930,6 @@ void createParticleCircling(Entity* parent, int duration, int sprite) } #define PARTICLE_LIFE my->skill[0] -#define PARTICLE_CASTER my->skill[1] void actParticleCircle(Entity* my) { @@ -5585,6 +6112,43 @@ void actParticleTimer(Entity* my) } else { + int sound = 0; + int spellID = SPELL_NONE; + switch ( my->particleTimerCountdownSprite ) + { + case 180: + sound = 169; + spellID = SPELL_SLIME_ACID; + break; + case 181: + sound = 169; + spellID = SPELL_SLIME_WATER; + break; + case 182: + sound = 164; + spellID = SPELL_SLIME_FIRE; + break; + case 183: + sound = 169; + spellID = SPELL_SLIME_TAR; + break; + case 184: + sound = 169; + spellID = SPELL_SLIME_METAL; + break; + default: + break; + } + if ( my->particleTimerVariable1 == 0 ) + { + // first fired after delay + my->particleTimerVariable1 = 1; + if ( sound > 0 ) + { + playSoundEntityLocal(parent, sound, 128); + } + } + my->x = parent->x; my->y = parent->y; my->yaw = parent->yaw; @@ -5633,7 +6197,7 @@ void actParticleTimer(Entity* my) entity->vel_z = -.5; if ( entity->behavior == &actMagicMissile ) { - spell_t* spell = getSpellFromID(SPELL_LIGHTNING); + spell_t* spell = getSpellFromID(spellID); entity->skill[4] = 0; // life start entity->skill[5] = TICKS_PER_SECOND; //lifetime entity->actmagicSpray = 1; @@ -5647,7 +6211,8 @@ void actParticleTimer(Entity* my) { elementNode = element->elements.first; element = (spellElement_t*)elementNode->element; - element->damage = 2; + element->damage = 3; + element->mana = 5; } node->deconstructor = &spellDeconstructor; diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index b992287f6..4d093b2a1 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -2032,6 +2032,42 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } //Also refactor the duration determining code. } + else if ( !strcmp(element->element_internal_name, spellElement_slime_spray.element_internal_name) ) + { + int particle = -1; + switch ( spell->ID ) + { + case SPELL_SLIME_ACID: + particle = 180; + break; + case SPELL_SLIME_WATER: + particle = 181; + break; + case SPELL_SLIME_FIRE: + particle = 182; + break; + case SPELL_SLIME_TAR: + particle = 183; + break; + case SPELL_SLIME_METAL: + particle = 184; + break; + default: + break; + } + if ( particle >= 0 ) + { + Entity* spellTimer = createParticleTimer(caster, 30, -1); + spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_MAGIC_SPRAY; + spellTimer->particleTimerCountdownSprite = particle; + result = spellTimer; + if ( !(caster && caster->behavior == &actMonster && caster->getStats() && caster->getStats()->type == SLIME) ) + { + // spawn these if not a slime doing its special attack, client spawns own particles + serverSpawnMiscParticles(caster, PARTICLE_EFFECT_SLIME_SPRAY, particle); + } + } + } // intentional separate from else/if chain. // disables propulsion if found a marked target. @@ -2818,6 +2854,10 @@ int spellGetCastSound(spell_t* spell) { return 169; } + else if ( spell->ID >= SPELL_SLIME_ACID && spell->ID <= SPELL_SLIME_METAL ) + { + return 0; + } else { return 169; diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 7fce313cb..4e2ecd194 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -26,62 +26,34 @@ #include "../mod_tools.hpp" std::map> particleTimerEmitterHitEntities; +ParticleEmitterHit_t* getParticleEmitterHitProps(Uint32 emitterUid, Entity* hitentity) +{ + if ( emitterUid == 0 || !hitentity ) { return nullptr; } + + if ( (Sint32)(hitentity->getUID()) >= 0 ) + { + auto& emitterHit = particleTimerEmitterHitEntities[emitterUid]; + auto find = emitterHit.find(hitentity->getUID()); + if ( find != emitterHit.end() ) + { + return &find->second; + } + else + { + auto& entry = emitterHit[hitentity->getUID()]; + return &entry; + } + } + return nullptr; +} void freeSpells() { - list_FreeAll(&spell_forcebolt.elements); - list_FreeAll(&spell_magicmissile.elements); - list_FreeAll(&spell_cold.elements); - list_FreeAll(&spell_fireball.elements); - list_FreeAll(&spell_lightning.elements); - list_FreeAll(&spell_removecurse.elements); - list_FreeAll(&spell_light.elements); - list_FreeAll(&spell_identify.elements); - list_FreeAll(&spell_magicmapping.elements); - list_FreeAll(&spell_sleep.elements); - list_FreeAll(&spell_confuse.elements); - list_FreeAll(&spell_slow.elements); - list_FreeAll(&spell_opening.elements); - list_FreeAll(&spell_locking.elements); - list_FreeAll(&spell_levitation.elements); - list_FreeAll(&spell_invisibility.elements); - list_FreeAll(&spell_teleportation.elements); - list_FreeAll(&spell_healing.elements); - list_FreeAll(&spell_extrahealing.elements); - list_FreeAll(&spell_cureailment.elements); - list_FreeAll(&spell_dig.elements); - list_FreeAll(&spell_summon.elements); - list_FreeAll(&spell_stoneblood.elements); - list_FreeAll(&spell_bleed.elements); - list_FreeAll(&spell_dominate.elements); - list_FreeAll(&spell_reflectMagic.elements); - list_FreeAll(&spell_acidSpray.elements); - list_FreeAll(&spell_stealWeapon.elements); - list_FreeAll(&spell_drainSoul.elements); - list_FreeAll(&spell_vampiricAura.elements); - list_FreeAll(&spell_charmMonster.elements); - list_FreeAll(&spell_revertForm.elements); - list_FreeAll(&spell_ratForm.elements); - list_FreeAll(&spell_spiderForm.elements); - list_FreeAll(&spell_trollForm.elements); - list_FreeAll(&spell_impForm.elements); - list_FreeAll(&spell_sprayWeb.elements); - list_FreeAll(&spell_poison.elements); - list_FreeAll(&spell_speed.elements); - list_FreeAll(&spell_fear.elements); - list_FreeAll(&spell_strike.elements); - list_FreeAll(&spell_detectFood.elements); - list_FreeAll(&spell_weakness.elements); - list_FreeAll(&spell_amplifyMagic.elements); - list_FreeAll(&spell_shadowTag.elements); - list_FreeAll(&spell_telePull.elements); - list_FreeAll(&spell_demonIllusion.elements); - list_FreeAll(&spell_trollsBlood.elements); - list_FreeAll(&spell_salvageItem.elements); - list_FreeAll(&spell_flutter.elements); - list_FreeAll(&spell_dash.elements); - list_FreeAll(&spell_polymorph.elements); - list_FreeAll(&spell_ghost_bolt.elements); + for ( auto it = allGameSpells.begin(); it != allGameSpells.end(); ++it ) + { + spell_t& spell = **it; + list_FreeAll(&spell.elements); + } } void spell_magicMap(int player) diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index 059c2c55e..9654b5c89 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -74,7 +74,12 @@ static const int SPELL_SELF_POLYMORPH = 52; static const int SPELL_CRAB_FORM = 53; static const int SPELL_CRAB_WEB = 54; static const int SPELL_GHOST_BOLT = 55; -static const int NUM_SPELLS = 56; +static const int SPELL_SLIME_ACID = 56; +static const int SPELL_SLIME_WATER = 57; +static const int SPELL_SLIME_FIRE = 58; +static const int SPELL_SLIME_TAR = 59; +static const int SPELL_SLIME_METAL = 60; +static const int NUM_SPELLS = 61; #define SPELLELEMENT_CONFUSE_BASE_DURATION 2//In seconds. @@ -147,7 +152,7 @@ static const int PARTICLE_EFFECT_DEVIL_SUMMON_MONSTER = 24; static const int PARTICLE_EFFECT_SHATTERED_GEM = 25; static const int PARTICLE_EFFECT_SHRINE_TELEPORT = 26; static const int PARTICLE_EFFECT_GHOST_TELEPORT = 27; -static const int PARTICLE_EFFECT_MAGIC_SPRAY = 28; +static const int PARTICLE_EFFECT_SLIME_SPRAY = 28; // actmagicIsVertical constants static const int MAGIC_ISVERTICAL_NONE = 0; @@ -168,6 +173,7 @@ struct ParticleEmitterHit_t int hits = 0; }; extern std::map> particleTimerEmitterHitEntities; +ParticleEmitterHit_t* getParticleEmitterHitProps(Uint32 emitterUid, Entity* hitentity); bool addSpell(int spell, int player, bool ignoreSkill = false); //Adds a spell to the client's spell list. Note: Do not use this to add custom spells. @@ -409,6 +415,12 @@ extern spellElement_t spellElement_flutter; extern spellElement_t spellElement_dash; extern spellElement_t spellElement_selfPolymorph; extern spellElement_t spellElement_ghostBolt; +extern spellElement_t spellElement_slimeAcid; +extern spellElement_t spellElement_slimeWater; +extern spellElement_t spellElement_slimeFire; +extern spellElement_t spellElement_slimeTar; +extern spellElement_t spellElement_slimeMetal; +extern spellElement_t spellElement_slime_spray; /* */ //TODO: Differentiate between touch spells, enchantment spells, personal spells, ranged spells, area of effect spells, close blast/burst spells, and enemy/ally target spells. @@ -503,6 +515,11 @@ extern spell_t spell_flutter; extern spell_t spell_dash; extern spell_t spell_polymorph; extern spell_t spell_ghost_bolt; +extern spell_t spell_slime_acid; +extern spell_t spell_slime_water; +extern spell_t spell_slime_fire; +extern spell_t spell_slime_tar; +extern spell_t spell_slime_metal; //TODO: Armor/protection/warding spells. //TODO: Targeting method? diff --git a/src/magic/setupSpells.cpp b/src/magic/setupSpells.cpp index 6425ef88b..fae683d58 100644 --- a/src/magic/setupSpells.cpp +++ b/src/magic/setupSpells.cpp @@ -239,6 +239,14 @@ void setupSpells() ///TODO: Verify this function. spellElement_missile_trio.duration = 25; //1 second. strcpy(spellElement_missile_trio.element_internal_name, "spell_element_trio"); + spellElementConstructor(&spellElement_slime_spray); + spellElement_slime_spray.mana = 1; + spellElement_slime_spray.base_mana = 1; + spellElement_slime_spray.overload_multiplier = 1; + spellElement_slime_spray.damage = 0; + spellElement_slime_spray.duration = 100; + strcpy(spellElement_slime_spray.element_internal_name, "spell_element_slime_spray"); + spellElementConstructor(&spellElement_dominate); spellElement_dominate.mana = 20; spellElement_dominate.base_mana = 20; @@ -431,6 +439,46 @@ void setupSpells() ///TODO: Verify this function. spellElement_ghostBolt.duration = 75; strcpy(spellElement_ghostBolt.element_internal_name, "spell_element_ghost_bolt"); + spellElementConstructor(&spellElement_slimeAcid); + spellElement_slimeAcid.mana = 5; + spellElement_slimeAcid.base_mana = 5; + spellElement_slimeAcid.overload_multiplier = 1; + spellElement_slimeAcid.damage = 5; + spellElement_slimeAcid.duration = 100; + strcpy(spellElement_slimeAcid.element_internal_name, "spell_element_slime_acid"); + + spellElementConstructor(&spellElement_slimeWater); + spellElement_slimeWater.mana = 5; + spellElement_slimeWater.base_mana = 5; + spellElement_slimeWater.overload_multiplier = 1; + spellElement_slimeWater.damage = 5; + spellElement_slimeWater.duration = 100; + strcpy(spellElement_slimeWater.element_internal_name, "spell_element_slime_water"); + + spellElementConstructor(&spellElement_slimeFire); + spellElement_slimeFire.mana = 5; + spellElement_slimeFire.base_mana = 5; + spellElement_slimeFire.overload_multiplier = 1; + spellElement_slimeFire.damage = 5; + spellElement_slimeFire.duration = 100; + strcpy(spellElement_slimeFire.element_internal_name, "spell_element_slime_fire"); + + spellElementConstructor(&spellElement_slimeTar); + spellElement_slimeTar.mana = 5; + spellElement_slimeTar.base_mana = 5; + spellElement_slimeTar.overload_multiplier = 1; + spellElement_slimeTar.damage = 5; + spellElement_slimeTar.duration = 100; + strcpy(spellElement_slimeTar.element_internal_name, "spell_element_slime_tar"); + + spellElementConstructor(&spellElement_slimeMetal); + spellElement_slimeMetal.mana = 5; + spellElement_slimeMetal.base_mana = 5; + spellElement_slimeMetal.overload_multiplier = 1; + spellElement_slimeMetal.damage = 5; + spellElement_slimeMetal.duration = 100; + strcpy(spellElement_slimeMetal.element_internal_name, "spell_element_slime_metal"); + spellConstructor(&spell_forcebolt); strcpy(spell_forcebolt.spell_internal_name, "spell_forcebolt"); spell_forcebolt.ID = SPELL_FORCEBOLT; @@ -1322,4 +1370,114 @@ void setupSpells() ///TODO: Verify this function. node->deconstructor = &spellElementDeconstructor; element = (spellElement_t*)node->element; element->node = node; + + spellConstructor(&spell_slime_acid); + strcpy(spell_slime_acid.spell_internal_name, "spell_slime_acid"); + spell_slime_acid.ID = SPELL_SLIME_ACID; + spell_slime_acid.difficulty = 100; + spell_slime_acid.elements.first = NULL; + spell_slime_acid.elements.last = NULL; + node = list_AddNodeLast(&spell_slime_acid.elements); + node->element = copySpellElement(&spellElement_slime_spray); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; //Tell the element what list it resides in. + //Now for the second element. + element->elements.first = NULL; + element->elements.last = NULL; + node = list_AddNodeLast(&element->elements); + node->element = copySpellElement(&spellElement_slimeAcid); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; + + spellConstructor(&spell_slime_water); + strcpy(spell_slime_water.spell_internal_name, "spell_slime_water"); + spell_slime_water.ID = SPELL_SLIME_WATER; + spell_slime_water.difficulty = 100; + spell_slime_water.elements.first = NULL; + spell_slime_water.elements.last = NULL; + node = list_AddNodeLast(&spell_slime_water.elements); + node->element = copySpellElement(&spellElement_slime_spray); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; //Tell the element what list it resides in. + //Now for the second element. + element->elements.first = NULL; + element->elements.last = NULL; + node = list_AddNodeLast(&element->elements); + node->element = copySpellElement(&spellElement_slimeWater); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; + + spellConstructor(&spell_slime_fire); + strcpy(spell_slime_fire.spell_internal_name, "spell_slime_fire"); + spell_slime_fire.ID = SPELL_SLIME_FIRE; + spell_slime_fire.difficulty = 100; + spell_slime_fire.elements.first = NULL; + spell_slime_fire.elements.last = NULL; + node = list_AddNodeLast(&spell_slime_fire.elements); + node->element = copySpellElement(&spellElement_slime_spray); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; //Tell the element what list it resides in. + //Now for the second element. + element->elements.first = NULL; + element->elements.last = NULL; + node = list_AddNodeLast(&element->elements); + node->element = copySpellElement(&spellElement_slimeFire); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; + + spellConstructor(&spell_slime_tar); + strcpy(spell_slime_tar.spell_internal_name, "spell_slime_tar"); + spell_slime_tar.ID = SPELL_SLIME_TAR; + spell_slime_tar.difficulty = 100; + spell_slime_tar.elements.first = NULL; + spell_slime_tar.elements.last = NULL; + node = list_AddNodeLast(&spell_slime_tar.elements); + node->element = copySpellElement(&spellElement_slime_spray); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; //Tell the element what list it resides in. + //Now for the second element. + element->elements.first = NULL; + element->elements.last = NULL; + node = list_AddNodeLast(&element->elements); + node->element = copySpellElement(&spellElement_slimeTar); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; + + spellConstructor(&spell_slime_metal); + strcpy(spell_slime_metal.spell_internal_name, "spell_slime_metal"); + spell_slime_metal.ID = SPELL_SLIME_METAL; + spell_slime_metal.difficulty = 100; + spell_slime_metal.elements.first = NULL; + spell_slime_metal.elements.last = NULL; + node = list_AddNodeLast(&spell_slime_metal.elements); + node->element = copySpellElement(&spellElement_slime_spray); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; //Tell the element what list it resides in. + //Now for the second element. + element->elements.first = NULL; + element->elements.last = NULL; + node = list_AddNodeLast(&element->elements); + node->element = copySpellElement(&spellElement_slimeMetal); + node->size = sizeof(spellElement_t); + node->deconstructor = &spellElementDeconstructor; + element = (spellElement_t*)node->element; + element->node = node; } diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index fd54aa80d..6a4a1eb64 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -75,6 +75,12 @@ spellElement_t spellElement_flutter; spellElement_t spellElement_dash; spellElement_t spellElement_selfPolymorph; spellElement_t spellElement_ghostBolt; +spellElement_t spellElement_slimeAcid; +spellElement_t spellElement_slimeWater; +spellElement_t spellElement_slimeFire; +spellElement_t spellElement_slimeTar; +spellElement_t spellElement_slimeMetal; +spellElement_t spellElement_slime_spray; spell_t spell_forcebolt; spell_t spell_magicmissile; @@ -130,6 +136,11 @@ spell_t spell_flutter; spell_t spell_dash; spell_t spell_polymorph; spell_t spell_ghost_bolt; +spell_t spell_slime_acid; +spell_t spell_slime_water; +spell_t spell_slime_fire; +spell_t spell_slime_tar; +spell_t spell_slime_metal; bool addSpell(int spell, int player, bool ignoreSkill) { @@ -310,6 +321,21 @@ bool addSpell(int spell, int player, bool ignoreSkill) case SPELL_GHOST_BOLT: new_spell = copySpell(&spell_ghost_bolt); break; + case SPELL_SLIME_ACID: + new_spell = copySpell(&spell_slime_acid); + break; + case SPELL_SLIME_WATER: + new_spell = copySpell(&spell_slime_water); + break; + case SPELL_SLIME_FIRE: + new_spell = copySpell(&spell_slime_fire); + break; + case SPELL_SLIME_TAR: + new_spell = copySpell(&spell_slime_tar); + break; + case SPELL_SLIME_METAL: + new_spell = copySpell(&spell_slime_metal); + break; default: return false; } @@ -941,6 +967,21 @@ spell_t* getSpellFromID(int ID) case SPELL_GHOST_BOLT: spell = &spell_ghost_bolt; break; + case SPELL_SLIME_ACID: + spell = &spell_slime_acid; + break; + case SPELL_SLIME_WATER: + spell = &spell_slime_water; + break; + case SPELL_SLIME_FIRE: + spell = &spell_slime_fire; + break; + case SPELL_SLIME_TAR: + spell = &spell_slime_tar; + break; + case SPELL_SLIME_METAL: + spell = &spell_slime_metal; + break; default: break; } diff --git a/src/net.cpp b/src/net.cpp index ad49d5b51..c64d8ea31 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2882,6 +2882,13 @@ static std::unordered_map clientPacketHandlers = { spellTimer->particleTimerCountdownSprite = sprite; } break; + case PARTICLE_EFFECT_SLIME_SPRAY: + { + Entity* spellTimer = createParticleTimer(entity, 30, -1); + spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_MAGIC_SPRAY; + spellTimer->particleTimerCountdownSprite = sprite; + } + break; case PARTICLE_EFFECT_PLAYER_AUTOMATON_DEATH: createParticleExplosionCharge(entity, 174, 100, 0.25); if ( entity && entity->behavior == &actPlayer ) @@ -6039,6 +6046,52 @@ static std::unordered_map serverPacketHandlers = { dropItem(item, player); }}, + // item drop (greasy) + { 'GRES', []() { + const int player = std::min(net_packet->data[25], (Uint8)(MAXPLAYERS - 1)); + client_keepalive[player] = ticks; + auto item = newItem(static_cast(SDLNet_Read32(&net_packet->data[4])), + static_cast(SDLNet_Read32(&net_packet->data[8])), + SDLNet_Read32(&net_packet->data[12]), + SDLNet_Read32(&net_packet->data[16]), + SDLNet_Read32(&net_packet->data[20]), + net_packet->data[24], + &stats[player]->inventory); + playerGreasyDropItem(player, item); + if ( net_packet->data[27] == 1 ) + { + // shield + if ( stats[player]->shield ) + { + if ( stats[player]->shield->node ) + { + list_RemoveNode(stats[player]->shield->node); + } + else + { + free(stats[player]->shield); + } + stats[player]->shield = nullptr; + } + } + else if ( net_packet->data[27] == 0 ) + { + // weapon + if ( stats[player]->weapon ) + { + if ( stats[player]->weapon->node ) + { + list_RemoveNode(stats[player]->weapon->node); + } + else + { + free(stats[player]->weapon); + } + stats[player]->weapon = nullptr; + } + } + } }, + // item drop (on death) {'DIEI', [](){ const int player = std::min(net_packet->data[25], (Uint8)(MAXPLAYERS - 1)); From ee2fc6f77fdff6ccd6c2622ec4e7ea994575cc49 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 16:55:33 +1000 Subject: [PATCH 096/244] * actplayer weapon sprite wasn't set to 0 when unequipped - hoping nothing breaks --- src/actplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 8b49aef77..2989ccb3d 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -9303,7 +9303,7 @@ void actPlayer(Entity* my) if ( stats[PLAYER_NUM]->weapon == NULL ) { entity->flags[INVISIBLE] = true; - //entity->sprite = 0; + entity->sprite = 0; } else { From 2ad0763964bb8ec6d86dd1c099ac1250b0ba759e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 16:56:08 +1000 Subject: [PATCH 097/244] * monster dont slide into doorways when the wall is broken next to it --- src/actmonster.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index ae56c49e4..71aac7652 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -4251,38 +4251,95 @@ void actMonster(Entity* my) else if ( entity->behavior == &actDoorFrame && entity->flags[INVISIBLE] ) { + int mapx = (int)floor(entity->x / 16); if ( entity->yaw >= -0.1 && entity->yaw <= 0.1 ) { // east/west doorway if ( my->y < floor(entity->y / 16) * 16 + 8 ) { + bool slide = true; + if ( mapy - 1 > 0 ) + { + int index = (mapy - 1) * MAPLAYERS + (mapx) * MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + index] ) + { + // no effect, wall is missing + slide = false; + } + } + + if ( slide ) + { // slide south MONSTER_VELX = 0; MONSTER_VELY = .25; } + } else { + bool slide = true; + if ( mapy + 1 < map.height ) + { + int index = (mapy + 1) * MAPLAYERS + (mapx) * MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + index] ) + { + // no effect, wall is missing + slide = false; + } + } + + if ( slide ) + { // slide north MONSTER_VELX = 0; MONSTER_VELY = -.25; } } + } else { // north/south doorway if ( my->x < floor(entity->x / 16) * 16 + 8 ) { + bool slide = true; + if ( mapx - 1 > 0 ) + { + int index = (mapy) * MAPLAYERS + (mapx - 1) * MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + index] ) + { + // no effect, wall is missing + slide = false; + } + } + + if ( slide ) + { // slide east MONSTER_VELX = .25; MONSTER_VELY = 0; } + } else { + bool slide = true; + if ( mapx + 1 < map.width ) + { + int index = (mapy) * MAPLAYERS + (mapx + 1) * MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + index] ) + { + // no effect, wall is missing + slide = false; + } + } + + if ( slide ) + { // slide west MONSTER_VELX = -.25; MONSTER_VELY = 0; } } + } wasInsideEntity = true; //messagePlayer(0, MESSAGE_DEBUG, "path: %d", my->monsterPathCount); ++my->monsterPathCount; From 4db596fe6ba64cbb24d6d42e943064a341be8ecf Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 17:01:15 +1000 Subject: [PATCH 098/244] * slime special atk --- src/actmonster.cpp | 57 ++++++++++++------- src/entity.hpp | 4 ++ src/monster_slime.cpp | 129 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 159 insertions(+), 31 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 71aac7652..d15811b2c 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -4252,6 +4252,7 @@ void actMonster(Entity* my) entity->flags[INVISIBLE] ) { int mapx = (int)floor(entity->x / 16); + int mapy = (int)floor(entity->y / 16); if ( entity->yaw >= -0.1 && entity->yaw <= 0.1 ) { // east/west doorway @@ -4270,10 +4271,10 @@ void actMonster(Entity* my) if ( slide ) { - // slide south - MONSTER_VELX = 0; - MONSTER_VELY = .25; - } + // slide south + MONSTER_VELX = 0; + MONSTER_VELY = .25; + } } else { @@ -4290,12 +4291,12 @@ void actMonster(Entity* my) if ( slide ) { - // slide north - MONSTER_VELX = 0; - MONSTER_VELY = -.25; + // slide north + MONSTER_VELX = 0; + MONSTER_VELY = -.25; + } } } - } else { // north/south doorway @@ -4314,10 +4315,10 @@ void actMonster(Entity* my) if ( slide ) { - // slide east - MONSTER_VELX = .25; - MONSTER_VELY = 0; - } + // slide east + MONSTER_VELX = .25; + MONSTER_VELY = 0; + } } else { @@ -4334,12 +4335,12 @@ void actMonster(Entity* my) if ( slide ) { - // slide west - MONSTER_VELX = -.25; - MONSTER_VELY = 0; + // slide west + MONSTER_VELX = -.25; + MONSTER_VELY = 0; + } } } - } wasInsideEntity = true; //messagePlayer(0, MESSAGE_DEBUG, "path: %d", my->monsterPathCount); ++my->monsterPathCount; @@ -5775,7 +5776,7 @@ void actMonster(Entity* my) } else { - my->yaw -= dir / 2; + my->yaw -= dir / 2; } } else @@ -8704,6 +8705,13 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) chooseWeapon(target, dist); bool hasrangedweapon = this->hasRangedWeapon(); bool lichRangeCheckOverride = false; + if ( myStats->type == SLIME ) + { + if ( monsterSpecialState == SLIME_CAST ) + { + lichRangeCheckOverride = true; + } + } if ( myStats->type == LICH_FIRE) { if ( monsterLichFireMeleeSeq == LICH_ATK_BASICSPELL_SINGLE ) @@ -8867,11 +8875,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) // check again for the target in attack range. return the result into hit.entity. double newTangent = atan2(target->y - this->y, target->x - this->x); - if ( myStats->type == SLIME && monsterSpecialTimer == MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY ) - { - hit.entity = uidToEntity(monsterTarget); - } - else if ( lichRangeCheckOverride ) + if ( lichRangeCheckOverride ) { hit.entity = uidToEntity(monsterTarget); } @@ -9727,6 +9731,15 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di } } break; + case SLIME: + // spray magic + if ( monsterSpecialState == SLIME_CAST ) + { + // special handled in slimeChooseWeapon() + monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY; + break; + } + break; case SPIDER: // spray web if ( dist < STRIKERANGE * 2 ) diff --git a/src/entity.hpp b/src/entity.hpp index 90c52edd4..1b8a6428b 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -924,6 +924,9 @@ class Entity case SUCCUBUS: succubusChooseWeapon(target, dist); break; + case SLIME: + slimeChooseWeapon(target, dist); + break; case SHOPKEEPER: if ( target ) { @@ -947,6 +950,7 @@ class Entity void vampireChooseWeapon(const Entity* target, double dist); void shadowChooseWeapon(const Entity* target, double dist); void succubusChooseWeapon(const Entity* target, double dist); + void slimeChooseWeapon(const Entity* target, double dist); void skeletonSummonSetEquipment(Stat* myStats, int rank); static void tinkerBotSetStats(Stat* myStats, int rank); static void mimicSetStats(Stat* myStats); diff --git a/src/monster_slime.cpp b/src/monster_slime.cpp index fb435429a..87d54086e 100644 --- a/src/monster_slime.cpp +++ b/src/monster_slime.cpp @@ -219,17 +219,77 @@ void initSlime(Entity* my, Stat* myStats) } } -const int slimeSprayDelay = TICKS_PER_SECOND + ((2 * TICKS_PER_SECOND) / 5); +const int slimeSprayDelayOffset = TICKS_PER_SECOND / 2; +const int slimeSprayDelay = TICKS_PER_SECOND + ((2 * TICKS_PER_SECOND) / 5) - slimeSprayDelayOffset; void slimeSprayAttack(Entity* my) { if ( !my ) { return; } auto color = MonsterData_t::getKeyFromSprite(my->sprite, SLIME); + Entity* spellTimer = nullptr; + if ( multiplayer == CLIENT ) + { + int particle = 180; + if ( color == "slime green" ) + { + particle = 180; + } + else if ( color == "slime blue" ) + { + particle = 181; + } + else if ( color == "slime red" ) + { + particle = 182; + } + else if ( color == "slime tar" ) + { + particle = 183; + } + else if ( color == "slime metal" ) + { + particle = 184; + } + spellTimer = createParticleTimer(my, 30, -1); + spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_MAGIC_SPRAY; + spellTimer->particleTimerCountdownSprite = particle; + } + else + { + if ( color == "slime green" ) + { + spellTimer = castSpell(my->getUID(), &spell_slime_acid, true, false); + } + else if ( color == "slime blue" ) + { + spellTimer = castSpell(my->getUID(), &spell_slime_water, true, false); + } + else if ( color == "slime red" ) + { + spellTimer = castSpell(my->getUID(), &spell_slime_fire, true, false); + } + else if ( color == "slime tar" ) + { + spellTimer = castSpell(my->getUID(), &spell_slime_tar, true, false); + } + else if ( color == "slime metal" ) + { + spellTimer = castSpell(my->getUID(), &spell_slime_metal, true, false); + } + } - Entity* spellTimer = createParticleTimer(my, 100, -1); + if ( spellTimer ) + { spellTimer->particleTimerPreDelay = slimeSprayDelay; - spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_MAGIC_SPRAY; - spellTimer->particleTimerCountdownSprite = 180 + local_rng.rand() % 5; + spellTimer->particleTimerDuration += slimeSprayDelay; + + createParticleDot(my); + + // play casting sound + playSoundEntityLocal(my, 170, 64); + // monster scream + playSoundEntityLocal(my, MONSTER_SPOTSND, 128); + } } void slimeAnimate(Entity* my, Stat* myStats, double dist) @@ -325,7 +385,7 @@ void slimeAnimate(Entity* my, Stat* myStats, double dist) if ( Stat* myStats = my->getStats() ) { myStats->EFFECTS[EFF_STUNNED] = true; - myStats->EFFECTS_TIMERS[EFF_STUNNED] = slimeSprayDelay; + myStats->EFFECTS_TIMERS[EFF_STUNNED] = slimeSprayDelay / 2; } } slimeSprayAttack(my); @@ -334,10 +394,10 @@ void slimeAnimate(Entity* my, Stat* myStats, double dist) const real_t squishRate = 3.0; real_t squishFactor = 0.3; - const int interval1 = (TICKS_PER_SECOND); - const int interval2 = (TICKS_PER_SECOND) + 8; - const int interval3 = 2 * TICKS_PER_SECOND - 10; - const int interval4 = 2 * TICKS_PER_SECOND + 25; + const int interval1 = (TICKS_PER_SECOND) - slimeSprayDelayOffset; + const int interval2 = (TICKS_PER_SECOND) + 8 - slimeSprayDelayOffset; + const int interval3 = 2 * TICKS_PER_SECOND - 10 - slimeSprayDelayOffset; + const int interval4 = 2 * TICKS_PER_SECOND + 25 - slimeSprayDelayOffset; if ( MONSTER_ATTACKTIME < interval1 ) { @@ -571,3 +631,54 @@ void slimeDie(Entity* my) list_RemoveNode(my->mynode); return; } + +void Entity::slimeChooseWeapon(const Entity* target, double dist) +{ + Stat* myStats = getStats(); + if ( !myStats ) + { + return; + } + + if ( monsterSpecialState != 0 && monsterSpecialTimer != 0 ) + { + return; + } + + if ( monsterSpecialTimer == 0 + && (ticks % 10 == 0) + && (monsterAttack == 0 || ((monsterAttack == 1) && monsterAttackTime >= 25)) + && dist < 48 ) + { + Stat* targetStats = target->getStats(); + if ( !targetStats ) + { + return; + } + + // try to charm enemy. + int specialRoll = -1; + int bonusFromHP = 0; + specialRoll = local_rng.rand() % 40; + if ( myStats->HP <= myStats->MAXHP * 0.8 ) + { + bonusFromHP += 2; // +% chance if on low health + } + if ( myStats->HP <= myStats->MAXHP * 0.4 ) + { + bonusFromHP += 3; // +extra % chance if on lower health + } + + int requiredRoll = (2 + bonusFromHP); + + if ( dist < STRIKERANGE ) + { + requiredRoll += 5; + } + + if ( specialRoll < requiredRoll ) + { + monsterSpecialState = SLIME_CAST; + } + } +} \ No newline at end of file From 8d8352e0c0a25fd96f4764eeb59ebeca9f8815c0 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 21:02:31 +1000 Subject: [PATCH 099/244] * dithered bright shader for player invis portrait * hud models dithered * add missing draw.hpp from solution --- VS.2015/Barony/Barony.vcxproj | 1 + src/acthudweapon.cpp | 114 +++++++++++++++++++++++++++++----- src/actplayer.cpp | 13 ++-- src/draw.cpp | 50 +++++++++++++++ src/draw.hpp | 1 + src/magic/act_HandMagic.cpp | 101 ++++++++++++++++-------------- src/opengl.cpp | 7 ++- src/ui/GameUI.cpp | 69 +++++++++++++++++--- 8 files changed, 280 insertions(+), 76 deletions(-) diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index c72244ef9..29ddf95a9 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -1247,6 +1247,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% + diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp index 01e5c87c9..ce7a5dbf7 100644 --- a/src/acthudweapon.cpp +++ b/src/acthudweapon.cpp @@ -102,6 +102,7 @@ void actHudArm(Entity* my) if ( HUD_SHAPESHIFT_HIDE > 0 ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; --HUD_SHAPESHIFT_HIDE; } } @@ -364,6 +365,7 @@ void actHudWeapon(Entity* my) if ( intro == true ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; return; } @@ -372,6 +374,7 @@ void actHudWeapon(Entity* my) if ( stats[HUDWEAPON_PLAYERNUM]->HP <= 0 ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; return; } } @@ -427,9 +430,11 @@ void actHudWeapon(Entity* my) if ( players[HUDWEAPON_PLAYERNUM]->movement.isPlayerSwimming() || players[HUDWEAPON_PLAYERNUM]->entity->skill[13] != 0 ) //skill[13] PLAYER_INWATER { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; if (parent) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; } return; } @@ -491,12 +496,15 @@ void actHudWeapon(Entity* my) } } - if ( players[HUDWEAPON_PLAYERNUM]->entity->skill[3] == 1 || players[HUDWEAPON_PLAYERNUM]->entity->isInvisible() ) // debug cam or player invisible + my->flags[INVISIBLE_DITHER] = false; + + if ( players[HUDWEAPON_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam or player invisible { my->flags[INVISIBLE] = true; if (parent != nullptr) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; } } else @@ -506,7 +514,16 @@ void actHudWeapon(Entity* my) my->flags[INVISIBLE] = true; if (parent != nullptr) { - parent->flags[INVISIBLE] = false; + if ( players[HUDWEAPON_PLAYERNUM]->entity->isInvisible() ) + { + parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = true; + } + else + { + parent->flags[INVISIBLE] = false; + parent->flags[INVISIBLE_DITHER] = false; + } } } else @@ -628,6 +645,7 @@ void actHudWeapon(Entity* my) if ( parent != NULL ) { parent->flags[INVISIBLE] = false; + parent->flags[INVISIBLE_DITHER] = false; } } else @@ -636,6 +654,12 @@ void actHudWeapon(Entity* my) if ( parent != NULL ) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; + } + if ( players[HUDWEAPON_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = true; } } } @@ -645,9 +669,11 @@ void actHudWeapon(Entity* my) if ( HUD_SHAPESHIFT_HIDE > 0 && HUD_SHAPESHIFT_HIDE < 2 ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; if ( parent != NULL ) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; } } @@ -656,9 +682,11 @@ void actHudWeapon(Entity* my) if ( playerRace != RAT ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; if (parent != NULL) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; } } } @@ -995,9 +1023,19 @@ void actHudWeapon(Entity* my) if ( playerGreasyDropItem(HUDWEAPON_PLAYERNUM, stats[HUDWEAPON_PLAYERNUM]->weapon) ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; if ( parent != nullptr ) { - parent->flags[INVISIBLE] = false; + if ( players[HUDWEAPON_PLAYERNUM]->entity->isInvisible() ) + { + parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = true; + } + else + { + parent->flags[INVISIBLE] = false; + parent->flags[INVISIBLE_DITHER] = false; + } } return; } @@ -3257,6 +3295,8 @@ void actHudShield(Entity* my) auto& camera_shakex2 = cameravars[HUDSHIELD_PLAYERNUM].shakex2; auto& camera_shakey2 = cameravars[HUDSHIELD_PLAYERNUM].shakey2; + my->flags[INVISIBLE_DITHER] = false; + // isn't active during intro/menu sequence if (intro == true) { @@ -3335,7 +3375,7 @@ void actHudShield(Entity* my) } HUD_LASTSHAPESHIFT_FORM = playerRace; - if ( players[HUDSHIELD_PLAYERNUM]->entity->skill[3] == 1 || players[HUDSHIELD_PLAYERNUM]->entity->isInvisible() ) // debug cam or player invisible + if ( players[HUDSHIELD_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam or player invisible { my->flags[INVISIBLE] = true; } @@ -3379,6 +3419,11 @@ void actHudShield(Entity* my) } my->sprite = itemModelFirstperson(stats[HUDSHIELD_PLAYERNUM]->shield); my->flags[INVISIBLE] = false; + if ( players[HUDSHIELD_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = true; + } } } @@ -3389,10 +3434,12 @@ void actHudShield(Entity* my) if ( players[HUDSHIELD_PLAYERNUM]->movement.isPlayerSwimming() || players[HUDSHIELD_PLAYERNUM]->entity->skill[13] != 0 ) //skill[13] PLAYER_INWATER { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; Entity* parent = uidToEntity(my->parent); if ( parent ) { parent->flags[INVISIBLE] = true; + parent->flags[INVISIBLE_DITHER] = false; } swimming = true; } @@ -3401,14 +3448,17 @@ void actHudShield(Entity* my) if ( hideShield ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } else if ( cast_animation[HUDSHIELD_PLAYERNUM].active ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } else if ( cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook && !spellbook ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } bool defending = false; @@ -3528,6 +3578,7 @@ void actHudShield(Entity* my) if ( dropShield ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; return; } @@ -3667,6 +3718,7 @@ void actHudShield(Entity* my) if ( hudweapon->skill[7] == RANGED_ANIM_FIRED && (!crossbow || (crossbow && hudweapon->skill[8] == CROSSBOW_ANIM_SHOOT)) ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; HUDSHIELD_MOVEY = 0; HUDSHIELD_PITCH = 0; HUDSHIELD_YAW = 0; @@ -3742,6 +3794,7 @@ void actHudShield(Entity* my) { players[HUDSHIELD_PLAYERNUM]->hud.throwGimpTimer = std::max(players[HUDSHIELD_PLAYERNUM]->hud.throwGimpTimer, 20); my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; HUDSHIELD_MOVEY = 0; HUDSHIELD_PITCH = 0; HUDSHIELD_YAW = 0; @@ -4016,6 +4069,8 @@ void actHudAdditional(Entity* my) auto& camera_shakex2 = cameravars[HUDSHIELD_PLAYERNUM].shakex2; auto& camera_shakey2 = cameravars[HUDSHIELD_PLAYERNUM].shakey2; + my->flags[INVISIBLE_DITHER] = false; + // isn't active during intro/menu sequence if ( intro == true ) { @@ -4045,19 +4100,16 @@ void actHudAdditional(Entity* my) return; } - if ( !players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(2) - || players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(2)->flags[INVISIBLE] - || players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(2)->sprite == 854 - || players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(2)->sprite == 1006 ) + Entity* shieldLimb = nullptr; + if ( players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.size() > 2 ) { - // if shield invisible or spider arm we're invis. - my->flags[INVISIBLE] = true; - return; + shieldLimb = players[HUDSHIELD_PLAYERNUM]->entity->bodyparts.at(2); } if ( stats[HUDSHIELD_PLAYERNUM]->shield == nullptr ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } else { @@ -4084,15 +4136,38 @@ void actHudAdditional(Entity* my) } my->sprite = itemModelFirstperson(stats[HUDSHIELD_PLAYERNUM]->shield); my->flags[INVISIBLE] = false; + if ( shieldLimb && shieldLimb->flags[INVISIBLE] ) + { + // if shield invisible we're invis. + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; + if ( players[HUDSHIELD_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE_DITHER] = shieldLimb->flags[INVISIBLE_DITHER]; + } + } + } + + + if ( !shieldLimb + || shieldLimb->sprite == 854 + || shieldLimb->sprite == 1006 ) + { + // if spider arm we're invis. + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; + return; } if ( cast_animation[HUDSHIELD_PLAYERNUM].active ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } else if ( cast_animation[HUDSHIELD_PLAYERNUM].active_spellbook && !spellbook ) { my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; } bool defending = false; @@ -4245,6 +4320,8 @@ void actHudArrowModel(Entity* my) my->flags[UNCLICKABLE] = true; + my->flags[INVISIBLE_DITHER] = false; + // isn't active during intro/menu sequence if ( intro == true ) { @@ -4278,7 +4355,7 @@ void actHudArrowModel(Entity* my) if ( crossbow ) { - if ( hudweapon->flags[INVISIBLE] || hudweapon->skill[6] != 0 ) // skill[6] is hideWeapon + if ( hudweapon->skill[6] != 0 ) // skill[6] is hideWeapon { my->flags[INVISIBLE] = true; return; @@ -4305,8 +4382,7 @@ void actHudArrowModel(Entity* my) } } } - else if ( hudweapon->flags[INVISIBLE] - || hudweapon->skill[6] != 0 + else if ( hudweapon->skill[6] != 0 || hudweapon->skill[7] != RANGED_ANIM_FIRED ) // skill[6] is hiding weapon, skill[7] is shooting something { my->flags[INVISIBLE] = true; @@ -4318,6 +4394,16 @@ void actHudArrowModel(Entity* my) my->scalez = 1.f; my->flags[INVISIBLE] = false; + if ( hudweapon->flags[INVISIBLE] ) + { + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = false; + if ( players[HUDSHIELD_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE_DITHER] = hudweapon->flags[INVISIBLE_DITHER]; + } + } + my->sprite = 934; if ( crossbow ) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 2989ccb3d..b77b16cc1 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -8352,7 +8352,7 @@ void actPlayer(Entity* my) { Entity* shield = (Entity*)shieldNode->element; bool bendArm = true; - if ( shield->flags[INVISIBLE] ) + if ( shield->flags[INVISIBLE] && !shield->flags[INVISIBLE_DITHER] ) { bendArm = false; } @@ -9095,7 +9095,7 @@ void actPlayer(Entity* my) if ( tempNode ) { Entity* weapon = (Entity*)tempNode->element; - if ( weapon->flags[INVISIBLE] || PLAYER_ARMBENDED || playerRace == CREATURE_IMP ) + if ( (weapon->flags[INVISIBLE] && !weapon->flags[INVISIBLE_DITHER]) || PLAYER_ARMBENDED || playerRace == CREATURE_IMP ) { if ( playerRace == INCUBUS || playerRace == SUCCUBUS ) { @@ -9215,7 +9215,7 @@ void actPlayer(Entity* my) { Entity* shield = (Entity*)tempNode->element; bool bendArm = true; - if ( shield->flags[INVISIBLE] ) + if ( shield->flags[INVISIBLE] && !shield->flags[INVISIBLE_DITHER] ) { bendArm = false; if ( insectoidLevitating ) @@ -9357,6 +9357,7 @@ void actPlayer(Entity* my) if ( entity->sprite <= 0 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } my->handleHumanoidWeaponLimb(entity, weaponarm); @@ -9428,6 +9429,7 @@ void actPlayer(Entity* my) if ( entity->sprite <= 0 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } my->handleHumanoidShieldLimb(entity, shieldarm); @@ -9490,6 +9492,7 @@ void actPlayer(Entity* my) if ( entity->sprite <= 0 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } @@ -9589,6 +9592,7 @@ void actPlayer(Entity* my) if ( entity->sprite <= 0 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } my->setHelmetLimbOffset(entity); @@ -9665,6 +9669,7 @@ void actPlayer(Entity* my) if ( entity->sprite <= 0 ) { entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = false; } } entity->scalex = 0.99; @@ -11062,7 +11067,7 @@ void playerAnimateSpider(Entity* my) } entity->flags[INVISIBLE] = my->flags[INVISIBLE]; - entity->flags[INVISIBLE_DITHER] = true; + entity->flags[INVISIBLE_DITHER] = entity->flags[INVISIBLE]; switch ( bodypart ) { diff --git a/src/draw.cpp b/src/draw.cpp index 2e03e885e..6092b101e 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -70,6 +70,7 @@ Shader framebuffer::hdrShader; Shader voxelShader; Shader voxelBrightShader; Shader voxelDitheredShader; +Shader voxelBrightDitheredShader; Shader worldShader; Shader worldDitheredShader; Shader worldDarkShader; @@ -363,6 +364,54 @@ void createCommonDrawResources() { buildVoxelShader(voxelDitheredShader, "voxelDitheredShader", true, vox_vertex_glsl, sizeof(vox_vertex_glsl), vox_dithered_fragment_glsl, sizeof(vox_dithered_fragment_glsl)); + + static const char vox_bright_dithered_fragment_glsl[] = + "in vec3 Color;" + "in vec3 Normal;" + "in vec4 WorldPos;" + "uniform float uDitherAmount;" + "uniform mat4 uColorRemap;" + "uniform vec4 uLightFactor;" + "uniform vec4 uLightColor;" + "uniform vec4 uColorAdd;" + "uniform vec4 uCameraPos;" + "uniform sampler2D uLightmap;" + "uniform vec2 uMapDims;" + "uniform float uFogDistance;" + "uniform vec4 uFogColor;" + "out vec4 FragColor;" + + "void dither(ivec2 pos, float amount) {" + "if (amount > 1.0) {" + "int d = int(amount) - 1;" + "if ((pos.x & d) == 0 && (pos.y & d) == 0) { discard; }" + "} else if (amount == 1.0) {" + "if (((pos.x + pos.y) & 1) == 0) { discard; }" + "} else if (amount < 1.0) {" + "int d = int(1.0 / amount) - 1;" + "if ((pos.x & d) != 0 || (pos.y & d) != 0) { discard; }" + "}" + "}" + + "void main() {" + "dither(ivec2(gl_FragCoord), uDitherAmount);" + "vec3 Remapped =" + " (uColorRemap[0].rgb * Color.r)+" + " (uColorRemap[1].rgb * Color.g)+" + " (uColorRemap[2].rgb * Color.b);" + "FragColor = vec4(Remapped, 1.0) * uLightFactor * uLightColor + uColorAdd;" + + "if (uFogDistance > 0.0) {" + "float dist = length(uCameraPos.xyz - WorldPos.xyz);" + "float lerp = (min(dist, uFogDistance) / uFogDistance) * uFogColor.a;" + "vec3 mixed = mix(FragColor.rgb, uFogColor.rgb, lerp);" + "FragColor = vec4(mixed, FragColor.a);" + "}" + "}"; + + buildVoxelShader(voxelBrightDitheredShader, "voxelBrightDitheredShader", true, + vox_vertex_glsl, sizeof(vox_vertex_glsl), + vox_bright_dithered_fragment_glsl, sizeof(vox_bright_dithered_fragment_glsl)); // world shader: @@ -658,6 +707,7 @@ void destroyCommonDrawResources() { voxelShader.destroy(); voxelBrightShader.destroy(); voxelDitheredShader.destroy(); + voxelBrightDitheredShader.destroy(); worldShader.destroy(); worldDitheredShader.destroy(); worldDarkShader.destroy(); diff --git a/src/draw.hpp b/src/draw.hpp index f1d43fc46..4819d7ebe 100644 --- a/src/draw.hpp +++ b/src/draw.hpp @@ -309,6 +309,7 @@ extern framebuffer main_framebuffer; extern Shader voxelShader; extern Shader voxelBrightShader; extern Shader voxelDitheredShader; +extern Shader voxelBrightDitheredShader; extern Shader worldShader; extern Shader worldDitheredShader; extern Shader worldDarkShader; diff --git a/src/magic/act_HandMagic.cpp b/src/magic/act_HandMagic.cpp index a748df6c1..901feac9b 100644 --- a/src/magic/act_HandMagic.cpp +++ b/src/magic/act_HandMagic.cpp @@ -86,13 +86,25 @@ void fireOffSpellAnimation(spellcasting_animation_manager_t* animation_manager, animation_manager->stage = CIRCLE; //Make the HUDWEAPON disappear, or somesuch? + players[player]->hud.magicLeftHand->flags[INVISIBLE_DITHER] = false; + players[player]->hud.magicRightHand->flags[INVISIBLE_DITHER] = false; if ( stat->type != RAT ) { if ( !usingSpellbook ) { players[player]->hud.magicLeftHand->flags[INVISIBLE] = false; + if ( caster->isInvisible() ) + { + players[player]->hud.magicLeftHand->flags[INVISIBLE] = true; + players[player]->hud.magicLeftHand->flags[INVISIBLE_DITHER] = true; + } } players[player]->hud.magicRightHand->flags[INVISIBLE] = false; + if ( caster->isInvisible() ) + { + players[player]->hud.magicRightHand->flags[INVISIBLE] = true; + players[player]->hud.magicRightHand->flags[INVISIBLE_DITHER] = true; + } } animation_manager->lefthand_angle = 0; @@ -160,10 +172,12 @@ void spellcastingAnimationManager_deactivate(spellcasting_animation_manager_t* a if ( players[animation_manager->player]->hud.magicLeftHand ) { players[animation_manager->player]->hud.magicLeftHand->flags[INVISIBLE] = true; + players[animation_manager->player]->hud.magicLeftHand->flags[INVISIBLE_DITHER] = false; } if ( players[animation_manager->player]->hud.magicRightHand ) { players[animation_manager->player]->hud.magicRightHand->flags[INVISIBLE] = true; + players[animation_manager->player]->hud.magicRightHand->flags[INVISIBLE_DITHER] = false; } } @@ -184,6 +198,7 @@ void spellcastingAnimationManager_completeSpell(spellcasting_animation_manager_t void actLeftHandMagic(Entity* my) { //int c = 0; + my->flags[INVISIBLE_DITHER] = false; if (intro == true) { my->flags[INVISIBLE] = true; @@ -369,14 +384,6 @@ void actLeftHandMagic(Entity* my) }*/ } - if ( playerRace == RAT ) - { - my->flags[INVISIBLE] = true; - my->y = 0; - my->z += 1; - } - - Entity*& hudarm = players[HANDMAGIC_PLAYERNUM]->hud.arm; if ( playerRace == SPIDER && hudarm && players[HANDMAGIC_PLAYERNUM]->entity->bodyparts.at(0) ) { @@ -396,26 +403,27 @@ void actLeftHandMagic(Entity* my) my->focalz = -1.5; } - bool wearingring = false; - - //Select model - if (stats[HANDMAGIC_PLAYERNUM]->ring != NULL) + if ( !cast_animation[HANDMAGIC_PLAYERNUM].active ) { - if (stats[HANDMAGIC_PLAYERNUM]->ring->type == RING_INVISIBILITY) - { - wearingring = true; - } + my->flags[INVISIBLE] = true; } - if (stats[HANDMAGIC_PLAYERNUM]->cloak != NULL) + else { - if (stats[HANDMAGIC_PLAYERNUM]->cloak->type == CLOAK_INVISIBILITY) + if ( playerRace == RAT ) { - wearingring = true; + my->flags[INVISIBLE] = true; + my->y = 0; + my->z += 1; + } + else if ( players[HANDMAGIC_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam + { + my->flags[INVISIBLE] = true; + } + else if ( players[HANDMAGIC_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = true; } - } - if (players[HANDMAGIC_PLAYERNUM]->entity->skill[3] == 1 || players[HANDMAGIC_PLAYERNUM]->entity->isInvisible() ) // debug cam or player invisible - { - my->flags[INVISIBLE] = true; } if ( (cast_animation[HANDMAGIC_PLAYERNUM].active || cast_animation[HANDMAGIC_PLAYERNUM].active_spellbook) ) @@ -546,6 +554,7 @@ void actLeftHandMagic(Entity* my) void actRightHandMagic(Entity* my) { + my->flags[INVISIBLE_DITHER] = false; if (intro == true) { my->flags[INVISIBLE] = true; @@ -725,11 +734,29 @@ void actRightHandMagic(Entity* my) }*/ } - if ( playerRace == RAT ) + if ( !(cast_animation[HANDMAGIC_PLAYERNUM].active || cast_animation[HANDMAGIC_PLAYERNUM].active_spellbook) ) { my->flags[INVISIBLE] = true; - my->y = 0; - my->z += 1; + } + else + { + my->flags[INVISIBLE] = false; + + if ( playerRace == RAT ) + { + my->flags[INVISIBLE] = true; + my->y = 0; + my->z += 1; + } + else if ( players[HANDMAGIC_PLAYERNUM]->entity->skill[3] == 1 ) // debug cam + { + my->flags[INVISIBLE] = true; + } + else if ( players[HANDMAGIC_PLAYERNUM]->entity->isInvisible() ) + { + my->flags[INVISIBLE] = true; + my->flags[INVISIBLE_DITHER] = true; + } } Entity*& hudarm = players[HANDMAGIC_PLAYERNUM]->hud.arm; @@ -752,28 +779,6 @@ void actRightHandMagic(Entity* my) my->focalz = -1.5; } - bool wearingring = false; - - //Select model - if (stats[HANDMAGIC_PLAYERNUM]->ring != NULL) - { - if (stats[HANDMAGIC_PLAYERNUM]->ring->type == RING_INVISIBILITY) - { - wearingring = true; - } - } - if (stats[HANDMAGIC_PLAYERNUM]->cloak != NULL) - { - if (stats[HANDMAGIC_PLAYERNUM]->cloak->type == CLOAK_INVISIBILITY) - { - wearingring = true; - } - } - if ( players[HANDMAGIC_PLAYERNUM]->entity->skill[3] == 1 || players[HANDMAGIC_PLAYERNUM]->entity->isInvisible() ) // debug cam or player invisible - { - my->flags[INVISIBLE] = true; - } - if ( (cast_animation[HANDMAGIC_PLAYERNUM].active || cast_animation[HANDMAGIC_PLAYERNUM].active_spellbook) ) { switch ( cast_animation[HANDMAGIC_PLAYERNUM].stage) diff --git a/src/opengl.cpp b/src/opengl.cpp index 0fe9510ed..3f75de3da 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -1029,6 +1029,7 @@ void glBeginCamera(view_t* camera, bool useHDR, map_t& map) uploadUniforms(voxelShader, (float*)&proj, (float*)&view, (float*)&mapDims); uploadUniforms(voxelBrightShader, (float*)&proj, (float*)&view, nullptr); uploadUniforms(voxelDitheredShader, (float*)&proj, (float*)&view, (float*)&mapDims); + uploadUniforms(voxelBrightDitheredShader, (float*)&proj, (float*)&view, (float*)&mapDims); uploadUniforms(worldShader, (float*)&proj, (float*)&view, (float*)&mapDims); uploadUniforms(worldDitheredShader, (float*)&proj, (float*)&view, (float*)&mapDims); uploadUniforms(worldDarkShader, (float*)&proj, (float*)&view, nullptr); @@ -1235,12 +1236,12 @@ void glDrawVoxel(view_t* camera, Entity* entity, int mode) { // bind shader auto& dither = entity->dithering[camera]; auto& shader = !entity->flags[BRIGHT] && !telepath ? - (dither.value < Entity::Dither::MAX ? voxelDitheredShader : voxelShader): - voxelBrightShader; + (dither.value < Entity::Dither::MAX ? voxelDitheredShader : voxelShader) : + ((entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] && dither.value < Entity::Dither::MAX) ? voxelBrightDitheredShader : voxelBrightShader); shader.bind(); // upload dither amount, if necessary - if (&shader == &voxelDitheredShader) { + if (&shader == &voxelDitheredShader || &shader == &voxelBrightDitheredShader) { GL_CHECK_ERR(glUniform1f(shader.uniform("uDitherAmount"), (float)((uint32_t)1 << (dither.value - 1)) / (1 << (Entity::Dither::MAX / 2 - 1)))); } diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index e4fc827c9..7c09f3467 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -22648,13 +22648,22 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset view.winw = pos.w; view.winh = pos.h; glBeginCamera(&view, false, map); - bool b = playerEntity->flags[BRIGHT]; - if (!dark) { playerEntity->flags[BRIGHT] = true; } - if ( !playerEntity->flags[INVISIBLE] ) + const int ditherVal = 5; + if ( !playerEntity->flags[INVISIBLE] || (playerEntity->flags[INVISIBLE] && playerEntity->flags[INVISIBLE_DITHER]) ) { + bool b = playerEntity->flags[BRIGHT]; + if (!dark) { playerEntity->flags[BRIGHT] = true; } + + int oldDither = playerEntity->dithering[&view].value; + if ( (playerEntity->flags[INVISIBLE] && playerEntity->flags[INVISIBLE_DITHER]) ) + { + playerEntity->dithering[&view].value = ditherVal; + //playerEntity->flags[BRIGHT] = false; + } glDrawVoxel(&view, playerEntity, REALCOLORS); + playerEntity->flags[BRIGHT] = b; + playerEntity->dithering[&view].value = oldDither; } - playerEntity->flags[BRIGHT] = b; int c = 0; if ( multiplayer != CLIENT ) { @@ -22669,12 +22678,20 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset } } Entity* entity = (Entity*)node->element; - if ( !entity->flags[INVISIBLE] ) + if ( !entity->flags[INVISIBLE] || (entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER]) ) { bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } + + int oldDither = entity->dithering[&view].value; + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + entity->dithering[&view].value = ditherVal; + //entity->flags[BRIGHT] = false; + } glDrawVoxel(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + entity->dithering[&view].value = oldDither; } c++; } @@ -22689,8 +22706,16 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset } bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } + + int oldDither = entity->dithering[&view].value; + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + entity->dithering[&view].value = ditherVal; + //entity->flags[BRIGHT] = false; + } glDrawSprite(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + entity->dithering[&view].value = oldDither; } } } @@ -22701,7 +22726,8 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset Entity* entity = (Entity*)node->element; if ( playerEntity->behavior == &actPlayer ) { - if ( (entity->behavior == &actPlayerLimb && entity->skill[2] == player && !entity->flags[INVISIBLE]) || (Sint32)entity->getUID() == -4 ) + if ( (entity->behavior == &actPlayerLimb && entity->skill[2] == player + && (!entity->flags[INVISIBLE] || (entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER]))) || (Sint32)entity->getUID() == -4 ) { if ( (Sint32)entity->getUID() == -4 ) // torch sprites { @@ -22711,26 +22737,55 @@ void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offset } bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } + + int oldDither = entity->dithering[&view].value; + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + entity->dithering[&view].value = ditherVal; + //entity->flags[BRIGHT] = false; + } + glDrawSprite(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + entity->dithering[&view].value = oldDither; } else { bool b = entity->flags[BRIGHT]; if (!dark) { entity->flags[BRIGHT] = true; } + + int oldDither = entity->dithering[&view].value; + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + entity->dithering[&view].value = ditherVal; + //entity->flags[BRIGHT] = false; + } + glDrawVoxel(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + entity->dithering[&view].value = oldDither; } + } } else if ( playerEntity->behavior == &actDeathGhost ) { - if ( entity->behavior == &actDeathGhostLimb && entity->skill[2] == player && !entity->flags[INVISIBLE] ) + if ( entity->behavior == &actDeathGhostLimb && entity->skill[2] == player + && (!entity->flags[INVISIBLE] || (entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER])) ) { bool b = entity->flags[BRIGHT]; if ( !dark ) { entity->flags[BRIGHT] = true; } + + int oldDither = entity->dithering[&view].value; + if ( entity->flags[INVISIBLE] && entity->flags[INVISIBLE_DITHER] ) + { + entity->dithering[&view].value = ditherVal; + //entity->flags[BRIGHT] = false; + } + glDrawVoxel(&view, entity, REALCOLORS); entity->flags[BRIGHT] = b; + entity->dithering[&view].value = oldDither; } } } From 6a3841a88e9d09580b6d012a324da6f2aebaf1ac Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 21:02:47 +1000 Subject: [PATCH 100/244] * map hash update --- src/files.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/files.cpp b/src/files.cpp index 4cc2b1107..d747dfbbc 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -937,8 +937,8 @@ std::unordered_map mapHashes = { { "ruins19e.lmp", 359371 }, { "ruins20.lmp", 239795 }, { "ruins20a.lmp", 39623 }, - { "ruins20b.lmp", 170787 }, - { "ruins20c.lmp", 406127 }, + { "ruins20b.lmp", 152619 }, + { "ruins20c.lmp", 416612 }, { "ruins20d.lmp", 57511 }, { "ruins20e.lmp", 369845 }, { "ruins21.lmp", 71662 }, From 1467b28e83c0635f817c7a006841829387f5a01f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 21:03:12 +1000 Subject: [PATCH 101/244] * book with no matching name in editor will spawn randomly instead of always 0 --- src/maps.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/maps.cpp b/src/maps.cpp index 7ea855864..45c2f81b1 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -7562,7 +7562,22 @@ void assignActions(map_t* map) } strcpy(buf, output.c_str()); - entity->skill[14] = getBook(buf); + int index = -1; + bool foundBook = false; + for ( auto& book : allBooks ) + { + ++index; + if ( book.default_name == buf ) + { + foundBook = true; + entity->skill[14] = getBook(buf); + break; + } + } + if ( !foundBook && allBooks.size() > 0 ) + { + entity->skill[14] = map_rng.rand() % allBooks.size(); + } if ( entity->skill[15] == 1 ) // editor set as identified { From 6776737f9f919df987724c64d6380b42274c0a12 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 31 Aug 2024 22:42:57 +1000 Subject: [PATCH 102/244] * achievements sort unlocked first toggle --- src/init_game.cpp | 43 +++++++++++++++++++++++--- src/mod_tools.cpp | 3 +- src/mod_tools.hpp | 1 + src/ui/MainMenu.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/init_game.cpp b/src/init_game.cpp index 7623f0b13..214b2b653 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -984,10 +984,18 @@ void sortAchievementsForDisplay() { if ( ach1 && !ach2 ) { + if ( Compendium_t::compendium_sorting_hide_ach_unlocked ) + { + return false; + } return true; } else if ( !ach1 && ach2 ) { + if ( Compendium_t::compendium_sorting_hide_ach_unlocked ) + { + return true; + } return false; } else if ( !ach1 && !ach2 && (lhsAchIsHidden || rhsAchIsHidden) ) @@ -1045,6 +1053,22 @@ void sortAchievementsForDisplay() } else { + if ( ach1 && !ach2 ) + { + if ( Compendium_t::compendium_sorting_hide_ach_unlocked ) + { + return false; + } + return true; + } + else if ( !ach1 && ach2 ) + { + if ( Compendium_t::compendium_sorting_hide_ach_unlocked ) + { + return true; + } + return false; + } return lhs.second < rhs.second; } } @@ -1077,12 +1101,23 @@ void sortAchievementsForDisplay() auto& achData = Compendium_t::achievements[name.first]; if ( foundHidden ) { - if ( achData.hidden && !achData.unlocked ) + if ( Compendium_t::compendium_sorting_hide_ach_unlocked ) { - achDisplay.numHidden++; + if ( achData.hidden && !achData.unlocked ) + { + achDisplay.numHidden++; + continue; + } + } + else + { + if ( achData.hidden && !achData.unlocked ) + { + achDisplay.numHidden++; + } + // hidden, so allow only 1 entry to represent all the hidden ones + continue; } - // hidden, so allow only 1 entry to represent all the hidden ones - continue; } ++numEntries; if ( numEntries > 1 && ((numEntries - 1) % 8 == 0) ) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 54ac7639a..a3fb8c6a6 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -16262,4 +16262,5 @@ std::map Compendium_t::AchievementData_t::achievementUnlockedLookup; bool Compendium_t::AchievementData_t::sortAlphabetical = false; std::string Compendium_t::compendium_sorting = "default"; -bool Compendium_t::compendium_sorting_hide_undiscovered = false; \ No newline at end of file +bool Compendium_t::compendium_sorting_hide_undiscovered = false; +bool Compendium_t::compendium_sorting_hide_ach_unlocked = false; \ No newline at end of file diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 31329c6b6..fae7d3399 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3607,6 +3607,7 @@ struct Compendium_t static std::unordered_map achievements; static std::string compendium_sorting; static bool compendium_sorting_hide_undiscovered; + static bool compendium_sorting_hide_ach_unlocked; enum EventTags { diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index a93ab7afb..e0a830a78 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -37432,7 +37432,6 @@ namespace MainMenu { (int)cvar_compendium_achievement_unlock->z); - auto& achCategories = Compendium_t::AchievementData_t::achievementCategories[name]; auto& achDisplay = Compendium_t::AchievementData_t::achievementsBookDisplay[name]; achDisplay.currentPage = std::min(achDisplay.currentPage, (int)achDisplay.pages.size() * 2); @@ -39503,6 +39502,13 @@ namespace MainMenu { compendium_current = "monsters"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -39639,6 +39645,13 @@ namespace MainMenu { compendium_current = "items"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -39746,6 +39759,13 @@ namespace MainMenu { compendium_current = "magic"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -39853,6 +39873,13 @@ namespace MainMenu { compendium_current = "world"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -39961,6 +39988,13 @@ namespace MainMenu { compendium_current = "codex"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -40118,6 +40152,13 @@ namespace MainMenu { compendium_current = "achievements"; if ( auto frame = static_cast(button.getParent()) ) { + if ( auto nav_filters = frame->findFrame("nav_filters") ) + { + if ( auto nav_filter_btn2 = nav_filters->findButton("nav_filter_sort2") ) + { + nav_filter_btn2->setPressed(Compendium_t::compendium_sorting_hide_ach_unlocked); + } + } if ( auto page_right = frame->findFrame("page_right") ) { if ( auto page_right_inner = page_right->findFrame("page_right_inner") ) @@ -40336,7 +40377,14 @@ namespace MainMenu { nav_filter_sort2->setColor(0); nav_filter_sort2->setSelectorOffset(SDL_Rect{ 0, 2, -6, 0 }); nav_filter_sort2->setBackground(""); - nav_filter_sort2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + if ( compendium_current == "achievements" ) + { + nav_filter_sort2->setPressed(Compendium_t::compendium_sorting_hide_ach_unlocked); + } + else + { + nav_filter_sort2->setPressed(Compendium_t::compendium_sorting_hide_undiscovered); + } nav_filter_sort2->setWidgetSearchParent("compendium"); nav_filter_sort2->setWidgetUp("nav_filter_sort"); nav_filter_sort2->addWidgetAction("MenuPageLeft", "tab_left"); @@ -40384,7 +40432,15 @@ namespace MainMenu { { playSound(637, 128); } - Compendium_t::compendium_sorting_hide_undiscovered = button.isPressed(); + if ( compendium_current == "achievements" ) + { + Compendium_t::compendium_sorting_hide_ach_unlocked = button.isPressed(); + Compendium_t::AchievementData_t::achievementsNeedResort = true; + } + else + { + Compendium_t::compendium_sorting_hide_undiscovered = button.isPressed(); + } if ( auto parent = static_cast(button.getParent()) ) { if ( parent = parent->getParent() ) @@ -40420,6 +40476,17 @@ namespace MainMenu { nav_filter_sort_txt2->setHJustify(Field::justify_t::LEFT); nav_filter_sort_txt2->setVJustify(Field::justify_t::TOP); nav_filter_sort_txt2->setColor(makeColorRGB(220, 178, 113)); + nav_filter_sort_txt2->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); + if ( compendium_current == "achievements" ) + { + txt->setText(Language::get(6247)); + } + else + { + txt->setText(Language::get(6196)); + } + }); } auto contents = navigation->addFrame("contents"); From 2a98401c4a136f3602426e19d034ce5fda3e4f03 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 1 Sep 2024 21:37:52 +1000 Subject: [PATCH 103/244] * editor stuff - chest mimic chances, AND/NAND gates, pressure plate entity types --- src/buttons.cpp | 50 ++++- src/draw.cpp | 24 +++ src/editor.cpp | 339 ++++++++++++++++++++++++++++++- src/entity.cpp | 4 + src/entity.hpp | 21 +- src/entity_editor.cpp | 4 + src/entity_shared.cpp | 59 +++++- src/files.cpp | 65 +++++- src/game.hpp | 1 + src/maps.cpp | 38 +++- src/mechanisms.cpp | 461 ++++++++++++++++++++++++++++++++++++++++-- 11 files changed, 1030 insertions(+), 36 deletions(-) diff --git a/src/buttons.cpp b/src/buttons.cpp index e23c619f5..a75da9802 100644 --- a/src/buttons.cpp +++ b/src/buttons.cpp @@ -1817,6 +1817,7 @@ void buttonSpriteProperties(button_t* my) snprintf(spriteProperties[0], 4, "%d", static_cast(selectedEntity[0]->yaw)); snprintf(spriteProperties[1], 4, "%d", selectedEntity[0]->skill[9]); snprintf(spriteProperties[2], 4, "%d", selectedEntity[0]->chestLocked); + snprintf(spriteProperties[3], 4, "%d", selectedEntity[0]->chestMimicChance); inputstr = spriteProperties[0]; cursorflash = ticks; menuVisible = 0; @@ -1824,8 +1825,8 @@ void buttonSpriteProperties(button_t* my) newwindow = 3; subx1 = xres / 2 - 160; subx2 = xres / 2 + 160; - suby1 = yres / 2 - 105; - suby2 = yres / 2 + 105; + suby1 = yres / 2 - 125; + suby2 = yres / 2 + 125; strcpy(subtext, "Chest Properties:"); break; case 3: //items @@ -2137,6 +2138,7 @@ void buttonSpriteProperties(button_t* my) snprintf(spriteProperties[2], 5, "%d", static_cast(selectedEntity[0]->signalTimerInterval)); snprintf(spriteProperties[3], 5, "%d", static_cast(selectedEntity[0]->signalTimerRepeatCount)); snprintf(spriteProperties[4], 2, "%d", static_cast(selectedEntity[0]->signalTimerLatchInput)); + snprintf(spriteProperties[5], 2, "%d", static_cast(selectedEntity[0]->signalInvertOutput)); inputstr = spriteProperties[0]; cursorflash = ticks; menuVisible = 0; @@ -2373,6 +2375,37 @@ void buttonSpriteProperties(button_t* my) strcpy(subtext, "Collider Model Properties:"); break; } + case 28: + snprintf(spriteProperties[0], 2, "%d", static_cast(selectedEntity[0]->signalInputDirection)); + snprintf(spriteProperties[1], 5, "%d", static_cast(selectedEntity[0]->signalActivateDelay)); + snprintf(spriteProperties[2], 5, "%d", static_cast(selectedEntity[0]->signalTimerInterval)); + snprintf(spriteProperties[3], 5, "%d", static_cast(selectedEntity[0]->signalTimerRepeatCount)); + snprintf(spriteProperties[4], 2, "%d", static_cast(selectedEntity[0]->signalTimerLatchInput)); + snprintf(spriteProperties[5], 2, "%d", static_cast(selectedEntity[0]->signalInvertOutput)); + inputstr = spriteProperties[0]; + cursorflash = ticks; + menuVisible = 0; + subwindow = 1; + newwindow = 32; + subx1 = xres / 2 - 220; + subx2 = xres / 2 + 220; + suby1 = yres / 2 - 120; + suby2 = yres / 2 + 120; + strcpy(subtext, "AND Gate Properties:"); + break; + case 29: + snprintf(spriteProperties[0], 2, "%d", static_cast(selectedEntity[0]->pressurePlateTriggerType)); + inputstr = spriteProperties[0]; + cursorflash = ticks; + menuVisible = 0; + subwindow = 1; + newwindow = 33; + subx1 = xres / 2 - 170; + subx2 = xres / 2 + 170; + suby1 = yres / 2 - 60; + suby2 = yres / 2 + 60; + strcpy(subtext, "Pressure Plate Properties:"); + break; default: strcpy(message, "No properties available for current sprite."); messagetime = 60; @@ -3097,6 +3130,7 @@ void buttonSpritePropertiesConfirm(button_t* my) selectedEntity[0]->yaw = (real_t)atoi(spriteProperties[0]); selectedEntity[0]->skill[9] = (Sint32)atoi(spriteProperties[1]); selectedEntity[0]->chestLocked = (Sint32)atoi(spriteProperties[2]); + selectedEntity[0]->chestMimicChance = (Sint32)atoi(spriteProperties[3]); break; case 3: //items if ( strcmp(spriteProperties[0], "0") == 0 ) @@ -3368,6 +3402,7 @@ void buttonSpritePropertiesConfirm(button_t* my) selectedEntity[0]->signalTimerInterval = (Sint32)atoi(spriteProperties[2]); selectedEntity[0]->signalTimerRepeatCount = (Sint32)atoi(spriteProperties[3]); selectedEntity[0]->signalTimerLatchInput = (Sint32)atoi(spriteProperties[4]); + selectedEntity[0]->signalInvertOutput = (Sint32)atoi(spriteProperties[5]); break; case 18: // custom portal { @@ -3478,6 +3513,17 @@ void buttonSpritePropertiesConfirm(button_t* my) selectedEntity[0]->colliderDiggable = (Sint32)atoi(spriteProperties[9]); selectedEntity[0]->colliderDamageTypes = (Sint32)atoi(spriteProperties[10]); break; + case 28: + selectedEntity[0]->signalInputDirection = (Sint32)atoi(spriteProperties[0]); + selectedEntity[0]->signalActivateDelay = (Sint32)atoi(spriteProperties[1]); + selectedEntity[0]->signalTimerInterval = (Sint32)atoi(spriteProperties[2]); + selectedEntity[0]->signalTimerRepeatCount = (Sint32)atoi(spriteProperties[3]); + selectedEntity[0]->signalTimerLatchInput = (Sint32)atoi(spriteProperties[4]); + selectedEntity[0]->signalInvertOutput = (Sint32)atoi(spriteProperties[5]); + break; + case 29: // pressure plate + selectedEntity[0]->pressurePlateTriggerType = (Sint32)atoi(spriteProperties[0]); + break; default: break; } diff --git a/src/draw.cpp b/src/draw.cpp index 6092b101e..b9f0df041 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2454,6 +2454,30 @@ void drawEntities2D(long camx, long camy) break; } } + else if ( entity->sprite == 185 || entity->sprite == 186 || entity->sprite == 187 ) + { + pos.y += sprites[entity->sprite]->h / 2; + pos.x += sprites[entity->sprite]->w / 2; + switch ( entity->signalInputDirection ) + { + case 0: + pos.x -= pos.w; + drawImageRotatedAlpha(sprites[entity->sprite], nullptr, &pos, 0.f, 255); + break; + case 1: + pos.y -= pos.h; + drawImageRotatedAlpha(sprites[entity->sprite], nullptr, &pos, PI / 2, 255); + break; + case 2: + pos.x += pos.w; + drawImageRotatedAlpha(sprites[entity->sprite], nullptr, &pos, PI, 255); + break; + case 3: + pos.y += pos.h; + drawImageRotatedAlpha(sprites[entity->sprite], nullptr, &pos, 3 * PI / 2, 255); + break; + } + } else { // draw sprite normally from sprites list diff --git a/src/editor.cpp b/src/editor.cpp index 54d316575..b1ff5324e 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -105,11 +105,12 @@ char monsterPropertyNames[14][16] = "Is NPC:" }; -char chestPropertyNames[3][40] = +char chestPropertyNames[4][40] = { "Orientation: (0-3)", "Chest Type: (0-7)", - "Locked Chance: (0-100%)" + "Locked Chance: (0-100%)", + "Mimic Chance: (0-100%)" }; char summonTrapPropertyNames[6][44] = @@ -303,13 +304,29 @@ char customPortalPropertyNames[7][54] = "Exit toggle between secret levels file (0-1)" }; -char signalTimerPropertyNames[5][55] = +char signalTimerPropertyNames[6][55] = { "Input signal direction (0 - 3)", "Output activation delay (0-9999 ticks, 50 ticks / sec)", "Output pulse time (0 - 9999 ticks, 50 ticks / sec)", "Output repeat count (0 - 9999)", - "Latch input and keep powered (0 - 1)" + "Latch input and keep powered (0 - 1)", + "Invert Output (0 - 1)" +}; + +char ANDGatePropertyNames[6][55] = +{ + "Output signal direction (0 - 3)", + "Output activation delay (0-9999 ticks, 50 ticks / sec)", + "Output pulse time (0 - 9999 ticks, 50 ticks / sec)", + "Output repeat count (0 - 9999)", + "Latch input and keep powered (0 - 1)", + "Invert Output (0 - 1)" +}; + +char pressurePlatePropertyNames[1][34] = +{ + "Trigger Type: (0-7)" }; char tablePropertyNames[3][34] = @@ -4002,7 +4019,7 @@ int main(int argc, char** argv) { errorMessage = 60; errorArr[i] = 1; - snprintf(spriteProperties[i], sizeof(spriteProperties[i]), "%d", 1); //reset + snprintf(spriteProperties[i], sizeof(spriteProperties[i]), "%d", -1); //reset } else if ( propertyInt == -1 ) { @@ -4017,6 +4034,27 @@ int main(int argc, char** argv) printTextFormattedColor(font8x8_bmp, pad_x3, pad_y2, color, tmpStr); } } + else if ( i == 3 ) + { + if ( propertyInt > 100 || propertyInt < -1 ) + { + errorMessage = 60; + errorArr[i] = 1; + snprintf(spriteProperties[i], sizeof(spriteProperties[i]), "%d", -1); //reset + } + else if ( propertyInt == -1 ) + { + printTextFormattedColor(font8x8_bmp, pad_x3, pad_y2, colorRandom, "Default Rand %"); + } + else + { + color = makeColorRGB(0, 255, 0); + char tmpStr[32] = ""; + strcpy(tmpStr, spriteProperties[i]); //reset + strcat(tmpStr, " %%"); + printTextFormattedColor(font8x8_bmp, pad_x3, pad_y2, color, tmpStr); + } + } } if ( errorMessage ) @@ -4035,12 +4073,12 @@ int main(int argc, char** argv) pad_x1 += 18; spacing = 18; //printText(font8x8_bmp, pad_x1 + 16, pad_y1, "Chest Facing"); - pad_y1 = suby1 + 28 + 7 * spacing; + pad_y1 = suby1 + 28 + 9 * spacing; printText(font8x8_bmp, pad_x1 + 32, pad_y1, "NORTH(3)"); - pad_y1 = suby1 + 28 + 8 * spacing; + pad_y1 = suby1 + 28 + 10 * spacing; printText(font8x8_bmp, pad_x1, pad_y1, "WEST(2)"); printText(font8x8_bmp, pad_x1 + 96 - 16, pad_y1, "EAST(0)"); - pad_y1 = suby1 + 28 + 9 * spacing; + pad_y1 = suby1 + 28 + 11 * spacing; printText(font8x8_bmp, pad_x1 + 32, pad_y1, "SOUTH(1)"); spacing = 14; @@ -6885,6 +6923,24 @@ int main(int argc, char** argv) } } } + else if ( i == 5 ) + { + if ( propertyInt > 1 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "non-inverted"); + } + else if ( propertyInt == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "output inverted"); + } + } + } else { // enter other row entries here @@ -8232,6 +8288,273 @@ int main(int argc, char** argv) } } } + else if ( newwindow == 32 ) + { + if ( selectedEntity[0] != nullptr ) + { + int numProperties = sizeof(ANDGatePropertyNames) / sizeof(ANDGatePropertyNames[0]); //find number of entries in property list + const int lenProperties = sizeof(ANDGatePropertyNames[0]) / sizeof(char); //find length of entry in property list + int spacing = 36; // 36 px between each item in the list. + int inputFieldHeader_y = suby1 + 28; // 28 px spacing from subwindow start. + int inputField_x = subx1 + 8; // 8px spacing from subwindow start. + int inputField_y = inputFieldHeader_y + 16; + int inputFieldWidth = 64; // width of the text field + int inputFieldFeedback_x = inputField_x + inputFieldWidth + 8; + char tmpPropertyName[lenProperties] = ""; + Uint32 color = makeColorRGB(0, 255, 0); + Uint32 colorRandom = makeColorRGB(0, 168, 255); + Uint32 colorError = makeColorRGB(255, 0, 0); + + for ( int i = 0; i < numProperties; i++ ) + { + int propertyInt = atoi(spriteProperties[i]); + + strcpy(tmpPropertyName, ANDGatePropertyNames[i]); + inputFieldHeader_y = suby1 + 28 + i * spacing; + inputField_y = inputFieldHeader_y + 16; + // box outlines then text + drawDepressed(inputField_x - 4, inputField_y - 4, inputField_x - 4 + inputFieldWidth, inputField_y + 16 - 4); + // print values on top of boxes + printText(font8x8_bmp, inputField_x, suby1 + 44 + i * spacing, spriteProperties[i]); + printText(font8x8_bmp, inputField_x, inputFieldHeader_y, tmpPropertyName); + + if ( errorArr[i] != 1 ) + { + if ( i == 0 ) + { + if ( propertyInt > 3 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + char tmpStr[8] = ""; + switch ( propertyInt ) + { + case 0: + strcpy(tmpStr, "East"); + break; + case 1: + strcpy(tmpStr, "South"); + break; + case 2: + strcpy(tmpStr, "West"); + break; + case 3: + strcpy(tmpStr, "North"); + break; + default: + break; + } + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr); + } + } + else if ( i == 1 ) + { + if ( propertyInt > 9999 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + } + else if ( i == 2 ) + { + if ( propertyInt > 9999 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "output without on/off toggling"); + } + } + } + else if ( i == 3 ) + { + if ( propertyInt > 9999 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "repeat infinite"); + } + } + } + else if ( i == 4 ) + { + if ( propertyInt > 1 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "turn off without input signal"); + } + else if ( propertyInt == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "stay on without input signal"); + } + } + } + else if ( i == 5 ) + { + if ( propertyInt > 1 || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "non-inverted"); + } + else if ( propertyInt == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, "output inverted"); + } + } + } + else + { + // enter other row entries here + } + } + + if ( errorMessage ) + { + if ( errorArr[i] == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, colorError, "Invalid ID!"); + } + } + } + + propertyPageTextAndInput(numProperties, inputFieldWidth); + + if ( editproperty < numProperties ) // edit + { + if ( !SDL_IsTextInputActive() ) + { + SDL_StartTextInput(); + inputstr = spriteProperties[0]; + } + + // set the maximum length allowed for user input + inputlen = 4; + propertyPageCursorFlash(spacing); + } + } + } + else if ( newwindow == 33 ) + { + if ( selectedEntity[0] != nullptr ) + { + int numProperties = sizeof(pressurePlatePropertyNames) / sizeof(pressurePlatePropertyNames[0]); //find number of entries in property list + const int lenProperties = sizeof(pressurePlatePropertyNames[0]) / sizeof(char); //find length of entry in property list + int spacing = 36; // 36 px between each item in the list. + int inputFieldHeader_y = suby1 + 28; // 28 px spacing from subwindow start. + int inputField_x = subx1 + 8; // 8px spacing from subwindow start. + int inputField_y = inputFieldHeader_y + 16; + int inputFieldWidth = 64; // width of the text field + int inputFieldFeedback_x = inputField_x + inputFieldWidth + 8; + char tmpPropertyName[lenProperties] = ""; + Uint32 color = makeColorRGB(0, 255, 0); + Uint32 colorRandom = makeColorRGB(0, 168, 255); + Uint32 colorError = makeColorRGB(255, 0, 0); + + for ( int i = 0; i < numProperties; i++ ) + { + int propertyInt = atoi(spriteProperties[i]); + + strcpy(tmpPropertyName, pressurePlatePropertyNames[i]); + inputFieldHeader_y = suby1 + 28 + i * spacing; + inputField_y = inputFieldHeader_y + 16; + // box outlines then text + drawDepressed(inputField_x - 4, inputField_y - 4, inputField_x - 4 + inputFieldWidth, inputField_y + 16 - 4); + // print values on top of boxes + printText(font8x8_bmp, inputField_x, suby1 + 44 + i * spacing, spriteProperties[i]); + printText(font8x8_bmp, inputField_x, inputFieldHeader_y, tmpPropertyName); + + if ( errorArr[i] != 1 ) + { + if ( i == 0 ) + { + if ( propertyInt >= Entity::PRESSURE_PLATE_ENUM_END || propertyInt < 0 ) + { + propertyPageError(i, 0); // reset to default 0. + } + else + { + char tmpStr[32] = ""; + switch ( propertyInt ) + { + case Entity::PRESSURE_PLATE_DEFAULT_ALL: + strcpy(tmpStr, "Default All"); + break; + case Entity::PRESSURE_PLATE_PLAYERS: + strcpy(tmpStr, "Player"); + break; + case Entity::PRESSURE_PLATE_MONSTERS: + strcpy(tmpStr, "Any Monster"); + break; + case Entity::PRESSURE_PLATE_ITEMS: + strcpy(tmpStr, "Items"); + break; + case Entity::PRESSURE_PLATE_BOULDERS: + strcpy(tmpStr, "Boulders"); + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_MONSTERS: + strcpy(tmpStr, "Players/Any Monster"); + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_ALLIES: + strcpy(tmpStr, "Players/Ally Monster"); + break; + case Entity::PRESSURE_PLATE_MONSTERS_NON_ALLY: + strcpy(tmpStr, "Non-Ally Monster"); + break; + default: + break; + } + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, color, tmpStr); + } + } + else + { + // enter other row entries here + } + } + + if ( errorMessage ) + { + if ( errorArr[i] == 1 ) + { + printTextFormattedColor(font8x8_bmp, inputFieldFeedback_x, inputField_y, colorError, "Invalid ID!"); + } + } + } + + propertyPageTextAndInput(numProperties, inputFieldWidth); + + if ( editproperty < numProperties ) // edit + { + if ( !SDL_IsTextInputActive() ) + { + SDL_StartTextInput(); + inputstr = spriteProperties[0]; + } + + // set the maximum length allowed for user input + inputlen = 2; + propertyPageCursorFlash(spacing); + } + } + } else if ( newwindow == 16 || newwindow == 17 ) { int textColumnLeft = subx1 + 16; diff --git a/src/entity.cpp b/src/entity.cpp index 02adde642..80b48cf01 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -64,6 +64,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli chestHasVampireBook(skill[11]), chestLockpickHealth(skill[12]), chestOldHealth(skill[15]), + chestMimicChance(skill[16]), char_gonnavomit(skill[26]), char_heal(skill[22]), char_energize(skill[23]), @@ -379,12 +380,15 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli signalTimerRepeatCount(skill[3]), signalTimerLatchInput(skill[4]), signalInputDirection(skill[5]), + signalGateANDPowerCount(skill[9]), + signalInvertOutput(skill[10]), effectPolymorph(skill[50]), effectShapeshift(skill[53]), entityShowOnMap(skill[59]), thrownProjectilePower(skill[19]), thrownProjectileCharge(skill[20]), playerStartDir(skill[1]), + pressurePlateTriggerType(skill[3]), worldTooltipAlpha(fskill[0]), worldTooltipZ(fskill[1]), worldTooltipActive(skill[0]), diff --git a/src/entity.hpp b/src/entity.hpp index 1b8a6428b..753d7159f 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -178,6 +178,7 @@ class Entity Sint32& chestHasVampireBook; // skill[11] Sint32& chestLockpickHealth; // skill[12] Sint32& chestOldHealth; //skill[15] + Sint32& chestMimicChance; //skill[16] Sint32& char_gonnavomit; // skill[26] Sint32& char_heal; // skill[22] @@ -563,6 +564,8 @@ class Entity Sint32& signalTimerRepeatCount; //skill[3] Sint32& signalTimerLatchInput; //skill[4] Sint32& signalInputDirection; //skill[5] + Sint32& signalGateANDPowerCount; //skill[9] + Sint32& signalInvertOutput; //skill[10] //--THROWN PROJECTILE-- Sint32& thrownProjectilePower; //skill[19] @@ -571,6 +574,21 @@ class Entity //--PLAYER SPAWN POINT-- Sint32& playerStartDir; //skill[1] + //--ACTTRAP/PERMANENT + Sint32 pressurePlateTriggerType; //skill[3] + enum PressurePlateTriggerTypes : int + { + PRESSURE_PLATE_DEFAULT_ALL, + PRESSURE_PLATE_PLAYERS, + PRESSURE_PLATE_MONSTERS, + PRESSURE_PLATE_ITEMS, + PRESSURE_PLATE_BOULDERS, + PRESSURE_PLATE_PLAYERS_OR_MONSTERS, + PRESSURE_PLATE_PLAYERS_OR_ALLIES, + PRESSURE_PLATE_MONSTERS_NON_ALLY, + PRESSURE_PLATE_ENUM_END + }; + //--WORLDTOOLTIP-- real_t& worldTooltipAlpha; //fskill[0] real_t& worldTooltipZ; //fskill[1] @@ -757,6 +775,7 @@ class Entity void actLightSource(); void actTextSource(); void actSignalTimer(); + void actSignalGateAND(); Monster getRace() const { @@ -1173,7 +1192,7 @@ void actTextSource(Entity* my); static const int NUM_ITEM_STRINGS = 333; static const int NUM_ITEM_STRINGS_BY_TYPE = 129; -static const int NUM_EDITOR_SPRITES = 180; +static const int NUM_EDITOR_SPRITES = 188; static const int NUM_EDITOR_TILES = 350; // furniture types. diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index 56fe66323..a9b690298 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -30,6 +30,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli chestHasVampireBook(skill[11]), chestLockpickHealth(skill[12]), chestOldHealth(skill[15]), + chestMimicChance(skill[16]), char_gonnavomit(skill[26]), char_heal(skill[22]), char_energize(skill[23]), @@ -348,9 +349,12 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli signalTimerRepeatCount(skill[3]), signalTimerLatchInput(skill[4]), signalInputDirection(skill[5]), + signalGateANDPowerCount(skill[9]), + signalInvertOutput(skill[10]), thrownProjectilePower(skill[19]), thrownProjectileCharge(skill[20]), playerStartDir(skill[1]), + pressurePlateTriggerType(skill[3]), worldTooltipAlpha(fskill[0]), worldTooltipZ(fskill[1]), worldTooltipActive(skill[0]), diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index 2429d47f1..0746d8bfd 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -153,6 +153,15 @@ int checkSpriteType(Sint32 sprite) return 26; case 179: return 27; + case 185: + case 186: + case 187: + // AND gate + return 28; + case 33: + case 34: + // act trap + return 29; default: return 0; break; @@ -994,7 +1003,15 @@ char spriteEditorNameStrings[NUM_EDITOR_SPRITES][64] = "NOT USED", "TELEPORT SHRINE", "SPELL SHRINE", - "COLLIDER DECORATION" + "COLLIDER DECORATION", + "NOT USED", + "NOT USED", + "NOT USED", + "NOT USED", + "NOT USED", + "AND GATE", + "AND GATE", + "AND GATE" }; char monsterEditorNameStrings[NUMMONSTERS][16] = @@ -1455,6 +1472,7 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->yaw = entityToCopy->yaw; entityNew->skill[9] = entityToCopy->skill[9]; entityNew->chestLocked = entityToCopy->chestLocked; + entityNew->chestMimicChance = entityToCopy->chestMimicChance; } else { @@ -1462,6 +1480,7 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->yaw = 1; entityNew->skill[9] = 0; entityNew->chestLocked = -1; + entityNew->chestMimicChance = -1; } } // items. @@ -1781,6 +1800,7 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->signalTimerInterval = entityToCopy->signalTimerInterval; entityNew->signalTimerRepeatCount = entityToCopy->signalTimerRepeatCount; entityNew->signalTimerLatchInput = entityToCopy->signalTimerLatchInput; + entityNew->signalInvertOutput = entityToCopy->signalInvertOutput; } else { @@ -1790,6 +1810,30 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->signalTimerInterval = 0; entityNew->signalTimerRepeatCount = 0; entityNew->signalTimerLatchInput = 0; + entityNew->signalInvertOutput = 0; + } + } + else if ( spriteType == 28 ) + { + if ( entityToCopy != nullptr ) + { + // copy old entity attributes to newly created. + entityNew->signalInputDirection = entityToCopy->signalInputDirection; + entityNew->signalActivateDelay = entityToCopy->signalActivateDelay; + entityNew->signalTimerInterval = entityToCopy->signalTimerInterval; + entityNew->signalTimerRepeatCount = entityToCopy->signalTimerRepeatCount; + entityNew->signalTimerLatchInput = entityToCopy->signalTimerLatchInput; + entityNew->signalInvertOutput = entityToCopy->signalInvertOutput; + } + else + { + // set default new entity attributes. + entityNew->signalInputDirection = 0; + entityNew->signalActivateDelay = 0; + entityNew->signalTimerInterval = 0; + entityNew->signalTimerRepeatCount = 0; + entityNew->signalTimerLatchInput = 0; + entityNew->signalInvertOutput = 0; } } else if ( spriteType == 18 ) @@ -1994,6 +2038,19 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->colliderDamageTypes = 0; } } + else if ( spriteType == 29 ) // pressure plates + { + if ( entityToCopy != nullptr ) + { + // copy old entity attributes to newly created. + entityNew->pressurePlateTriggerType = entityToCopy->pressurePlateTriggerType; + } + else + { + // set default new entity attributes. + entityNew->pressurePlateTriggerType = 0; + } + } if ( entityToCopy != nullptr ) { diff --git a/src/files.cpp b/src/files.cpp index d747dfbbc..a4e4991f1 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -1854,9 +1854,14 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea // read map version number fp->read(valid_data, sizeof(char), strlen("BARONY LMPV2.0")); - if ( strncmp(valid_data, "BARONY LMPV2.8", strlen("BARONY LMPV2.0")) == 0 ) + if ( strncmp(valid_data, "BARONY LMPV2.9", strlen("BARONY LMPV2.0")) == 0 ) { - // V2.7 version of editor - teleport shrine dest x update + // V2.9 version of editor - chest mimic chance, and gates, pressure plate triggers + editorVersion = 29; + } + else if ( strncmp(valid_data, "BARONY LMPV2.8", strlen("BARONY LMPV2.0")) == 0 ) + { + // V2.8 version of editor - teleport shrine dest x update editorVersion = 28; } else if ( strncmp(valid_data, "BARONY LMPV2.7", strlen("BARONY LMPV2.0")) == 0 ) @@ -2089,6 +2094,7 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea case 26: case 27: case 28: + case 29: // V2.0+ of editor version switch ( checkSpriteType(sprite) ) { @@ -2198,9 +2204,20 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea } break; case 2: - fp->read(&entity->yaw, sizeof(real_t), 1); - fp->read(&entity->skill[9], sizeof(Sint32), 1); - fp->read(&entity->chestLocked, sizeof(Sint32), 1); + if ( editorVersion >= 29 ) + { + fp->read(&entity->yaw, sizeof(real_t), 1); + fp->read(&entity->skill[9], sizeof(Sint32), 1); + fp->read(&entity->chestLocked, sizeof(Sint32), 1); + fp->read(&entity->chestMimicChance, sizeof(Sint32), 1); + } + else + { + setSpriteAttributes(entity, nullptr, nullptr); + fp->read(&entity->yaw, sizeof(real_t), 1); + fp->read(&entity->skill[9], sizeof(Sint32), 1); + fp->read(&entity->chestLocked, sizeof(Sint32), 1); + } break; case 3: fp->read(&entity->skill[10], sizeof(Sint32), 1); @@ -2341,6 +2358,10 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea fp->read(&entity->signalTimerInterval, sizeof(Sint32), 1); fp->read(&entity->signalTimerRepeatCount, sizeof(Sint32), 1); fp->read(&entity->signalTimerLatchInput, sizeof(Sint32), 1); + if ( editorVersion >= 29 ) + { + fp->read(&entity->signalInvertOutput, sizeof(Sint32), 1); + } break; case 18: fp->read(&entity->portalCustomSprite, sizeof(Sint32), 1); @@ -2426,6 +2447,25 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea fp->read(&entity->colliderDiggable, sizeof(Sint32), 1); fp->read(&entity->colliderDamageTypes, sizeof(Sint32), 1); break; + case 28: + fp->read(&entity->signalInputDirection, sizeof(Sint32), 1); + fp->read(&entity->signalActivateDelay, sizeof(Sint32), 1); + fp->read(&entity->signalTimerInterval, sizeof(Sint32), 1); + fp->read(&entity->signalTimerRepeatCount, sizeof(Sint32), 1); + fp->read(&entity->signalTimerLatchInput, sizeof(Sint32), 1); + fp->read(&entity->signalInvertOutput, sizeof(Sint32), 1); + break; + case 29: + if ( editorVersion >= 29 ) + { + fp->read(&entity->pressurePlateTriggerType, sizeof(Sint32), 1); + } + else + { + // don't read data, set default. + setSpriteAttributes(entity, nullptr, nullptr); + } + break; default: break; } @@ -2625,7 +2665,7 @@ int saveMap(const char* filename2) return 1; } - fp->write("BARONY LMPV2.8", sizeof(char), strlen("BARONY LMPV2.0")); // magic code + fp->write("BARONY LMPV2.9", sizeof(char), strlen("BARONY LMPV2.0")); // magic code fp->write(map.name, sizeof(char), 32); // map filename fp->write(map.author, sizeof(char), 32); // map author fp->write(&map.width, sizeof(Uint32), 1); // map width @@ -2685,6 +2725,7 @@ int saveMap(const char* filename2) fp->write(&entity->yaw, sizeof(real_t), 1); fp->write(&entity->skill[9], sizeof(Sint32), 1); fp->write(&entity->chestLocked, sizeof(Sint32), 1); + fp->write(&entity->chestMimicChance, sizeof(Sint32), 1); break; case 3: // items @@ -2790,6 +2831,7 @@ int saveMap(const char* filename2) fp->write(&entity->signalTimerInterval, sizeof(Sint32), 1); fp->write(&entity->signalTimerRepeatCount, sizeof(Sint32), 1); fp->write(&entity->signalTimerLatchInput, sizeof(Sint32), 1); + fp->write(&entity->signalInvertOutput, sizeof(Sint32), 1); break; case 18: fp->write(&entity->portalCustomSprite, sizeof(Sint32), 1); @@ -2855,6 +2897,17 @@ int saveMap(const char* filename2) fp->write(&entity->colliderDiggable, sizeof(Sint32), 1); fp->write(&entity->colliderDamageTypes, sizeof(Sint32), 1); break; + case 28: + fp->write(&entity->signalInputDirection, sizeof(Sint32), 1); + fp->write(&entity->signalActivateDelay, sizeof(Sint32), 1); + fp->write(&entity->signalTimerInterval, sizeof(Sint32), 1); + fp->write(&entity->signalTimerRepeatCount, sizeof(Sint32), 1); + fp->write(&entity->signalTimerLatchInput, sizeof(Sint32), 1); + fp->write(&entity->signalInvertOutput, sizeof(Sint32), 1); + break; + case 29: + fp->write(&entity->pressurePlateTriggerType, sizeof(Sint32), 1); + break; default: break; } diff --git a/src/game.hpp b/src/game.hpp index bae6775b3..1da96e768 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -299,6 +299,7 @@ void actExpansionEndGamePortal(Entity* my); void actSoundSource(Entity* my); void actLightSource(Entity* my); void actSignalTimer(Entity* my); +void actSignalGateAND(Entity* my); void startMessages(); bool frameRateLimit(Uint32 maxFrameRate, bool resetAccumulator = true, bool sleep = false); diff --git a/src/maps.cpp b/src/maps.cpp index 45c2f81b1..17f981d0c 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -7775,6 +7775,26 @@ void assignActions(map_t* map) entity->setUID(-3);*/ break; } + //AND gate + case 185: + case 186: + case 187: + { + entity->sizex = 2; + entity->sizey = 2; + entity->x += 8; + entity->y += 8; + entity->behavior = &actSignalGateAND; + entity->flags[SPRITE] = true; + entity->flags[INVISIBLE] = true; + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->skill[28] = 1; // is a mechanism + if ( entity->sprite == 186 ) { entity->signalInputDirection += 4; } + if ( entity->sprite == 187 ) { entity->signalInputDirection += 8; } + entity->sprite = -1; + break; + } default: break; } @@ -7914,7 +7934,7 @@ void assignActions(map_t* map) numMimics = 0; } - static ConsoleVariable cvar_mimic_chance("/mimic_chance", 2); + static ConsoleVariable cvar_mimic_chance("/mimic_chance", 10); static ConsoleVariable cvar_mimic_debug("/mimic_debug", false); std::vector mimics; @@ -7925,8 +7945,11 @@ void assignActions(map_t* map) auto chosen = map_rng.rand() % chests.size(); if ( allowedGenerateMimicOnChest(chests[chosen]->x / 16, chests[chosen]->y / 16, *map) ) { - mimics.push_back(chests[chosen]); - chests.erase(chests.begin() + chosen); + if ( chests[chosen]->chestMimicChance != 0 ) + { + mimics.push_back(chests[chosen]); + chests.erase(chests.begin() + chosen); + } } } @@ -7941,7 +7964,14 @@ void assignActions(map_t* map) { chance = std::min(100, std::max(0, *cvar_mimic_chance)); } - doMimic = chest->entity_rng->rand() % 100 < chance; + if ( chest->chestMimicChance >= 0 ) + { + doMimic = chest->entity_rng->rand() % 100 < chest->chestMimicChance; + } + else + { + doMimic = chest->entity_rng->rand() % 100 < chance; + } } if ( doMimic ) diff --git a/src/mechanisms.cpp b/src/mechanisms.cpp index 9e5dc45b5..c74eaf93f 100644 --- a/src/mechanisms.cpp +++ b/src/mechanisms.cpp @@ -21,6 +21,7 @@ //Circuits do not overlap. They connect to all their neighbors, allowing for circuits to interfere with eachother. static ConsoleVariable cvar_wire_debug("/wire_debug", false); +void signalGateANDOnReceive(Entity& gate, const bool powered, const int receivex, const int receivey); void actCircuit(Entity* my) { @@ -116,6 +117,13 @@ void Entity::updateCircuitNeighbors() break; } } + else if ( powerable->behavior == &::actSignalGateAND ) + { + int x1 = static_cast(this->x / 16); + int y1 = static_cast(this->y / 16); + //messagePlayer(0, "%d, %d, %d, %d", x1, x2, y1, y2); + signalGateANDOnReceive(*powerable, circuit_status > 1, x1, y1); + } else { (circuit_status > 1) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); @@ -159,14 +167,6 @@ void Entity::mechanismPowerOff() } - - - - - - - - /* * skill[0] = power status. * * 0 = off @@ -431,6 +431,58 @@ void actTrap(Entity* my) { if ( floor(entity->x / 16) == floor(my->x / 16) && floor(entity->y / 16) == floor(my->y / 16) ) { + switch ( my->pressurePlateTriggerType ) + { + default: + case Entity::PRESSURE_PLATE_DEFAULT_ALL: + break; + case Entity::PRESSURE_PLATE_PLAYERS: + if ( entity->behavior != &actPlayer ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_MONSTERS: + if ( entity->behavior != &actMonster ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_ITEMS: + if ( entity->behavior != &actItem ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_BOULDERS: + if ( entity->behavior != &actBoulder ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_MONSTERS: + if ( !(entity->behavior == &actPlayer || entity->behavior == &actMonster) ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_ALLIES: + if ( !(entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_MONSTERS_NON_ALLY: + if ( entity->behavior != &actMonster ) + { + continue; + } + if ( entity->monsterAllyGetPlayerLeader() ) + { + continue; // non allies only + } + break; + } somebodyonme = true; if ( !TRAP_ON ) { @@ -553,6 +605,59 @@ void actTrapPermanent(Entity* my) { if ( floor(entity->x / 16) == floor(my->x / 16) && floor(entity->y / 16) == floor(my->y / 16) ) { + switch ( my->pressurePlateTriggerType ) + { + default: + case Entity::PRESSURE_PLATE_DEFAULT_ALL: + break; + case Entity::PRESSURE_PLATE_PLAYERS: + if ( entity->behavior != &actPlayer ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_MONSTERS: + if ( entity->behavior != &actMonster ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_ITEMS: + if ( entity->behavior != &actItem ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_BOULDERS: + if ( entity->behavior != &actBoulder ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_MONSTERS: + if ( !(entity->behavior == &actPlayer || entity->behavior == &actMonster) ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_PLAYERS_OR_ALLIES: + if ( !(entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) + { + continue; + } + break; + case Entity::PRESSURE_PLATE_MONSTERS_NON_ALLY: + if ( entity->behavior != &actMonster ) + { + continue; + } + if ( entity->monsterAllyGetPlayerLeader() ) + { + continue; // non allies only + } + break; + } + my->toggleSwitch(); TRAPPERMANENT_ON = 1; } @@ -630,6 +735,13 @@ void Entity::toggleSwitch(int skillIndexForPower) break; } } + else if ( powerable->behavior == &::actSignalGateAND ) + { + int x1 = static_cast(this->x / 16); + int y1 = static_cast(this->y / 16); + //messagePlayer(0, "%d, %d, %d, %d", x1, x2, y1, y2); + signalGateANDOnReceive(*powerable, switchPower, x1, y1); + } else { (switchPower) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); @@ -701,6 +813,13 @@ void Entity::switchUpdateNeighbors() break; } } + else if ( powerable->behavior == &::actSignalGateAND ) + { + int x1 = static_cast(this->x / 16); + int y1 = static_cast(this->y / 16); + //messagePlayer(0, "%d, %d, %d, %d", x1, x2, y1, y2); + signalGateANDOnReceive(*powerable, true, x1, y1); + } else { powerable->mechanismPowerOn(); @@ -858,6 +977,7 @@ void Entity::actSoundSource() #define SIGNALTIMER_DELAYCOUNT skill[6] #define SIGNALTIMER_TIMERCOUNT skill[7] #define SIGNALTIMER_REPEATCOUNT skill[8] +#define SIGNAL_INIT skill[11] void actSignalTimer(Entity* my) { @@ -880,6 +1000,14 @@ void Entity::actSignalTimer() int ty = y / 16; list_t *neighbors = nullptr; bool updateNeighbors = false; + if ( !SIGNAL_INIT ) + { + SIGNAL_INIT = 1; + if ( signalInvertOutput != 0 ) + { + updateNeighbors = true; // once off power the neighbours since needs an external kick + } + } if ( circuit_status == CIRCUIT_ON || signalTimerLatchInput == 2 ) { @@ -982,6 +1110,7 @@ void Entity::actSignalTimer() } if ( neighbors != nullptr ) { + bool power_to_neighbors = ((signalInvertOutput == 0) ? (switch_power == SWITCH_POWERED) : (!(switch_power == SWITCH_POWERED))); node_t* node = nullptr; for ( node = neighbors->first; node != nullptr; node = node->next ) { @@ -993,7 +1122,7 @@ void Entity::actSignalTimer() { if ( powerable->behavior == actCircuit ) { - (switch_power == SWITCH_POWERED) ? powerable->circuitPowerOn() : powerable->circuitPowerOff(); + (power_to_neighbors) ? powerable->circuitPowerOn() : powerable->circuitPowerOff(); } else { @@ -1004,34 +1133,41 @@ void Entity::actSignalTimer() case 0: // west if ( static_cast(this->x / 16) == static_cast((powerable->x / 16) - 1) ) { - (switch_power == SWITCH_POWERED) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); } break; case 1: // south if ( static_cast(this->y / 16) == static_cast((powerable->y / 16) - 1) ) { - (switch_power == SWITCH_POWERED) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); } break; case 2: // east if ( static_cast(this->x / 16) == static_cast((powerable->x / 16) + 1) ) { - (switch_power == SWITCH_POWERED) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); } break; case 3: // north if ( static_cast(this->y / 16) == static_cast((powerable->y / 16) + 1) ) { - (switch_power == SWITCH_POWERED) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); } break; default: break; } } + else if ( powerable->behavior == &::actSignalGateAND ) + { + int x1 = static_cast(this->x / 16); + int y1 = static_cast(this->y / 16); + //messagePlayer(0, "%d, %d, %d, %d", x1, x2, y1, y2); + signalGateANDOnReceive(*powerable, power_to_neighbors, x1, y1); + } else { - (switch_power == SWITCH_POWERED) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); } } } @@ -1042,3 +1178,300 @@ void Entity::actSignalTimer() } } } + +void actSignalGateAND(Entity* my) +{ + if ( !my ) + { + return; + } + + my->actSignalGateAND(); +} + +struct SignalGate_t +{ + enum SignalGateDir + { + DIR_EAST, + DIR_SOUTH, + DIR_WEST, + DIR_NORTH + }; + std::map> recvDirs = + { + { DIR_EAST + 0, {DIR_SOUTH, DIR_NORTH}}, + { DIR_EAST + 4, {DIR_SOUTH, DIR_WEST}}, + { DIR_EAST + 8, {DIR_WEST, DIR_NORTH}}, + { DIR_SOUTH + 0, {DIR_EAST, DIR_WEST}}, + { DIR_SOUTH + 4, {DIR_NORTH, DIR_WEST}}, + { DIR_SOUTH + 8, {DIR_EAST, DIR_NORTH}}, + { DIR_WEST + 0, {DIR_SOUTH, DIR_NORTH}}, + { DIR_WEST + 4, {DIR_EAST, DIR_NORTH}}, + { DIR_WEST + 8, {DIR_SOUTH, DIR_EAST}}, + { DIR_NORTH + 0, {DIR_EAST, DIR_WEST}}, + { DIR_NORTH + 4, {DIR_EAST, DIR_SOUTH}}, + { DIR_NORTH + 8, {DIR_SOUTH, DIR_WEST}} + }; +}; + +SignalGate_t SignalGateProps; + +void signalGateANDOnReceive(Entity& gate, const bool powered, const int receivex, const int receivey) +{ + int x = static_cast(gate.x / 16); + int y = static_cast(gate.y / 16); + + auto& dirsAllowed = SignalGateProps.recvDirs[gate.signalInputDirection]; + Uint32 bits = 0; + bool foundResult = false; + for ( auto dir : dirsAllowed ) + { + bits |= (1 << dir); + bool res = false; + if ( dir == SignalGate_t::DIR_EAST ) + { + if ( receivey == y && ((receivex - 1) == x) ) + { + res = true; + } + } + else if ( dir == SignalGate_t::DIR_SOUTH ) + { + if ( receivex == x && ((receivey - 1) == y) ) + { + res = true; + } + } + else if ( dir == SignalGate_t::DIR_WEST ) + { + if ( receivey == y && ((receivex + 1) == x) ) + { + res = true; + } + } + else if ( dir == SignalGate_t::DIR_NORTH ) + { + if ( receivex == x && ((receivey + 1) == y) ) + { + res = true; + } + } + + if ( res ) + { + if ( powered ) + { + gate.signalGateANDPowerCount |= (1 << dir); + } + else + { + gate.signalGateANDPowerCount &= ~(1 << dir); + } + foundResult = true; + } + } + + if ( foundResult ) + { + if ( gate.signalGateANDPowerCount == bits ) + { + gate.skill[28] = 2; + } + else + { + gate.skill[28] = 1; + } + } +} + +void Entity::actSignalGateAND() +{ + if ( multiplayer == CLIENT ) + { + return; + } + + int tx = x / 16; + int ty = y / 16; + list_t* neighbors = nullptr; + bool updateNeighbors = false; + if ( !SIGNAL_INIT ) + { + SIGNAL_INIT = 1; + if ( signalInvertOutput != 0 ) + { + updateNeighbors = true; // once off power the neighbours since needs an external kick + } + } + + if ( circuit_status == CIRCUIT_ON || signalTimerLatchInput == 2 ) + { + if ( signalTimerLatchInput == 1 ) + { + signalTimerLatchInput = 2; + } + if ( signalActivateDelay > 0 && SIGNALTIMER_DELAYCOUNT == 0 && switch_power == SWITCH_UNPOWERED ) + { + SIGNALTIMER_DELAYCOUNT = signalActivateDelay; + } + if ( SIGNALTIMER_DELAYCOUNT > 0 ) + { + --SIGNALTIMER_DELAYCOUNT; + if ( SIGNALTIMER_DELAYCOUNT != 0 ) + { + return; + } + } + if ( switch_power == SWITCH_UNPOWERED ) + { + switch_power = SWITCH_POWERED; + updateNeighbors = true; + if ( signalTimerRepeatCount > 0 && SIGNALTIMER_REPEATCOUNT <= 0 ) + { + SIGNALTIMER_REPEATCOUNT = signalTimerRepeatCount; + } + } + else if ( signalTimerInterval > 0 ) + { + if ( SIGNALTIMER_TIMERCOUNT == 0 ) + { + SIGNALTIMER_TIMERCOUNT = signalTimerInterval; + } + if ( SIGNALTIMER_TIMERCOUNT > 0 ) + { + --SIGNALTIMER_TIMERCOUNT; + if ( SIGNALTIMER_TIMERCOUNT != 0 ) + { + return; + } + } + if ( switch_power == SWITCH_POWERED ) + { + switch_power = 2; + updateNeighbors = true; + } + else + { + if ( signalTimerRepeatCount > 0 ) + { + if ( SIGNALTIMER_REPEATCOUNT > 1 ) + { + switch_power = SWITCH_POWERED; + updateNeighbors = true; + --SIGNALTIMER_REPEATCOUNT; + } + } + else + { + switch_power = SWITCH_POWERED; + updateNeighbors = true; + } + } + } + } + else if ( circuit_status == CIRCUIT_OFF && signalTimerLatchInput == 0 ) + { + if ( switch_power != SWITCH_UNPOWERED ) + { + switch_power = SWITCH_UNPOWERED; + updateNeighbors = true; + } + if ( signalTimerInterval > 0 ) + { + SIGNALTIMER_TIMERCOUNT = signalTimerInterval; + } + if ( signalTimerRepeatCount > 0 ) + { + SIGNALTIMER_REPEATCOUNT = signalTimerRepeatCount; + } + } + + if ( updateNeighbors ) + { + switch ( signalInputDirection % 4 ) + { + case 0: // east + getPowerablesOnTile(tx + 1, ty, &neighbors); + break; + case 1: // south + getPowerablesOnTile(tx, ty + 1, &neighbors); + break; + case 2: // east + getPowerablesOnTile(tx - 1, ty, &neighbors); + break; + case 3: // north + getPowerablesOnTile(tx, ty - 1, &neighbors); + break; + } + if ( neighbors != nullptr ) + { + bool power_to_neighbors = ((signalInvertOutput == 0) ? (switch_power == SWITCH_POWERED) : (!(switch_power == SWITCH_POWERED))); + + node_t* node = nullptr; + for ( node = neighbors->first; node != nullptr; node = node->next ) + { + if ( node->element ) + { + Entity* powerable = (Entity*)(node->element); + + if ( powerable ) + { + if ( powerable->behavior == actCircuit ) + { + (power_to_neighbors) ? powerable->circuitPowerOn() : powerable->circuitPowerOff(); + } + else + { + if ( powerable->behavior == &::actSignalTimer ) + { + switch ( powerable->signalInputDirection ) + { + case 0: // west + if ( static_cast(this->x / 16) == static_cast((powerable->x / 16) - 1) ) + { + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + } + break; + case 1: // south + if ( static_cast(this->y / 16) == static_cast((powerable->y / 16) - 1) ) + { + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + } + break; + case 2: // east + if ( static_cast(this->x / 16) == static_cast((powerable->x / 16) + 1) ) + { + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + } + break; + case 3: // north + if ( static_cast(this->y / 16) == static_cast((powerable->y / 16) + 1) ) + { + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + } + break; + default: + break; + } + } + else if ( powerable->behavior == &::actSignalGateAND ) + { + int x1 = static_cast(this->x / 16); + int y1 = static_cast(this->y / 16); + //messagePlayer(0, "%d, %d, %d, %d", x1, x2, y1, y2); + signalGateANDOnReceive(*powerable, power_to_neighbors, x1, y1); + } + else + { + (power_to_neighbors) ? powerable->mechanismPowerOn() : powerable->mechanismPowerOff(); + } + } + } + } + } + list_FreeAll(neighbors); //Free the list. + free(neighbors); + } + } +} \ No newline at end of file From 0a419f9658a6a6eed253b88aa12e9af8050d4f7f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 1 Sep 2024 21:38:18 +1000 Subject: [PATCH 104/244] * herx lich - stub out some code to teleport allies into arena, work on it later --- src/actmonster.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/monster_lich.cpp | 6 ++--- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index d15811b2c..57a544caf 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -2611,6 +2611,65 @@ void actMonster(Entity* my) MONSTER_VELY = sin(dir) * 5; my->monsterSpecialTimer = 0; } + + // check walls + if ( my->monsterLichAllyStatus == 0 ) + { + if ( !strcmp(map.name, "Boss") ) + { + int x1 = 34; + int y1 = 14; + int x2 = 38; + int y2 = 18; + + for ( int x = x1; x <= x2; ++x ) + { + for ( int y = y1; y <= y2; ++y ) + { + if ( x > x1 && x < x2 && y > y1 && y < y2 ) + { + // inner empty section + continue; + } + + int mapIndex = (y) * MAPLAYERS + (x) * MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + mapIndex] ) + { + // wall has been broken, fights on + my->monsterLichAllyStatus = 1; + + //int arena_x1 = 25; + //int arena_y1 = 5; + //int arena_x2 = 47; + //int arena_y2 = 27; + //for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + //{ + // if ( Entity* entity = (Entity*)node->element ) + // { + // int x = entity->x / 16; + // int y = entity->y / 16; + // if ( !(x >= arena_x1 && x <= arena_x2 && y >= arena_y1 && y <= arena_y2) ) + // { + // // outside arena, teleport in + // if ( !entity->monsterIsTinkeringCreation() ) + // { + // if ( Entity* leader = entity->monsterAllyGetPlayerLeader() ) + // { + // int tele_x = 26 + local_rng.rand() % 3; + // int tele_y = 15 + local_rng.rand() % 3; + // entity->x = tele_x * 16.0 + 8.0; + // entity->y = tele_y * 16.0 + 8.0; + // } + // } + // } + // } + //} + break; + } + } + } + } + } } if ( ((myStats->type == LICH_FIRE && my->monsterState != MONSTER_STATE_LICHFIRE_DIE) diff --git a/src/monster_lich.cpp b/src/monster_lich.cpp index 75f297280..db81dc857 100644 --- a/src/monster_lich.cpp +++ b/src/monster_lich.cpp @@ -341,15 +341,15 @@ void lichAnimate(Entity* my, double dist) { my->light = addLight(my->x / 16, my->y / 16, "herx_glow"); } - else if ( !my->skill[27] ) + else if ( !my->monsterLichBattleState ) { - my->skill[27] = 1; + my->monsterLichBattleState = 1; serverUpdateEntitySkill(my, 27); } } else { - if ( !my->skill[27] ) + if ( !my->monsterLichBattleState ) { my->light = addLight(my->x / 16, my->y / 16, "herx_glow"); } From fbe382c2b70e8cab1c1de2e25ad916ccee819793 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 1 Sep 2024 22:08:33 +1000 Subject: [PATCH 105/244] * prev commit add missing bit --- src/actmonster.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 57a544caf..dbf32b9bb 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -2622,9 +2622,9 @@ void actMonster(Entity* my) int x2 = 38; int y2 = 18; - for ( int x = x1; x <= x2; ++x ) + for ( int x = x1; x <= x2 && my->monsterLichAllyStatus == 0; ++x ) { - for ( int y = y1; y <= y2; ++y ) + for ( int y = y1; y <= y2 && my->monsterLichAllyStatus == 0; ++y ) { if ( x > x1 && x < x2 && y > y1 && y < y2 ) { From 1862a6b1ef7b34dbb7f81250af9c04227c0ebab2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 2 Sep 2024 22:24:45 +1000 Subject: [PATCH 106/244] * slimes get nametags (also fix tag height for slimes) * slime ambushes in water/lava * slimes can spawn in water/lava with hack * summon trap have option for auto summon in proximity * fix client nametags not disappearing when dead/losing ally * fix dying in water in singleplayer making a lot of splash noises * fix some cases on death when monsterAllyIndex wasn't cleared and caused some AI weirdness --- src/actitem.cpp | 13 ++++++- src/actmonster.cpp | 36 ++++++++++++++++-- src/actplayer.cpp | 7 ++++ src/actsprite.cpp | 8 ++++ src/actsummontrap.cpp | 77 ++++++++++++++++++++++++++++++++++--- src/buttons.cpp | 6 ++- src/draw.cpp | 9 ++++- src/editor.cpp | 24 ++++++++++-- src/entity.cpp | 30 ++++++++------- src/entity_shared.cpp | 2 + src/files.cpp | 6 +++ src/game.cpp | 2 +- src/magic/magic.cpp | 7 +++- src/maps.cpp | 88 +++++++++++++++++++++++++++++++++++++++---- src/menu.cpp | 2 +- src/net.cpp | 3 +- src/opengl.cpp | 4 ++ 17 files changed, 283 insertions(+), 41 deletions(-) diff --git a/src/actitem.cpp b/src/actitem.cpp index bce55246f..d2da9f33e 100644 --- a/src/actitem.cpp +++ b/src/actitem.cpp @@ -606,7 +606,18 @@ void actItem(Entity* my) else if (overWater) { if (!ITEM_SPLOOSHED) { ITEM_SPLOOSHED = true; - playSoundEntity(my, 136, 64); + bool splash = true; + if ( multiplayer == SINGLE && !splitscreen && my->parent == achievementObserver.playerUids[clientnum] ) + { + if ( !players[clientnum] || (players[clientnum] && !players[clientnum]->entity) ) + { + splash = false; // otherwise dropping items into water on death makes a ruckus + } + } + if ( splash ) + { + playSoundEntity(my, 136, 64); + } } } else { diff --git a/src/actmonster.cpp b/src/actmonster.cpp index dbf32b9bb..2dee31e42 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1132,6 +1132,14 @@ Entity* summonMonsterNoSmoke(Monster creature, long x, long y, bool forceLocatio entity->clientStats = new Stat(creature + 1000); } + if ( multiplayer != CLIENT ) + { + if ( myStats->type == SLIME ) + { + myStats->setAttribute("slime_type", "terrain_spawn_override"); + } + } + // Find a free tile next to the source and then spawn it there. if ( multiplayer != CLIENT && !forceLocation ) { @@ -1194,6 +1202,13 @@ Entity* summonMonsterNoSmoke(Monster creature, long x, long y, bool forceLocatio end: + if ( multiplayer != CLIENT ) + { + if ( myStats->type == SLIME ) + { + myStats->attributes.erase("slime_type"); + } + } nummonsters++; @@ -1770,7 +1785,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], newNode->element = myuid; *myuid = my->getUID(); - if (monsterclicked >= 0 && monsterclicked < MAXPLAYERS && (myStats->type == HUMAN || myStats->name[0])) + if (monsterclicked >= 0 && monsterclicked < MAXPLAYERS && ((myStats->type == HUMAN || myStats->type == SLIME) || myStats->name[0])) { // give us a random name (if necessary) if ( myStats->type == HUMAN @@ -1783,9 +1798,15 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], size_t len = names[choice].size(); stringCopy(myStats->name, name, sizeof(Stat::name), len); } + else if ( myStats->type == SLIME + && !myStats->name[0] ) + { + std::string name = getMonsterLocalizedName(SLIME); + stringCopy(myStats->name, name.c_str(), sizeof(Stat::name), name.size()); + } // ... and a nametag - if (!monsterNameIsGeneric(*myStats)) { + if (!monsterNameIsGeneric(*myStats) || myStats->type == SLIME) { if (monsterclicked == clientnum || splitscreen) { Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = my->x; @@ -9459,6 +9480,7 @@ bool forceFollower(Entity& leader, Entity& follower) follower.monsterState = 0; follower.monsterTarget = 0; + follower.monsterAllyIndex = -1; followerStats->leader_uid = leader.getUID(); for ( node_t* node = leaderStats->FOLLOWERS.first; node != nullptr; node = node->next ) @@ -9477,7 +9499,7 @@ bool forceFollower(Entity& leader, Entity& follower) int player = leader.isEntityPlayer(); - if (player >= 0 && player < MAXPLAYERS && (followerStats->type == HUMAN || followerStats->name[0])) + if (player >= 0 && player < MAXPLAYERS && ((followerStats->type == HUMAN || followerStats->type == SLIME) || followerStats->name[0])) { // give us a random name (if necessary) if ( followerStats->type == HUMAN && @@ -9490,9 +9512,15 @@ bool forceFollower(Entity& leader, Entity& follower) size_t len = names[choice].size(); stringCopy(followerStats->name, name, sizeof(Stat::name), len); } + else if ( followerStats->type == SLIME + && !followerStats->name[0] ) + { + std::string name = getMonsterLocalizedName(SLIME); + stringCopy(followerStats->name, name.c_str(), sizeof(Stat::name), name.size()); + } // ... and a nametag - if (!monsterNameIsGeneric(*followerStats)) { + if (!monsterNameIsGeneric(*followerStats) || followerStats->type == SLIME) { if (player == clientnum || splitscreen) { Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = follower.x; diff --git a/src/actplayer.cpp b/src/actplayer.cpp index b77b16cc1..5ec91f336 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -7604,6 +7604,12 @@ void actPlayer(Entity* my) } else if ( myFollower->flags[USERFLAG2] ) { + myFollower->monsterAllyIndex = -1; + if ( multiplayer == SERVER ) + { + serverUpdateEntitySkill(myFollower, 42); // update monsterAllyIndex for clients. + } + // our leader died, let's undo the color change since we're now rabid. myFollower->flags[USERFLAG2] = false; serverUpdateEntityFlag(myFollower, USERFLAG2); @@ -7797,6 +7803,7 @@ void actPlayer(Entity* my) entity->skill[13] = qtyToDrop; entity->skill[14] = item->appearance; entity->skill[15] = item->identified; + entity->parent = achievementObserver.playerUids[PLAYER_NUM]; } } else diff --git a/src/actsprite.cpp b/src/actsprite.cpp index b64293fe8..d5afd9291 100644 --- a/src/actsprite.cpp +++ b/src/actsprite.cpp @@ -81,6 +81,14 @@ void actSpriteNametag(Entity* my) my->x = parent->x; my->y = parent->y; my->z = parent->z - 6; + if ( parent->getMonsterTypeFromSprite() == SLIME ) + { + my->z -= 3.0; + if ( parent->monsterAttack == MONSTER_POSE_MAGIC_WINDUP2 ) + { + my->z += parent->focalz / 2; + } + } } } else diff --git a/src/actsummontrap.cpp b/src/actsummontrap.cpp index 20b5912a7..4091188e8 100644 --- a/src/actsummontrap.cpp +++ b/src/actsummontrap.cpp @@ -31,16 +31,45 @@ See LICENSE for details. #define SUMMONTRAP_FIRED my->skill[6] #define SUMMONTRAP_INITIALIZED my->skill[7] #define SUMMONTRAP_TICKS_TO_FIRE my->skill[8] +#define SUMMONTRAP_SPAWN_IN_PLAYER_PROXIMITY my->skill[9] void actSummonTrap(Entity* my) { - if ( !my || my->skill[28] == 0 || (SUMMONTRAP_INITIALIZED && SUMMONTRAP_FIRED) ) + if ( !my || (SUMMONTRAP_INITIALIZED && SUMMONTRAP_FIRED) ) { return; } + Entity* foundTriggerEntity = nullptr; + if ( SUMMONTRAP_SPAWN_IN_PLAYER_PROXIMITY > 0 && !SUMMONTRAP_FIRED ) + { + if ( ticks % TICKS_PER_SECOND == 0 || SUMMONTRAP_TICKS_TO_FIRE > 0 ) + { + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, SUMMONTRAP_SPAWN_IN_PLAYER_PROXIMITY); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !foundTriggerEntity; ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( entity && (entity->behavior == &actPlayer || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) + { + real_t tangent = atan2(entity->y - my->y, entity->x - my->x); + lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity); + if ( hit.entity == entity ) + { + foundTriggerEntity = entity; + break; + } + } + } + } + } + } + // received on signal - if ( (my->skill[28] == 2 && !SUMMONTRAP_POWERTODISABLE) || my->skill[28] == 1 && SUMMONTRAP_POWERTODISABLE ) + if ( foundTriggerEntity || ((my->skill[28] == 2 && !SUMMONTRAP_POWERTODISABLE) || my->skill[28] == 1 && SUMMONTRAP_POWERTODISABLE) ) { if ( SUMMONTRAP_TICKS_TO_FIRE > 0 ) { @@ -116,9 +145,12 @@ void actSummonTrap(Entity* my) if ( !(gameModeManager.getMode() == gameModeManager.GAME_MODE_TUTORIAL || gameModeManager.getMode() == gameModeManager.GAME_MODE_TUTORIAL_INIT) ) { - for ( int c = 0; c < MAXPLAYERS; ++c ) + if ( !foundTriggerEntity ) { - Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_TRAP_SUMMONED_MONSTERS, "summoning trap", 1); + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_TRAP_SUMMONED_MONSTERS, "summoning trap", 1); + } } } if ( useCustomMonsters ) @@ -142,10 +174,45 @@ void actSummonTrap(Entity* my) { monster->getStats()->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; // disable champion normally. } + + if ( foundTriggerEntity ) + { + monster->lookAtEntity(*foundTriggerEntity); + if ( monster->checkEnemy(foundTriggerEntity) ) + { + monster->monsterAcquireAttackTarget(*foundTriggerEntity, MONSTER_STATE_PATH); + } + if ( foundTriggerEntity->behavior == &actPlayer ) + { + messagePlayer(foundTriggerEntity->skill[2], MESSAGE_INTERACTION, Language::get(6248), + getMonsterLocalizedName(monster->getStats()->type).c_str()); + } + } } } - playSoundEntity(my, 153, 128); + int x = my->x / 16; + int y = my->y / 16; + int mapIndex = (y)*MAPLAYERS + (x)*MAPLAYERS * map.height; + bool splash = false; + if ( x > 0 && x < map.width && y > 0 && y < map.height ) + { + if ( map.tiles[mapIndex] ) + { + if ( lavatiles[map.tiles[mapIndex]] || swimmingtiles[map.tiles[mapIndex]] ) + { + splash = true; + } + } + } + if ( splash ) + { + playSoundEntity(my, 136, 128); + } + else + { + playSoundEntity(my, 153, 128); + } if ( !SUMMONTRAP_INITIALIZED ) { diff --git a/src/buttons.cpp b/src/buttons.cpp index a75da9802..4d41e9fbb 100644 --- a/src/buttons.cpp +++ b/src/buttons.cpp @@ -1863,13 +1863,14 @@ void buttonSpriteProperties(button_t* my) snprintf(spriteProperties[3], 4, "%d", static_cast(selectedEntity[0]->skill[3])); //Amount of Spawns snprintf(spriteProperties[4], 4, "%d", static_cast(selectedEntity[0]->skill[4])); //Requires Power snprintf(spriteProperties[5], 4, "%d", static_cast(selectedEntity[0]->skill[5])); //Chance to Stop Working + snprintf(spriteProperties[6], 4, "%d", static_cast(selectedEntity[0]->skill[9])); //Autospawn inputstr = spriteProperties[0]; cursorflash = ticks; menuVisible = 0; subwindow = 1; newwindow = 6; - subx1 = xres / 2 - 210; - subx2 = xres / 2 + 210; + subx1 = xres / 2 - 220; + subx2 = xres / 2 + 220; suby1 = yres / 2 - 140; suby2 = yres / 2 + 140; strcpy(subtext, "Summoning Trap Properties:"); @@ -3197,6 +3198,7 @@ void buttonSpritePropertiesConfirm(button_t* my) } selectedEntity[0]->skill[4] = (Sint32)atoi(spriteProperties[4]); //Requires Power selectedEntity[0]->skill[5] = (Sint32)atoi(spriteProperties[5]); //Chance to Stop Working + selectedEntity[0]->skill[9] = (Sint32)atoi(spriteProperties[6]); //Autospawn break; case 5: //power crystal selectedEntity[0]->yaw = (real_t)atoi(spriteProperties[0]); diff --git a/src/draw.cpp b/src/draw.cpp index b9f0df041..d64745ec7 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2302,7 +2302,14 @@ void drawEntities3D(view_t* camera, int mode) auto stats = parent->behavior == &actPlayer ? parent->getStats() : (parent->clientsHaveItsStats ? parent->clientStats : nullptr); if (stats && stats->name[0]) { - glDrawSpriteFromImage(camera, entity, stats->name, mode); + if ( parent->behavior == &actMonster && entity->skill[0] == clientnum && (!players[clientnum]->entity || parent->monsterAllyIndex != clientnum) ) + { + // previous ally but we are dead (lost the ally) or someone stole our mon + } + else + { + glDrawSpriteFromImage(camera, entity, stats->name, mode); + } } } else { auto stats = parent->getStats(); diff --git a/src/editor.cpp b/src/editor.cpp index b1ff5324e..7cfab087c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -113,14 +113,15 @@ char chestPropertyNames[4][40] = "Mimic Chance: (0-100%)" }; -char summonTrapPropertyNames[6][44] = +char summonTrapPropertyNames[7][44] = { "Monster To Spawn: (-1 to 32)", "Quantity Per Spawn: (1-9)", "Time Between Spawns: (1-999s)", "Amount of Spawn Instances: (1-99)", "Requires Power to Disable: (0-1)", - "Chance to Stop Working Each Spawn: (0-100%)" + "Chance to Stop Working Each Spawn: (0-100%)", + "Autospawn Next To Player (0-16)" }; char itemPropertyNames[6][36] = @@ -4766,6 +4767,23 @@ int main(int argc, char** argv) printTextFormatted(font8x8_bmp, pad_x3, pad_y2, tmpStr); } } + else if ( i == 6 ) + { + if ( propertyInt > 16 || propertyInt < 0 ) + { + errorMessage = 60; + errorArr[i] = 1; + snprintf(spriteProperties[i], sizeof(spriteProperties[i]), "%d", 0); //reset + } + else if ( propertyInt == 0 ) + { + printTextFormattedColor(font8x8_bmp, pad_x3, pad_y2, color2, ""); + } + else if ( propertyInt > 0 ) + { + printTextFormattedColor(font8x8_bmp, pad_x3, pad_y2, color, "Auto activate near player (x tiles)"); + } + } } if ( errorMessage ) @@ -4818,7 +4836,7 @@ int main(int argc, char** argv) { inputlen = 2; } - else if ( editproperty == 2 || editproperty == 5 ) + else if ( editproperty == 2 || editproperty == 5 || editproperty == 6 ) { inputlen = 3; } diff --git a/src/entity.cpp b/src/entity.cpp index 80b48cf01..623a711c0 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -6444,18 +6444,20 @@ bool Entity::isWaterWalking() const } if ( stats->type == SLIME ) { + if ( stats->getAttribute("slime_type") == "terrain_spawn_override" ) + { + return true; + } auto color = MonsterData_t::getKeyFromSprite(sprite, SLIME); - if ( color == "slime green" - || color == "slime blue" - || color == "slime red" - || color == "slime tar" - || color == "slime metal" ) + if ( color == "slime blue" + || color == "slime tar" ) { return true; } } } } + return false; } bool Entity::isLavaWalking() const { @@ -6469,18 +6471,19 @@ bool Entity::isLavaWalking() const } if ( stats->type == SLIME ) { + if ( stats->getAttribute("slime_type") == "terrain_spawn_override" ) + { + return true; + } auto color = MonsterData_t::getKeyFromSprite(sprite, SLIME); - if ( color == "slime green" - || color == "slime blue" - || color == "slime red" - || color == "slime tar" - || color == "slime metal" ) + if ( color == "slime red" ) { return true; } } } } + return false; } /*------------------------------------------------------------------------------- @@ -18026,7 +18029,7 @@ Item* Entity::getBestShieldIHave() const void Entity::degradeArmor(Stat& hitstats, Item& armor, int armornum) { - if ( hitstats.type == SHADOW ) + if ( hitstats.type == SHADOW || hitstats.type == LICH || hitstats.type == LICH_FIRE || hitstats.type == LICH_ICE ) { return; //Shadows' armor and shields don't break. } @@ -20326,7 +20329,8 @@ bool monsterNameIsGeneric(Stat& monsterStats) || strstr(monsterStats.name, "training") || strstr(monsterStats.name, "Training") || strstr(monsterStats.name, "Mysterious") - || strstr(monsterStats.name, "shaman") ) + || strstr(monsterStats.name, "shaman") + || strstr(monsterStats.name, getMonsterLocalizedName(SLIME).c_str()) ) { // If true, pretend the monster doesn't have a name and use the generic message "You hit the lesser skeleton!" return true; @@ -21636,7 +21640,7 @@ bool monsterChangesColorWhenAlly(Stat* myStats, Entity* entity) race = myStats->type; } - if ( race == HUMAN || race == SENTRYBOT || race == NOTHING + if ( race == HUMAN || race == SENTRYBOT || race == NOTHING || race == SLIME || race == SPELLBOT || race == AUTOMATON || race == GYROBOT || race == DUMMYBOT ) { return false; diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index 0746d8bfd..f6d06151b 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -1519,6 +1519,7 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->skill[3] = entityToCopy->skill[3]; entityNew->skill[4] = entityToCopy->skill[4]; entityNew->skill[5] = entityToCopy->skill[5]; + entityNew->skill[9] = entityToCopy->skill[9]; } else { @@ -1529,6 +1530,7 @@ void setSpriteAttributes(Entity* entityNew, Entity* entityToCopy, Entity* entity entityNew->skill[3] = 1; entityNew->skill[4] = 0; entityNew->skill[5] = 0; + entityNew->skill[9] = 0; } } // power crystal diff --git a/src/files.cpp b/src/files.cpp index a4e4991f1..912cd986e 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -2237,6 +2237,10 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea fp->read(&entity->skill[3], sizeof(Sint32), 1); fp->read(&entity->skill[4], sizeof(Sint32), 1); fp->read(&entity->skill[5], sizeof(Sint32), 1); + if ( editorVersion >= 29 ) + { + fp->read(&entity->skill[9], sizeof(Sint32), 1); + } break; case 5: fp->read(&entity->yaw, sizeof(real_t), 1); @@ -2737,12 +2741,14 @@ int saveMap(const char* filename2) fp->write(&entity->skill[16], sizeof(Sint32), 1); break; case 4: + // summon trap fp->write(&entity->skill[0], sizeof(Sint32), 1); fp->write(&entity->skill[1], sizeof(Sint32), 1); fp->write(&entity->skill[2], sizeof(Sint32), 1); fp->write(&entity->skill[3], sizeof(Sint32), 1); fp->write(&entity->skill[4], sizeof(Sint32), 1); fp->write(&entity->skill[5], sizeof(Sint32), 1); + fp->write(&entity->skill[9], sizeof(Sint32), 1); break; case 5: fp->write(&entity->yaw, sizeof(real_t), 1); diff --git a/src/game.cpp b/src/game.cpp index 6c36c78f7..9e8334711 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2428,7 +2428,7 @@ void gameLogic(void) serverUpdateAllyStat(c, monster->getUID(), monsterStats->LVL, monsterStats->HP, monsterStats->MAXHP, monsterStats->type); } - if (players[c]->isLocalPlayer() && monsterStats->name[0] && !monsterNameIsGeneric(*monsterStats)) { + if (players[c]->isLocalPlayer() && monsterStats->name[0] && (!monsterNameIsGeneric(*monsterStats) || monsterStats->type == SLIME)) { Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = monster->x; nametag->y = monster->y; diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 4e2ecd194..b729ac5a8 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -1978,6 +1978,11 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell FollowerMenu[c].recentEntity = nullptr; } } + target->monsterAllyIndex = -1; + if ( multiplayer == SERVER ) + { + serverUpdateEntitySkill(target, 42); // update monsterAllyIndex for clients. + } break; } } @@ -1990,6 +1995,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell if ( forceFollower(*leader, *summonedEntity) ) { + summonedEntity->monsterAllyIndex = -1; if ( leader->behavior == &actPlayer ) { summonedEntity->monsterAllyIndex = leader->skill[2]; @@ -1997,7 +2003,6 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell { serverUpdateEntitySkill(summonedEntity, 42); // update monsterAllyIndex for clients. } - } // change the color of the hit entity. summonedEntity->flags[USERFLAG2] = true; diff --git a/src/maps.cpp b/src/maps.cpp index 17f981d0c..f3e05deae 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -3217,6 +3217,8 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } std::vector underworldEmptyTiles; + std::vector waterEmptyTiles; + std::vector lavaEmptyTiles; // monsters, decorations, and items numpossiblelocations = map.width * map.height; @@ -3229,25 +3231,41 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple possiblelocations[y + x * map.height] = false; numpossiblelocations--; } + else if ( x < getMapPossibleLocationX1() || x >= getMapPossibleLocationX2() + || y < getMapPossibleLocationY1() || y >= getMapPossibleLocationY2() ) + { + possiblelocations[y + x * map.height] = false; + --numpossiblelocations; + } else if ( lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] ) { possiblelocations[y + x * map.height] = false; numpossiblelocations--; + + if ( map.monsterexcludelocations[x + y * map.width] == false ) + { + if ( !checkObstacle(x * 16 + 8, y * 16 + 8, NULL, NULL, false, true, false) ) + { + lavaEmptyTiles.push_back(x + y * 1000); + } + } } else if ( swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] ) { possiblelocations[y + x * map.height] = false; numpossiblelocations--; + + if ( map.monsterexcludelocations[x + y * map.width] == false ) + { + if ( !checkObstacle(x * 16 + 8, y * 16 + 8, NULL, NULL, false, true, false) ) + { + waterEmptyTiles.push_back(x + y * 1000); + } + } } else { - if ( x < getMapPossibleLocationX1() || x >= getMapPossibleLocationX2() - || y < getMapPossibleLocationY1() || y >= getMapPossibleLocationY2() ) - { - possiblelocations[y + x * map.height] = false; - --numpossiblelocations; - } - else if ( checkObstacle(x * 16 + 8, y * 16 + 8, NULL, NULL, false, true, false) ) + if ( checkObstacle(x * 16 + 8, y * 16 + 8, NULL, NULL, false, true, false) ) { possiblelocations[y + x * map.height] = false; --numpossiblelocations; @@ -4022,6 +4040,57 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } } + if ( svFlags & SV_FLAG_TRAPS ) + { + int numSlimebushes = 0; + if ( currentlevel > 5 && currentlevel < 10 ) + { + numSlimebushes += 2 + map_rng.rand() % 3; + } + else if ( currentlevel >= 10 ) + { + numSlimebushes += 4 + map_rng.rand() % 3; + } + if ( waterEmptyTiles.size() > 0 || lavaEmptyTiles.size() > 0 ) + { + while ( numSlimebushes > 0 ) + { + size_t totalSize = waterEmptyTiles.size() + lavaEmptyTiles.size(); + int pick = map_rng.rand() % totalSize; + + int x = 0; + int y = 0; + if ( pick < waterEmptyTiles.size() ) + { + x = (waterEmptyTiles[pick]) % 1000; + y = (waterEmptyTiles[pick]) / 1000; + } + else if ( pick >= waterEmptyTiles.size() && ((pick - waterEmptyTiles.size()) < lavaEmptyTiles.size()) ) + { + x = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) % 1000; + y = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) / 1000; + } + + if ( x != 0 && y != 0 ) + { + Entity* summonTrap = newEntity(97, 1, map.entities, nullptr); + summonTrap->x = x * 16.0; + summonTrap->y = y * 16.0; + setSpriteAttributes(summonTrap, nullptr, nullptr); + summonTrap->skill[9] = 3; // x tile auto activate + summonTrap->skill[0] = SLIME; + } + + --numSlimebushes; + --totalSize; + if ( totalSize < 10 ) + { + break; + } + } + } + } + int numBreakables = std::min(15, numpossiblelocations / 10); struct BreakableNode_t { @@ -6011,7 +6080,10 @@ void assignActions(map_t* map) // summon monster trap case 97: { - entity->skill[28] = 1; // is a mechanism + if ( entity->skill[9] == 0 ) // no auto activate + { + entity->skill[28] = 1; // is a mechanism + } if ( entity->skill[1] == 0 ) { // not generated by editor, set monster qty to 1. diff --git a/src/menu.cpp b/src/menu.cpp index 2e747c1b0..163641b88 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9066,7 +9066,7 @@ void doNewGame(bool makeHighscore) { } else if (multiplayer != CLIENT && players[c]->isLocalPlayer()) { - if (monsterStats->name[0] && !monsterNameIsGeneric(*monsterStats)) { + if (monsterStats->name[0] && (!monsterNameIsGeneric(*monsterStats) || monsterStats->type == SLIME)) { Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = monster->x; nametag->y = monster->y; diff --git a/src/net.cpp b/src/net.cpp index c64d8ea31..4fd6348bd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -4329,7 +4329,8 @@ static std::unordered_map clientPacketHandlers = { { strcpy(monster->clientStats->name, (char*)&net_packet->data[12]); } - if ( monster->clientStats->name[0] && !monsterNameIsGeneric(*monster->clientStats) ) { + if ( monster->clientStats->name[0] && (!monsterNameIsGeneric(*monster->clientStats) || monster->clientStats->type == SLIME)) + { Entity* nametag = newEntity(-1, 1, map.entities, nullptr); nametag->x = monster->x; nametag->y = monster->y; diff --git a/src/opengl.cpp b/src/opengl.cpp index 3f75de3da..a07223051 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -788,6 +788,10 @@ static void uploadLightUniforms(view_t* camera, Shader& shader, Entity* entity, remap.x.y = 1.f; remap.y.z = 1.f; remap.z.x = 1.f; + + //remap.x.x = 0.8f; - desaturate option + //remap.y.y = 0.8f; + //remap.z.z = 0.8f; } } #ifndef EDITOR From abc3821bbb9ac78cfaefafaf6307927033e0e5fe Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 2 Sep 2024 22:26:25 +1000 Subject: [PATCH 107/244] * sprays dont hit allies * spray atk gib lighting * fix typo in slime atk --- src/actgib.cpp | 35 +++++++++++++++++++++++++++++++++++ src/collision.cpp | 4 ++++ src/magic/actmagic.cpp | 37 +++++++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/actgib.cpp b/src/actgib.cpp index 33672c4c1..de974cba6 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -36,6 +36,7 @@ #define GIB_LIFESPAN my->skill[4] #define GIB_PLAYER my->skill[11] #define GIB_POOF my->skill[5] +#define GIB_LIGHTING my->skill[6] void poof(Entity* my) { if (GIB_POOF) { @@ -57,6 +58,7 @@ void actGib(Entity* my) if ( my->z == 8 && fabs(GIB_VELX) < .01 && fabs(GIB_VELY) < .01 ) { poof(my); + my->removeLightField(); list_RemoveNode(my->mynode); return; } @@ -65,6 +67,7 @@ void actGib(Entity* my) if ( my->ticks > GIB_LIFESPAN && GIB_LIFESPAN ) { poof(my); + my->removeLightField(); list_RemoveNode(my->mynode); return; } @@ -83,12 +86,43 @@ void actGib(Entity* my) GIB_VELX = GIB_VELX * .95; GIB_VELY = GIB_VELY * .95; + if ( GIB_LIGHTING ) + { + my->removeLightField(); + } + // gravity if ( my->z < 8 ) { GIB_VELZ += GIB_GRAVITY; my->z += GIB_VELZ; my->roll += 0.1; + + if ( GIB_LIGHTING && my->flags[SPRITE] && my->sprite >= 180 && my->sprite <= 184 ) + { + const char* lightname = nullptr; + switch ( my->sprite ) + { + case 180: + lightname = "magic_spray_green_flicker"; + break; + case 181: + lightname = "magic_spray_blue_flicker"; + break; + case 182: + lightname = "magic_spray_orange_flicker"; + break; + case 183: + lightname = "magic_spray_purple_flicker"; + break; + case 184: + lightname = "magic_spray_white_flicker"; + break; + default: + break; + } + my->light = addLight(my->x / 16, my->y / 16, lightname); + } } else { @@ -119,6 +153,7 @@ void actGib(Entity* my) if ( my->z > 128 ) { poof(my); + my->removeLightField(); list_RemoveNode(my->mynode); return; } diff --git a/src/collision.cpp b/src/collision.cpp index baa1593cd..297383c86 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -774,6 +774,10 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { continue; } + if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) + { + continue; + } } } else if ( parent && parent->behavior == &actDeathGhost diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index a50c16ebe..97b0a7563 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -26,7 +26,11 @@ #include "magic.hpp" #include "../mod_tools.hpp" -static const char* colorForSprite(int sprite, bool darker) { +static const char* colorForSprite(Entity* my, int sprite, bool darker) { + if ( my && my->flags[SPRITE] ) + { + return nullptr; + } if (darker) { switch (sprite) { case 672: @@ -3095,7 +3099,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( parent && parent->behavior == &actPlayer ) { Uint32 color = makeColorRGB(0, 255, 0); - messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(3879), Language::get(3878), MSG_COMBAT); + messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, Language::get(3878), Language::get(3879), MSG_COMBAT); } } } @@ -4422,7 +4426,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if (1) { //Make the ball light up stuff as it travels. - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, false)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, false)); if ( flickerLights ) { @@ -4438,12 +4442,12 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if (lightball_lighting == 1) { my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, false)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, false)); } else { my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, true)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, true)); } lightball_flicker = 0; } @@ -4497,7 +4501,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. void actMagicClient(Entity* my) { my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, false)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, false)); if ( flickerLights ) { @@ -4513,12 +4517,12 @@ void actMagicClient(Entity* my) if (lightball_lighting == 1) { my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, false)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, false)); } else { my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, true)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, true)); } lightball_flicker = 0; } @@ -6064,7 +6068,7 @@ void actParticleTimer(Entity* my) { if ( my->particleTimerCountdownAction < 100 ) { - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, true)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, true)); playSoundEntityLocal(my, 167, 128); createParticleDropRising(my, 680, 1.0); createParticleCircling(my, 70, my->particleTimerCountdownSprite); @@ -6076,7 +6080,7 @@ void actParticleTimer(Entity* my) { if ( my->particleTimerCountdownAction < 100 ) { - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, true)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, true)); playSoundEntityLocal(my, 167, 128); createParticleDropRising(my, 593, 1.0); createParticleCircling(my, 70, my->particleTimerCountdownSprite); @@ -6185,6 +6189,7 @@ void actParticleTimer(Entity* my) entity->flags[PASSABLE] = true; entity->flags[NOUPDATE] = true; entity->flags[UNCLICKABLE] = true; + entity->flags[BRIGHT] = true; entity->sizex = 2; entity->sizey = 2; @@ -6195,7 +6200,15 @@ void actParticleTimer(Entity* my) entity->vel_x = vel * cos(entity->yaw) + parent->vel_x; entity->vel_y = vel * sin(entity->yaw) + parent->vel_y; entity->vel_z = -.5; - if ( entity->behavior == &actMagicMissile ) + if ( entity->behavior == &actGib ) + { + if ( my->ticks % 5 == 0 ) + { + // add lighting + entity->skill[6] = 1; + } + } + else if ( entity->behavior == &actMagicMissile ) { spell_t* spell = getSpellFromID(spellID); entity->skill[4] = 0; // life start @@ -7379,7 +7392,7 @@ void actParticleShadowTag(Entity* my) { --PARTICLE_LIFE; my->removeLightField(); - my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my->sprite, true)); + my->light = addLight(my->x / 16, my->y / 16, colorForSprite(my, my->sprite, true)); Entity* parent = uidToEntity(my->parent); if ( parent ) From f4651e3424c3c1473498ffb72320cfbbc3b55d14 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 2 Sep 2024 22:28:00 +1000 Subject: [PATCH 108/244] * slime spawn types --- src/monster_slime.cpp | 71 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/monster_slime.cpp b/src/monster_slime.cpp index 87d54086e..afa2d11da 100644 --- a/src/monster_slime.cpp +++ b/src/monster_slime.cpp @@ -21,6 +21,7 @@ #include "prng.hpp" #include "interface/consolecommand.hpp" #include "magic/magic.hpp" +#include "mod_tools.hpp" int getSlimeFrame(std::string color, int frame) { @@ -47,12 +48,21 @@ void slimeSetType(Entity* my, Stat* myStats, bool sink, BaronyRNG* rng) { if ( !my || !myStats ) { return; } + if ( gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL + || gameModeManager.currentMode == GameModeManager_t::GAME_MODE_TUTORIAL_INIT ) + { + myStats->setAttribute("slime_type", "slime green"); + return; + } std::vector> possibleTypes = { {"slime green", 1} }; if ( sink ) { if ( currentlevel >= 5 ) { - possibleTypes.push_back({ "slime blue", 2 }); + if ( strcmp(map.name, "Hell") ) + { + possibleTypes.push_back({ "slime blue", 2 }); + } } if ( currentlevel >= 10 ) { @@ -71,7 +81,10 @@ void slimeSetType(Entity* my, Stat* myStats, bool sink, BaronyRNG* rng) { if ( currentlevel >= 5 ) { - possibleTypes.push_back({"slime blue", 2}); + if ( strcmp(map.name, "Hell") ) + { + possibleTypes.push_back({"slime blue", 2}); + } } if ( currentlevel >= 10 ) { @@ -86,7 +99,53 @@ void slimeSetType(Entity* my, Stat* myStats, bool sink, BaronyRNG* rng) possibleTypes.push_back({"slime metal", 5}); } } + + int x = my->x / 16; + int y = my->y / 16; + int mapIndex = (y)*MAPLAYERS + (x)*MAPLAYERS * map.height; + if ( x > 0 && x < map.width && y > 0 && y < map.height ) + { + if ( map.tiles[mapIndex] ) + { + if ( lavatiles[map.tiles[mapIndex]] ) + { + possibleTypes.clear(); + possibleTypes.push_back({ "slime red", 3 }); + } + else if ( swimmingtiles[map.tiles[mapIndex]] ) + { + possibleTypes.clear(); + possibleTypes.push_back({ "slime blue", 2 }); + if ( currentlevel >= 15 ) + { + possibleTypes.push_back({ "slime tar", 4 }); + } + } + } + } + if ( currentlevel >= 20 ) + { + for ( auto it = possibleTypes.begin(); it != possibleTypes.end(); ) + { + if ( it->first == "slime green" || it->first == "slime blue" ) + { + if ( possibleTypes.size() > 1 ) + { + it = possibleTypes.erase(it); + } + else + { + ++it; + } + } + else + { + ++it; + } + } + } + int roll = rng ? rng->rand() % possibleTypes.size() : local_rng.rand() % possibleTypes.size(); myStats->setAttribute("slime_type", possibleTypes[roll].first); } @@ -178,6 +237,11 @@ void initSlime(Entity* my, Stat* myStats) slimeSetStats(*my, *myStats); } + if ( !strcmp(myStats->name, "") ) + { + strcpy(myStats->name, getMonsterLocalizedName(SLIME).c_str()); + } + // apply random stat increases if set in stat_shared.cpp or editor setRandomMonsterStats(myStats, rng); @@ -280,7 +344,7 @@ void slimeSprayAttack(Entity* my) if ( spellTimer ) { - spellTimer->particleTimerPreDelay = slimeSprayDelay; + spellTimer->particleTimerPreDelay = slimeSprayDelay; spellTimer->particleTimerDuration += slimeSprayDelay; createParticleDot(my); @@ -379,7 +443,6 @@ void slimeAnimate(Entity* my, Stat* myStats, double dist) my->scaley = 1.0; my->scalez = 1.0; slimeBob = 0.0; - createParticleDot(my); if ( multiplayer != CLIENT ) { if ( Stat* myStats = my->getStats() ) From 220ed208641190ddc95689bfa4b03a034bbe0823 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 2 Sep 2024 22:30:50 +1000 Subject: [PATCH 109/244] * lang update --- lang/en.txt | 14 ++++++++++++++ lang/item_names.json | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index 90a98882a..4f0bb6d9d 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6461,5 +6461,19 @@ Magic Required: %d (%s)# 6232 barrel# 6233 stump# 6234 A %s jumps out from the %s!# +6235 You are knocked back by a torrent!# +6236 You are sprayed with grease!# +6237 You are sprayed with magma!# +6238 %s is sprayed with burning magma!# +6239 The %s is sprayed with burning magma!# +6240 %s is sprayed with corrosive sludge!# +6241 The %s is sprayed with corrosive sludge!# +6242 You are sprayed with corrosive sludge!# +6243 %s is sprayed with grease!# +6244 The %s is sprayed with grease!# +6245 Your hands become slippery!# +6246 Your shield slips through your fingers!# +6247 SORT BY LOCKED FIRST# +6248 A %s jumps out!# 6250 END# diff --git a/lang/item_names.json b/lang/item_names.json index b8b4b7e40..30f5eed3a 100644 --- a/lang/item_names.json +++ b/lang/item_names.json @@ -1483,6 +1483,21 @@ }, "spell_ghost_bolt": { "name": "Ghostbolt" + }, + "spell_slime_acid": { + "name": "Slime Spray (Acid)" + }, + "spell_slime_water": { + "name": "Slime Spray (Water)" + }, + "spell_slime_fire": { + "name": "Slime Spray (Magma)" + }, + "spell_slime_tar": { + "name": "Slime Spray (Tar)" + }, + "spell_slime_metal": { + "name": "Slime Spray (Metal)" } } } \ No newline at end of file From 62d502f01835667153104d2685e7a27cfdfc3703 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 3 Sep 2024 00:27:16 +1000 Subject: [PATCH 110/244] * fix required dlc prompt causing crashes in splitscreen --- src/ui/MainMenu.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index e0a830a78..ae9496aae 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -13411,12 +13411,15 @@ namespace MainMenu { soundCancel(); closeMono(); }); - if ( auto txt = prompt->findField("text") ) + if ( prompt ) { - SDL_Rect pos = txt->getSize(); - pos.y -= 8; - pos.h += 8; - txt->setSize(pos); + if ( auto txt = prompt->findField("text") ) + { + SDL_Rect pos = txt->getSize(); + pos.y -= 8; + pos.h += 8; + txt->setSize(pos); + } } } From 0308c789f4d2959db59be947792f265d7e1609ee Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 3 Sep 2024 01:25:02 +1000 Subject: [PATCH 111/244] * show dead prompt in splitscreen --- src/ui/GameUI.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 7c09f3467..615228f33 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -10250,7 +10250,7 @@ static void checkControllerState(int player) { void HUDDrawGameEndHint(const int player, SDL_Rect rect) { - if ( multiplayer == CLIENT || multiplayer == SERVER ) + if ( (multiplayer == CLIENT || multiplayer == SERVER) || (multiplayer == SINGLE && splitscreen) ) { bool everyonedead = true; for ( int i = 0; i < MAXPLAYERS; ++i ) @@ -10265,12 +10265,37 @@ void HUDDrawGameEndHint(const int player, SDL_Rect rect) { everyonedead = false; } + else if ( multiplayer == SINGLE && players[i]->entity ) + { + everyonedead = false; + } } } if ( players[player]->bControlEnabled ) { players[player]->hud.animDeadPromptDisplay = true; + if ( players[player]->bUseCompactGUIWidth() && players[player]->bUseCompactGUIHeight() ) + { + if ( !players[player]->messageZone.notification_messages.empty() || !players[player]->shootmode ) + { + players[player]->hud.animDeadPromptDisplay = false; + } + } + else if ( players[player]->bUseCompactGUIWidth() && !players[player]->bUseCompactGUIHeight() ) + { + if ( !players[player]->shootmode && !CalloutMenu[player].calloutMenuIsOpen() ) + { + players[player]->hud.animDeadPromptDisplay = false; + } + } + else if ( players[player]->bUseCompactGUIHeight() && !players[player]->bUseCompactGUIWidth() ) + { + if ( CalloutMenu[player].calloutMenuIsOpen() && !players[player]->shootmode ) + { + players[player]->hud.animDeadPromptDisplay = false; + } + } } if ( everyonedead && players[player]->hud.animDeadPromptDisplay ) From 53f256de422f62255455c8962bdc1fff6b2f00a2 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 3 Sep 2024 01:25:17 +1000 Subject: [PATCH 112/244] * slime ally name camelcase --- src/draw.cpp | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/draw.cpp b/src/draw.cpp index d64745ec7..803b19d4e 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -25,6 +25,7 @@ #include "interface/consolecommand.hpp" #include "colors.hpp" #include "ui/Text.hpp" +#include "ui/GameUI.hpp" #include @@ -2308,7 +2309,23 @@ void drawEntities3D(view_t* camera, int mode) } else { - glDrawSpriteFromImage(camera, entity, stats->name, mode); + if ( parent->getMonsterTypeFromSprite() == SLIME ) + { + if ( !strcmp(stats->name, getMonsterLocalizedName(SLIME).c_str()) ) + { + std::string name = stats->name; + camelCaseString(name); + glDrawSpriteFromImage(camera, entity, name.c_str(), mode); + } + else + { + glDrawSpriteFromImage(camera, entity, stats->name, mode); + } + } + else + { + glDrawSpriteFromImage(camera, entity, stats->name, mode); + } } } } else { @@ -2318,7 +2335,23 @@ void drawEntities3D(view_t* camera, int mode) playerEntityMatchesUid(stats->leader_uid): playerEntityMatchesUid(entity->parent); if (player >= 0 && (!stats->leader_uid || camera == &players[player]->camera())) { - glDrawSpriteFromImage(camera, entity, stats->name, mode); + if ( stats->type == SLIME ) + { + if ( !strcmp(stats->name, getMonsterLocalizedName(SLIME).c_str()) ) + { + std::string name = stats->name; + camelCaseString(name); + glDrawSpriteFromImage(camera, entity, name.c_str(), mode); + } + else + { + glDrawSpriteFromImage(camera, entity, stats->name, mode); + } + } + else + { + glDrawSpriteFromImage(camera, entity, stats->name, mode); + } } } } From 6db4edb44f6e5a24f8f3897a00f1e5d6096b44e9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:01:56 +1000 Subject: [PATCH 113/244] * fmod - torch sounds are now looping rather than instanced * fmod - update system to have more voices, maybe improve performance?? * remove monster sound loop from actMonster, unnecessary --- src/actcampfire.cpp | 30 +++++++++++++++++++++++++ src/actmonster.cpp | 36 ++++++++++-------------------- src/acttorch.cpp | 39 ++++++++++++++++++++++++++++++++- src/engine/audio/defines.cpp | 2 +- src/engine/audio/init_audio.cpp | 20 +++++++++++++++-- src/engine/audio/sound_game.cpp | 38 ++++++++++++++++++++++++++++++-- src/entity.cpp | 8 +++++++ src/entity.hpp | 6 +++++ src/files.cpp | 7 +++++- src/game.cpp | 19 +++++++++++----- 10 files changed, 169 insertions(+), 36 deletions(-) diff --git a/src/actcampfire.cpp b/src/actcampfire.cpp index 82236f98f..cf7f62f9b 100644 --- a/src/actcampfire.cpp +++ b/src/actcampfire.cpp @@ -48,12 +48,29 @@ void actCampfire(Entity* my) // crackling sounds if ( CAMPFIRE_HEALTH > 0 ) { +#ifdef USE_FMOD + if ( CAMPFIRE_SOUNDTIME == 0 ) + { + CAMPFIRE_SOUNDTIME--; + my->entity_sound = playSoundEntityLocal(my, 133, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else CAMPFIRE_SOUNDTIME--; if ( CAMPFIRE_SOUNDTIME <= 0 ) { CAMPFIRE_SOUNDTIME = 480; playSoundEntityLocal( my, 133, 128 ); } +#endif // spew flame particles if ( flickerLights ) @@ -115,6 +132,19 @@ void actCampfire(Entity* my) { my->removeLightField(); my->light = NULL; + +#ifdef USE_FMOD + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( playing ) + { + my->entity_sound->stop(); + my->entity_sound = nullptr; + } + } +#endif } if ( multiplayer != CLIENT ) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 2dee31e42..e0754ae7b 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -3617,6 +3617,11 @@ void actMonster(Entity* my) // check to see if monster can scream again if ( MONSTER_SOUND != NULL ) { +#ifdef DEBUG_EVENT_TIMERS + auto time1 = std::chrono::high_resolution_clock::now(); + auto time2 = std::chrono::high_resolution_clock::now(); + auto accum = 1000 * std::chrono::duration_cast>(time2 - time1).count(); +#endif #ifdef USE_FMOD bool playing; MONSTER_SOUND->isPlaying(&playing); @@ -3624,19 +3629,6 @@ void actMonster(Entity* my) { MONSTER_SOUND = nullptr; } - else - { - for ( c = 0; c < numsounds; c++ ) - { - bool playing = true; - MONSTER_SOUND->isPlaying(&playing); - if (!playing) - { - MONSTER_SOUND = nullptr; - break; - } - } - } #elif defined USE_OPENAL ALboolean playing; OPENAL_Channel_IsPlaying(MONSTER_SOUND, &playing); @@ -3644,18 +3636,14 @@ void actMonster(Entity* my) { MONSTER_SOUND = NULL; } - else +#endif + +#ifdef DEBUG_EVENT_TIMERS + time2 = std::chrono::high_resolution_clock::now(); + accum = 1000 * std::chrono::duration_cast>(time2 - time1).count(); + if ( accum > 2 ) { - for ( c = 0; c < numsounds; c++ ) - { - ALboolean playing = true; - OPENAL_Channel_IsPlaying(MONSTER_SOUND, &playing); - if (!playing) - { - MONSTER_SOUND = NULL; - break; - } - } + printlog("Large tick time: [actMonster 1] %f", accum); } #endif } diff --git a/src/acttorch.cpp b/src/acttorch.cpp index 6150583a0..1c5b6f82d 100644 --- a/src/acttorch.cpp +++ b/src/acttorch.cpp @@ -46,12 +46,31 @@ void actTorch(Entity* my) } // ambient noises (yeah, torches can make these...) +#ifdef USE_FMOD + if ( TORCH_FIRE == 0 ) + { + TORCH_FIRE--; + //TORCH_FIRE = 480; + my->entity_sound = playSoundEntityLocal(my, 133, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else TORCH_FIRE--; if ( TORCH_FIRE <= 0 ) { TORCH_FIRE = 480; - playSoundEntityLocal( my, 133, 32 ); + playSoundEntityLocal(my, 133, 32); } +#endif + if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 ) { if ( Entity* entity = spawnFlame(my, SPRITE_FLAME) ) @@ -200,12 +219,30 @@ void actCrystalShard(Entity* my) } // ambient noises (yeah, torches can make these...) +#ifdef USE_FMOD + if ( TORCH_FIRE == 0 ) + { + TORCH_FIRE--; + //TORCH_FIRE = 480; + my->entity_sound = playSoundEntityLocal(my, 133, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else TORCH_FIRE--; if ( TORCH_FIRE <= 0 ) { TORCH_FIRE = 480; playSoundEntityLocal(my, 133, 32); } +#endif if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 ) { /*Entity* entity = spawnFlame(my, SPRITE_CRYSTALFLAME); diff --git a/src/engine/audio/defines.cpp b/src/engine/audio/defines.cpp index 56b41a71c..64b1991d7 100644 --- a/src/engine/audio/defines.cpp +++ b/src/engine/audio/defines.cpp @@ -19,7 +19,7 @@ FMOD::System* fmod_system = nullptr; FMOD_RESULT fmod_result; -int fmod_maxchannels = 100; +int fmod_maxchannels = 256; int fmod_flags; FMOD::Sound** sounds = nullptr; diff --git a/src/engine/audio/init_audio.cpp b/src/engine/audio/init_audio.cpp index 6a9487a70..5177c6b2d 100644 --- a/src/engine/audio/init_audio.cpp +++ b/src/engine/audio/init_audio.cpp @@ -52,10 +52,17 @@ bool initSoundEngine() FMOD_SPEAKERMODE speakerMode{}; fmod_system->getSoftwareFormat(&sampleRate, &speakerMode, &numRawSpeakers); fmod_system->setSoftwareFormat(sampleRate, fmod_speakermode, numRawSpeakers); + FMOD_ADVANCEDSETTINGS settings{}; + settings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); + settings.vol0virtualvol = 0.001; + fmod_system->setAdvancedSettings(&settings); + + // default 64 + fmod_system->setSoftwareChannels(32); if (!no_sound) { - fmod_result = fmod_system->init(fmod_maxchannels, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED, fmod_extraDriverData); + fmod_result = fmod_system->init(fmod_maxchannels, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_VOL0_BECOMES_VIRTUAL/*| FMOD_INIT_THREAD_UNSAFE | FMOD_INIT_PROFILE_ENABLE*/, fmod_extraDriverData); if (FMODErrorCheck()) { printlog("[FMOD]: Failed to initialize FMOD. DISABLING AUDIO.\n"); @@ -63,6 +70,10 @@ bool initSoundEngine() return false; } +#ifndef NDEBUG + //FMOD::Debug_Initialize(FMOD_DEBUG_LEVEL_WARNING | FMOD_DEBUG_TYPE_MEMORY); +#endif + int selected_driver = 0; int numDrivers = 0; fmod_system->getNumDrivers(&numDrivers); @@ -194,7 +205,12 @@ int loadSoundResources(real_t base_load_percent, real_t top_load_percent) { fp->gets2(name, 128); completePath(full_path, name); - fmod_result = fmod_system->createSound(full_path, (FMOD_DEFAULT | FMOD_3D), nullptr, &sounds[c]); + FMOD_MODE flags = FMOD_DEFAULT | FMOD_3D | FMOD_LOWMEM; + if ( c == 133 ) + { + flags |= FMOD_LOOP_NORMAL; + } + fmod_result = fmod_system->createSound(full_path, flags, nullptr, &sounds[c]); if (FMODErrorCheck()) { printlog("warning: failed to load '%s' listed at line %d in sounds.txt\n", full_path, c + 1); diff --git a/src/engine/audio/sound_game.cpp b/src/engine/audio/sound_game.cpp index f92059c98..05f6c2198 100644 --- a/src/engine/audio/sound_game.cpp +++ b/src/engine/audio/sound_game.cpp @@ -36,7 +36,7 @@ FMOD::ChannelGroup* getChannelGroupForSoundIndex(Uint32 snd) { return soundEnvironment_group; } - if ( snd == 149 ) + if ( snd == 149 || snd == 133 ) { return soundAmbient_group; } @@ -207,7 +207,35 @@ FMOD::Channel* playSoundPosLocal(real_t x, real_t y, Uint16 snd, Uint8 vol) //printlog("Channel index: %d, audibility: %f, vol: %f, pos x: %.2f | y: %.2f", i, audibility, volume, playingPosition.z, playingPosition.x); if ( abs(volume - (vol / 255.f)) < 0.05 ) { - if ( sqrt(pow(playingPosition.x - position.x, 2) + pow(playingPosition.z - position.z, 2)) <= 1.5 ) + if ( (pow(playingPosition.x - position.x, 2) + pow(playingPosition.z - position.z, 2)) <= 2.25 ) + { + //printlog("Culling sound due to proximity, pos x: %.2f | y: %.2f", position.z, position.x); + return nullptr; + } + } + } + } + } + + if ( soundEnvironment_group && getChannelGroupForSoundIndex(snd) == soundEnvironment_group ) + { + int numChannels = 0; + soundEnvironment_group->getNumChannels(&numChannels); + for ( int i = 0; i < numChannels; ++i ) + { + FMOD::Channel* c; + if ( soundEnvironment_group->getChannel(i, &c) == FMOD_RESULT::FMOD_OK ) + { + float audibility = 0.f; + c->getAudibility(&audibility); + float volume = 0.f; + c->getVolume(&volume); + FMOD_VECTOR playingPosition; + c->get3DAttributes(&playingPosition, nullptr); + //printlog("Channel index: %d, audibility: %f, vol: %f, pos x: %.2f | y: %.2f", i, audibility, volume, playingPosition.z, playingPosition.x); + if ( abs(volume - (vol / 255.f)) < 0.05 ) + { + if ( (pow(playingPosition.x - position.x, 2) + pow(playingPosition.z - position.z, 2)) <= 2.25 ) { //printlog("Culling sound due to proximity, pos x: %.2f | y: %.2f", position.z, position.x); return nullptr; @@ -217,6 +245,12 @@ FMOD::Channel* playSoundPosLocal(real_t x, real_t y, Uint16 snd, Uint8 vol) } } + /*FMOD_OPENSTATE openState; + unsigned int percentBuffered = 0; + bool starving = false; + bool diskbusy = false; + sounds[snd]->getOpenState(&openState, &percentBuffered, &starving, &diskbusy); + printlog("Sound: %d state: %d pc: %d starving: %d diskbusy: %d", snd, openState, percentBuffered, starving, diskbusy);*/ fmod_result = fmod_system->playSound(sounds[snd], getChannelGroupForSoundIndex(snd), true, &channel); if (FMODErrorCheck()) { diff --git a/src/entity.cpp b/src/entity.cpp index 623a711c0..43b99eb1c 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -571,6 +571,14 @@ Entity::~Entity() myTileListNode = nullptr; } +#ifdef USE_FMOD + if ( entity_sound ) + { + entity_sound->stop(); + entity_sound = nullptr; + } +#endif + // alert clients of the entity's deletion if ( multiplayer == SERVER && !loading ) { diff --git a/src/entity.hpp b/src/entity.hpp index 753d7159f..fc2ae922a 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -99,6 +99,12 @@ class Entity std::unordered_map dithering; vec4_t lightBonus; +#ifdef USE_FMOD + FMOD::Channel* entity_sound = nullptr; +#else + void* entity_sound = nullptr; +#endif + Uint32 getUID() const {return uid;} void setUID(Uint32 new_uid); Uint32 ticks; // duration of the entity's existence diff --git a/src/files.cpp b/src/files.cpp index 912cd986e..1e49dfd50 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -4774,7 +4774,12 @@ void physfsReloadSounds(bool reloadAll) sounds[c]->release(); sounds[c] = nullptr; } - fmod_result = fmod_system->createSound(soundFile.c_str(), FMOD_DEFAULT | FMOD_3D, nullptr, &sounds[c]); //TODO: FMOD_SOFTWARE -> FMOD_DEFAULT? + FMOD_MODE flags = FMOD_DEFAULT | FMOD_3D | FMOD_LOWMEM; + if ( c == 133 ) + { + flags |= FMOD_LOOP_NORMAL; + } + fmod_result = fmod_system->createSound(soundFile.c_str(), flags, nullptr, &sounds[c]); //TODO: FMOD_SOFTWARE -> FMOD_DEFAULT? if ( FMODErrorCheck() ) { printlog("warning: failed to load '%s' listed at line %d in sounds.txt\n", name, c + 1); diff --git a/src/game.cpp b/src/game.cpp index 9e8334711..9f8485a3d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1641,12 +1641,13 @@ void gameLogic(void) //if( TICKS_PER_SECOND ) //generatePathMaps(); - bool debugMonsterTimer = false && !gamePaused; + bool debugMonsterTimer = false && !gamePaused && keystatus[SDLK_g]; if ( debugMonsterTimer ) { printlog("loop start"); } real_t accum = 0.0; + std::map entityAccum; DebugStats.eventsT3 = std::chrono::high_resolution_clock::now(); // run world UI entities @@ -1789,12 +1790,13 @@ void gameLogic(void) entity->ranbehavior = true; nextnode = node->next; - if ( debugMonsterTimer && entity->behavior == &actMonster ) + if ( debugMonsterTimer ) { auto t2 = std::chrono::high_resolution_clock::now(); - printlog("%d: %d %f", entity->sprite, entity->monsterState, - 1000 * std::chrono::duration_cast>(t2 - t).count()); + //printlog("%d: %d %f", entity->sprite, entity->monsterState, + // 1000 * std::chrono::duration_cast>(t2 - t).count()); accum += 1000 * std::chrono::duration_cast>(t2 - t).count(); + entityAccum[entity->sprite] += 1000 * std::chrono::duration_cast>(t2 - t).count(); } } @@ -2519,7 +2521,14 @@ void gameLogic(void) } if ( debugMonsterTimer ) { - printlog("accum: %f", accum); + //printlog("accum: %f", accum); + if ( accum > 5.0 ) + { + for ( auto& pair : entityAccum ) + { + printlog("entity: %d, accum: %.2f", pair.first, pair.second); + } + } } for ( node = map.entities->first; node != nullptr; node = node->next ) { From 71ee9aa15e1a599e09ff7b5f33585e58228c42e1 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:02:30 +1000 Subject: [PATCH 114/244] * bat adjust callout/hp bar heights to suit body --- src/interface/interface.cpp | 28 ++++++++++++++++++++++++++++ src/interface/interface.hpp | 1 + 2 files changed, 29 insertions(+) diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index 30d066fc1..df8b65ac3 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -11113,12 +11113,26 @@ void EnemyHPDamageBarHandler::EnemyHPDetails::updateWorldCoordinates() worldX = entity->lerpRenderState.x.position * 16.0; worldY = entity->lerpRenderState.y.position * 16.0; worldZ = entity->lerpRenderState.z.position + enemyBarSettings.getHeightOffset(entity); + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( entity->bodyparts.size() > 0 ) + { + worldZ += entity->bodyparts[0]->lerpRenderState.z.position; + } + } } else { worldX = entity->x; worldY = entity->y; worldZ = entity->z + enemyBarSettings.getHeightOffset(entity); + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( entity->bodyparts.size() > 0 ) + { + worldZ += entity->bodyparts[0]->z; + } + } } if ( entity->behavior == &actDoor && entity->flags[PASSABLE] ) { @@ -24572,6 +24586,13 @@ void CalloutRadialMenu::update() callout.y = entity->lerpRenderState.y.position * 16.0; callout.z = entity->lerpRenderState.z.position + enemyBarSettings.getHeightOffset(entity); callout.z -= 4; + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( entity->bodyparts.size() > 0 ) + { + callout.z += entity->bodyparts[0]->lerpRenderState.z.position; + } + } } else { @@ -24579,6 +24600,13 @@ void CalloutRadialMenu::update() callout.y = entity->y; callout.z = entity->z + enemyBarSettings.getHeightOffset(entity); callout.z -= 4; + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( entity->bodyparts.size() > 0 ) + { + callout.z += entity->bodyparts[0]->z; + } + } } } else if ( callout.entityUid != 0 ) diff --git a/src/interface/interface.hpp b/src/interface/interface.hpp index fbe1aa959..97c91534a 100644 --- a/src/interface/interface.hpp +++ b/src/interface/interface.hpp @@ -71,6 +71,7 @@ enum DamageGib { DMG_BLEED, DMG_POISON, DMG_HEAL, + DMG_MISS, DMG_TODO }; class EnemyHPDamageBarHandler From e400dda05c22ff8072ddc34fdf26d2e90e6876dd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:03:13 +1000 Subject: [PATCH 115/244] * compendium open hotkey for keyboard --- src/game.cpp | 55 +++++++++++++++++++++++++++++++++ src/ui/MainMenu.cpp | 75 +++++++++++++++++++++++++++++++++++++++------ src/ui/MainMenu.hpp | 1 + 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 9f8485a3d..2b9150d64 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -7236,6 +7236,7 @@ int main(int argc, char** argv) // toggling the game menu bool doPause = false; + bool doCompendium = false; if ( !fadeout ) { bool noOneUsingKeyboard = true; @@ -7272,6 +7273,18 @@ int main(int argc, char** argv) } break; } + const bool compendiumOpenToggle = + !intro + && inputs.bPlayerUsingKeyboardControl(i) + && !Input::inputs[i].isDisabled() + && !command; + if ( compendiumOpenToggle ) + { + if ( Input::inputs[i].consumeBinaryToggle("Compendium") ) + { + doCompendium = true; + } + } } if (noOneUsingKeyboard && keystatus[SDLK_ESCAPE]) { doPause = true; @@ -7310,6 +7323,40 @@ int main(int argc, char** argv) } } } + else if ( doCompendium ) + { + bool isOpen = false; + if ( MainMenu::main_menu_frame ) + { + if ( auto compendium = MainMenu::main_menu_frame->findFrame("compendium") ) + { + isOpen = true; + } + } + if ( !isOpen ) + { + if ( !gamePaused ) + { + pauseGame(0, MAXPLAYERS); + } + if ( !gamePaused ) + { + doCompendium = false; + } + } + else if ( isOpen ) + { + if ( gamePaused ) + { + pauseGame(0, MAXPLAYERS); + doCompendium = false; + if ( !gamePaused ) + { + MainMenu::openCompendium(); // will close + } + } + } + } // main drawing drawClearBuffers(); @@ -7440,6 +7487,14 @@ int main(int argc, char** argv) handleButtons(); } + if ( doCompendium ) + { + if ( gamePaused ) + { + MainMenu::openCompendium(); + } + } + if ( gamePaused ) // draw after main menu windows etc. { UIToastNotificationManager.drawNotifications(MainMenu::isCutsceneActive(), true); // draw this before the cursor diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index ae9496aae..715f69bae 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -123,6 +123,7 @@ namespace MainMenu { {"Cycle NPCs", "E", "DpadY-", emptyBinding}, {"Open Map", "M", hiddenBinding, emptyBinding}, {"Open Log", "L", hiddenBinding, emptyBinding}, + {"Compendium", "P", hiddenBinding, emptyBinding}, {"Toggle Minimap", "`", "ButtonRightStick", emptyBinding}, #ifdef NINTENDO {"Hotbar Left", "MouseWheelUp", "ButtonY", emptyBinding}, @@ -187,6 +188,7 @@ namespace MainMenu { {"Cycle NPCs", "E", "DpadY-", emptyBinding}, {"Open Map", "M", hiddenBinding, emptyBinding}, {"Open Log", "L", hiddenBinding, emptyBinding}, + {"Compendium", "P", hiddenBinding, emptyBinding}, {"Toggle Minimap", "`", "ButtonRightStick", emptyBinding}, #ifdef NINTENDO {"Hotbar Left", "MouseWheelUp", "ButtonY", emptyBinding}, @@ -253,6 +255,7 @@ namespace MainMenu { #endif {"Open Map", "M", hiddenBinding, emptyBinding}, {"Open Log", "L", hiddenBinding, emptyBinding}, + {"Compendium", "P", hiddenBinding, emptyBinding}, {"Toggle Minimap", "`", "ButtonRightStick", emptyBinding}, {"Hotbar Left", "MouseWheelUp", "DpadX-", emptyBinding}, {"Hotbar Right", "MouseWheelDown", "DpadX+", emptyBinding}, @@ -307,6 +310,7 @@ namespace MainMenu { {"Cycle NPCs", "E", emptyBinding, emptyBinding}, {"Open Map", "M", hiddenBinding, emptyBinding}, {"Open Log", "L", hiddenBinding, emptyBinding}, + {"Compendium", "P", hiddenBinding, emptyBinding}, {"Toggle Minimap", "`", emptyBinding, emptyBinding}, #ifdef NINTENDO {"Hotbar Left", "MouseWheelUp", "ButtonLeftBumper", emptyBinding}, @@ -4650,6 +4654,10 @@ namespace MainMenu { { return Language::get(6044); } + else if ( !strcmp(binding, "Compendium") ) + { + return Language::get(6251); + } for (auto& b : defaultBindings[0].bindings) { if (b.action == binding) { @@ -4659,6 +4667,10 @@ namespace MainMenu { { continue; // don't increment c, not in the linear language entries } + else if ( b.action == "Compendium" ) + { + continue; // don't increment c, not in the linear language entries + } ++c; } return Language::get(c); @@ -8714,6 +8726,7 @@ namespace MainMenu { else { cause_of_death = score->stats->killer_name; + cause_of_death[0] = (char)toupper((int)cause_of_death[0]); } break; } @@ -10081,8 +10094,6 @@ namespace MainMenu { genericSubwindowFinalizeBasic(*subwindow, y); } - static void openCompendium(); - /******************************************************************************/ static void archivesLeaderboards(Button& button) { @@ -26477,6 +26488,7 @@ namespace MainMenu { cause_of_death[0] = (char)toupper((int)cause_of_death[0]); } else { cause_of_death = stats[player]->killer_name; + cause_of_death[0] = (char)toupper((int)cause_of_death[0]); } break; } @@ -33199,6 +33211,7 @@ namespace MainMenu { || monsterType == LICH_FIRE || monsterType == SHADOW || monsterType == MIMIC + || monsterType == OCTOPUS || monsterType == SPELLBOT || monsterType == SENTRYBOT || monsterType == GYROBOT @@ -35996,13 +36009,23 @@ namespace MainMenu { if ( page_right = page_right->findFrame("page_right_inner") ) { + // debug + //if ( svFlags & SV_FLAG_HARDCORE ) + //{ + // svFlags &= ~(SV_FLAG_HARDCORE); + //} + //else + //{ + // svFlags |= SV_FLAG_HARDCORE; + //} + if ( auto txt = page_right->findField("level type") ) { std::string str = Language::get(6190); if ( entry.lvl.size() > 0 ) { char buf[32] = "-"; - auto& stat = entry.lvl; + auto stat = entry.getDisplayStat("lvl"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -36056,6 +36079,7 @@ namespace MainMenu { } txt->setText(str.c_str()); } + if ( auto txt = page_right->findField("hp") ) { txt->setText(Language::get(6199)); @@ -36063,7 +36087,7 @@ namespace MainMenu { if ( auto txt = page_right->findField("hp val") ) { char buf[32] = "-"; - auto& stat = entry.hp; + auto stat = entry.getDisplayStat("hp"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -36081,7 +36105,7 @@ namespace MainMenu { if ( auto txt = page_right->findField("ac val") ) { char buf[32] = "-"; - auto& stat = entry.ac; + auto stat = entry.getDisplayStat("ac"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -36099,7 +36123,7 @@ namespace MainMenu { if ( auto txt = page_right->findField("spd val") ) { char buf[32] = "-"; - auto& stat = entry.spd; + auto stat = entry.getDisplayStat("spd"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -36117,7 +36141,7 @@ namespace MainMenu { if ( auto txt = page_right->findField("atk val") ) { char buf[32] = "-"; - auto& stat = entry.atk; + auto stat = entry.getDisplayStat("atk"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -36135,7 +36159,7 @@ namespace MainMenu { if ( auto txt = page_right->findField("rangeatk val") ) { char buf[32] = "-"; - auto& stat = entry.rangeatk; + auto stat = entry.getDisplayStat("rangeatk"); if ( stat.size() > 1 ) { snprintf(buf, sizeof(buf), "%d - %d", stat[0], stat[1]); @@ -38355,6 +38379,17 @@ namespace MainMenu { charTxt->setVJustify(Field::justify_t::TOP); charTxt->setSize(SDL_Rect{ padx, pady, 300, 24 }); charTxt->setColor(makeColor(198, 190, 179, 255)); + charTxt->setTickCallback([](Widget& widget) { + Field* txt = static_cast(&widget); + if ( !intro && (svFlags & SV_FLAG_HARDCORE) ) + { + txt->setText(Language::get(6250)); + } + else + { + txt->setText(Language::get(6189)); + } + }); padx += 4; pady += 23; @@ -39025,7 +39060,27 @@ namespace MainMenu { button->setDisabled(true); } - static void openCompendium() { + void openCompendium() { + if ( main_menu_frame ) + { + if ( auto compendium = main_menu_frame->findFrame("compendium") ) + { + if ( auto frame = static_cast(compendium->getParent()) ) + { + frame->removeSelf(); + } + soundCancel(); + auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); + auto compendium_button = buttons->findButton("Dungeon Compendium"); assert(compendium_button); + compendium_button->select(); + return; + } + } + else + { + return; + } + contents_activate_from_tab = true; players[getMenuOwner()]->inventoryUI.compendiumItemTooltipDisplay.type = NUMITEMS; compendiumItemTooltip.clear(); @@ -39096,7 +39151,7 @@ namespace MainMenu { frame = static_cast(frame->getParent()); frame = static_cast(frame->getParent()); frame->removeSelf(); - assert(main_menu_frame); + //assert(main_menu_frame); if ( main_menu_frame ) { auto buttons = main_menu_frame->findFrame("buttons"); assert(buttons); auto compendium_button = buttons->findButton("Dungeon Compendium"); assert(compendium_button); diff --git a/src/ui/MainMenu.hpp b/src/ui/MainMenu.hpp index c766257d6..ab2739117 100644 --- a/src/ui/MainMenu.hpp +++ b/src/ui/MainMenu.hpp @@ -114,6 +114,7 @@ namespace MainMenu { void controllerDisconnected(int player); // controller disconnect prompt, eg if a player unplugs a controller void tutorialFirstTimeCompleted(); // tutorial first level completed event void openGameoverWindow(int player, bool tutorial = false); // opens gameover window, used when player dies + void openCompendium(); void disconnectedFromServer(const char* text); // called when the player is disconnected from the server, prompts them to end the game void receivedInvite(void*); // called when a player receives an invite to a lobby (EOS or Steam) void setupSplitscreen(); // used to resize player game views, for example if a player drops or we change the aspect ratio From b513cc9299e539e1c3c1c3c6fc4784759df24633 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:05:04 +1000 Subject: [PATCH 116/244] * dmg gib for miss, custom dmg gibs now transferred to clients over net --- src/actgib.cpp | 41 +++++++++++++++++++++++++++++++++++++---- src/game.hpp | 2 +- src/magic/castSpell.cpp | 4 ++-- src/net.cpp | 10 ++++++++++ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/actgib.cpp b/src/actgib.cpp index de974cba6..f00bd6c29 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -37,6 +37,7 @@ #define GIB_PLAYER my->skill[11] #define GIB_POOF my->skill[5] #define GIB_LIGHTING my->skill[6] +#define GIB_DMG_MISS my->skill[7] void poof(Entity* my) { if (GIB_POOF) { @@ -184,7 +185,7 @@ void actDamageGib(Entity* my) GIB_VELX = GIB_VELX * .95; GIB_VELY = GIB_VELY * .95; - if ( my->skill[3] == DMG_WEAKER || my->skill[3] == DMG_WEAKEST ) + if ( my->skill[3] == DMG_WEAKER || my->skill[3] == DMG_WEAKEST || my->skill[3] == DMG_MISS ) { real_t scale = 0.2; if ( my->ticks > 10 ) @@ -196,7 +197,8 @@ void actDamageGib(Entity* my) my->scalez = scale; } else if ( my->skill[3] == DMG_STRONGER - || my->skill[3] == DMG_STRONGEST ) + || my->skill[3] == DMG_STRONGEST + || my->skill[3] == DMG_MISS ) { real_t scale = 0.2; auto& anim = EnemyHPDamageBarHandler::damageGibAnimCurves[DMG_DEFAULT]; @@ -208,6 +210,10 @@ void actDamageGib(Entity* my) { scale *= anim[my->ticks] / 100.0; } + if ( my->skill[3] == DMG_MISS ) + { + scale = 0.2; + } my->scalex = scale; my->scaley = scale; my->scalez = scale; @@ -399,7 +405,7 @@ Entity* spawnGib(Entity* parentent, int customGibSprite) return entity; } -Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) +Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool miss, bool updateClients) { if ( !parentent ) { @@ -419,7 +425,7 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) entity->sizey = 1; real_t vel = (local_rng.rand() % 10) / 20.f; entity->vel_z = -.5; - if ( gibDmgType == DMG_STRONGER || gibDmgType == DMG_STRONGEST ) + if ( gibDmgType == DMG_STRONGER || gibDmgType == DMG_STRONGEST || gibDmgType == DMG_MISS ) { vel = 0.25; entity->vel_z = -.4; @@ -437,6 +443,7 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) entity->skill[0] = dmgAmount; entity->skill[3] = gibDmgType; entity->fskill[3] = 0.04; + entity->skill[7] = miss ? 1 : 0; entity->behavior = &actDamageGib; entity->ditheringDisabled = true; entity->flags[SPRITE] = true; @@ -474,6 +481,8 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) break; case DMG_POISON: break; + case DMG_MISS: + break; case DMG_HEAL: color = hudColors.characterSheetGreen; break; @@ -483,6 +492,30 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType) break; } entity->skill[6] = color; + + if ( updateClients ) + { + if ( multiplayer == SERVER ) + { + for ( int c = 1; c < MAXPLAYERS; c++ ) + { + if ( client_disconnected[c] || players[c]->isLocalPlayer() ) + { + continue; + } + strcpy((char*)net_packet->data, "DMGG"); + SDLNet_Write32(parentent->getUID(), &net_packet->data[4]); + SDLNet_Write16((Sint16)dmgAmount, &net_packet->data[8]); + net_packet->data[10] = gibDmgType; + net_packet->data[11] = miss ? 1 : 0; + net_packet->address.host = net_clients[c - 1].host; + net_packet->address.port = net_clients[c - 1].port; + net_packet->len = 12; + sendPacketSafe(net_sock, -1, net_packet, c - 1); + } + } + } + return entity; } diff --git a/src/game.hpp b/src/game.hpp index 1da96e768..622fd944e 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -245,7 +245,7 @@ void actGoldBag(Entity* my); void actGib(Entity* my); void actDamageGib(Entity* my); Entity* spawnGib(Entity* parentent, int customGibSprite = -1); -Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType); +Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool miss = false, bool updateClients = false); Entity* spawnGibClient(Sint16 x, Sint16 y, Sint16 z, Sint16 sprite); void serverSpawnGibForClient(Entity* gib); void actLadder(Entity* my); diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 4d093b2a1..7dacf7967 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -1532,7 +1532,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool totalHeal += std::max(players[i]->entity->getHP() - oldHP, 0); if ( totalHeal > 0 ) { - spawnDamageGib(players[i]->entity, -totalHeal, DamageGib::DMG_HEAL); + spawnDamageGib(players[i]->entity, -totalHeal, DamageGib::DMG_HEAL, false, true); } playSoundEntity(caster, 168, 128); @@ -1556,7 +1556,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool totalHeal += heal; if ( heal > 0 ) { - spawnDamageGib(entity, -heal, DamageGib::DMG_HEAL); + spawnDamageGib(entity, -heal, DamageGib::DMG_HEAL, false, true); } playSoundEntity(entity, 168, 128); spawnMagicEffectParticles(entity->x, entity->y, entity->z, 169); diff --git a/src/net.cpp b/src/net.cpp index 4fd6348bd..91e3d6b5d 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3023,6 +3023,16 @@ static std::unordered_map clientPacketHandlers = { } }}, + // custom damage gib (miss/healing) + {'DMGG', [](){ + Uint32 uid = SDLNet_Read32(&net_packet->data[4]); + Sint16 dmg = (Sint16)SDLNet_Read16(&net_packet->data[8]); + DamageGib gib = DMG_DEFAULT; + gib = (DamageGib)(net_packet->data[10]); + bool miss = net_packet->data[11] != 0 ? 1 : 0; + spawnDamageGib(uidToEntity(uid), dmg, gib, miss); + }}, + // ping {'PING', [](){ messagePlayer(clientnum, MESSAGE_MISC, Language::get(1117), (SDL_GetTicks() - pingtime)); From 25e99c08707120659694ff17228bbf9d1000483b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:05:33 +1000 Subject: [PATCH 117/244] * bat no charm no poly --- src/magic/magic.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index b729ac5a8..09b35e2dd 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -131,6 +131,7 @@ bool spellEffectDominate(Entity& my, spellElement_t& element, Entity& caster, En || hitstats->type == LICH_FIRE || hitstats->type == SHADOW || hitstats->type == MIMIC + || hitstats->type == OCTOPUS || (hitstats->type == VAMPIRE && MonsterData_t::nameMatchesSpecialNPCName(*hitstats, "bram kindly")) || (hitstats->type == COCKATRICE && !strncmp(map.name, "Cockatrice Lair", 15)) ) @@ -1307,6 +1308,7 @@ int getCharmMonsterDifficulty(Entity& my, Stat& myStats) case LICH_FIRE: case MINOTAUR: case MIMIC: + case OCTOPUS: difficulty = 666; break; } @@ -1706,7 +1708,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell if ( targetStats->type == LICH || targetStats->type == SHOPKEEPER || targetStats->type == DEVIL || targetStats->type == MINOTAUR || targetStats->type == LICH_FIRE || targetStats->type == LICH_ICE || (target->behavior == &actMonster && target->monsterAllySummonRank != 0) - || targetStats->type == MIMIC + || targetStats->type == MIMIC || targetStats->type == OCTOPUS || (targetStats->type == INCUBUS && !strncmp(targetStats->name, "inner demon", strlen("inner demon"))) || targetStats->type == SENTRYBOT || targetStats->type == SPELLBOT || targetStats->type == GYROBOT || targetStats->type == DUMMYBOT From a81d24587848db2914d856485fd916e0d0f55e14 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:06:02 +1000 Subject: [PATCH 118/244] * missed a bit for dmg gib miss --- src/draw.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/draw.cpp b/src/draw.cpp index 803b19d4e..f78d8347f 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2376,10 +2376,17 @@ void drawEntities3D(view_t* camera, int mode) } else { + if ( entity->skill[7] == 1 ) + { + glDrawSpriteFromImage(camera, entity, Language::get(6249), mode); + } + else + { snprintf(buf, sizeof(buf), "%d", entity->skill[0]); glDrawSpriteFromImage(camera, entity, buf, mode); } } + } else { glDrawSprite(camera, entity, mode); From 2aa90533edcc519ded34e877d46bf367ad9db0af Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:12:39 +1000 Subject: [PATCH 119/244] * bat commit --- src/actbeartrap.cpp | 4 +- src/actboulder.cpp | 4 + src/actmonster.cpp | 220 +++++++++- src/collision.cpp | 238 +++++++++-- src/entity.cpp | 183 +++++++- src/entity.hpp | 5 + src/entity_shared.cpp | 1 + src/maps.cpp | 20 + src/monster.hpp | 15 +- src/monster_bat.cpp | 940 ++++++++++++++++++++++++++++++++++++++++++ src/player.cpp | 12 +- src/stat_shared.cpp | 20 + 12 files changed, 1578 insertions(+), 84 deletions(-) create mode 100644 src/monster_bat.cpp diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index 9dcafae7c..e577e23d7 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -120,7 +120,7 @@ void actBeartrap(Entity* my) { continue; } - if ( stat->type == GYROBOT ) + if ( stat->type == GYROBOT || entity->isUntargetableBat() ) { continue; } @@ -952,7 +952,7 @@ void actBomb(Entity* my) { continue; } - if ( stat->type == GYROBOT ) + if ( stat->type == GYROBOT || entity->isUntargetableBat() ) { continue; } diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 64ece3142..36fa193c1 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -202,6 +202,10 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit if ( entity->behavior == &actPlayer || entity->behavior == &actMonster ) { + if ( entity->behavior == &actMonster && entity->isUntargetableBat() && my->z > -2.0 ) // boulder doesnt kill when not in air + { + return 0; + } if ( ignoreInsideEntity || entityInsideEntity( my, entity ) ) { Stat* stats = entity->getStats(); diff --git a/src/actmonster.cpp b/src/actmonster.cpp index e0754ae7b..0060076b4 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -36,35 +36,35 @@ float limbs[NUMMONSTERS][20][3]; bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING - { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // HUMAN - { 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // RAT - { 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GOBLIN + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // HUMAN + { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // RAT + { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GOBLIN { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // SLIME { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // TROLL - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // OCTOPUS + { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // OCTOPUS { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SPIDER { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // GHOUL { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SKELETON { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCORPION - { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // IMP + { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // IMP { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // CRAB - { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GNOME - { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 }, // DEMON + { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GNOME + { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 }, // DEMON { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SUCCUBUS { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // MIMIC { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // LICH { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // MINOTAUR { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // DEVIL - { 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER + { 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // KOBOLD - { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCARAB + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCARAB { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // CRYSTALGOLEM { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // INCUBUS { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // VAMPIRE { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // SHADOW - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // COCKATRICE + { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // COCKATRICE { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // INSECTOID - { 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GOATMAN + { 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GOATMAN { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // AUTOMATON { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_ICE { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_FIRE @@ -125,7 +125,7 @@ double sightranges[NUMMONSTERS] = 256, // GOBLIN 80, // SLIME 32, // TROLL - 0, // OCTOPUS + 128, // OCTOPUS 96, // SPIDER 128, // GHOUL 192, // SKELETON @@ -630,6 +630,10 @@ void Entity::updateEntityOnHit(Entity* attacker, bool alertTarget) { disturbMimic(attacker, true, true); } + else if ( myStats->type == OCTOPUS ) + { + disturbBat(attacker, true, true); + } } } @@ -2256,6 +2260,7 @@ void sentrybotPickSpotNoise(Entity* my, Stat* myStats) } void mimicResetIdle(Entity* my); +void batResetIdle(Entity* my); void monsterAnimate(Entity* my, Stat* myStats, double dist) { @@ -2294,6 +2299,7 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist) case GYROBOT: gyroBotAnimate(my, myStats, dist); break; case DUMMYBOT: dummyBotAnimate(my, myStats, dist); break; case MIMIC: mimicAnimate(my, myStats, dist); break; + case OCTOPUS: batAnimate(my, myStats, dist); break; default: break; } @@ -2385,6 +2391,7 @@ void actMonster(Entity* my) case GYROBOT: initGyroBot(my, nullptr); break; case DUMMYBOT: initDummyBot(my, nullptr); break; case MIMIC: initMimic(my, nullptr); break; + case OCTOPUS: initBat(my, nullptr); break; default: printlog("Unknown monster, can't init!"); break; } } @@ -2478,6 +2485,7 @@ void actMonster(Entity* my) case GYROBOT: initGyroBot(my, myStats); break; case DUMMYBOT: initDummyBot(my, myStats); break; case MIMIC: initMimic(my, myStats); break; + case OCTOPUS: initBat(my, myStats); break; default: break; //This should never be reached. } } @@ -3598,6 +3606,8 @@ void actMonster(Entity* my) case MIMIC: mimicDie(my); break; + case OCTOPUS: + batDie(my); default: break; //This should never be reached. } @@ -3913,6 +3923,10 @@ void actMonster(Entity* my) messagePlayer(monsterclicked, MESSAGE_INTERACTION, Language::get(6081)); } } + else if ( myStats->type == OCTOPUS && my->monsterSpecialState == BAT_REST ) + { + my->disturbBat(players[monsterclicked]->entity, false, false); + } else { messagePlayerMonsterEvent(monsterclicked, 0xFFFFFFFF, *myStats, Language::get(514), Language::get(515), MSG_COMBAT); @@ -4308,7 +4322,20 @@ void actMonster(Entity* my) { continue; } - if ( entityInsideEntity(my, entity) && entity->getRace() != GYROBOT ) + bool entityInside = entityInsideEntity(my, entity); + /*if ( my->getRace() == OCTOPUS && entity->getRace() == OCTOPUS ) + { + int x1 = my->sizex; + int y1 = my->sizey; + my->sizex = 2; + my->sizey = 2; + + int x2 = entity->sizex; + int y2 = entity->sizey; + entity->sizex = 2; + entity->sizey = 2; + }*/ + if ( entityInside && entity->getRace() != GYROBOT ) { if ( entity->behavior != &actDoorFrame ) { @@ -5106,7 +5133,7 @@ void actMonster(Entity* my) { std::vector> possibleCoordinates; my->monsterMoveTime = local_rng.rand() % 30; - if ( myStats->type == MIMIC ) + if ( myStats->type == MIMIC || myStats->type == OCTOPUS ) { my->monsterMoveTime = 2 + local_rng.rand() % 4; } @@ -5121,6 +5148,11 @@ void actMonster(Entity* my) searchLimitX = 5; searchLimitY = 5; } + else if ( myStats->type == OCTOPUS ) + { + searchLimitX = 7; + searchLimitY = 7; + } int lowerX = std::max(0, centerX - searchLimitX); // assigned upper/lower x coords from entity start position. int upperX = std::min(centerX + searchLimitX, map.width); @@ -5629,7 +5661,7 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != OCTOPUS ) { // break it down! my->monsterHitTime++; @@ -6738,7 +6770,7 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != OCTOPUS ) { // break it down! my->monsterHitTime++; @@ -7028,6 +7060,10 @@ void actMonster(Entity* my) { mimicResetIdle(my); } + else if ( !target && myStats->type == OCTOPUS ) + { + batResetIdle(my); + } else if ( my->monsterAllyState == ALLY_STATE_MOVETO ) { if ( my->monsterAllyInteractTarget != 0 ) @@ -8575,7 +8611,112 @@ void actMonster(Entity* my) } else { - if ( myStats && myStats->type == MIMIC && myStats->EFFECTS[EFF_MIMIC_LOCKED] && !my->isInertMimic() ) + if ( myStats && myStats->type == OCTOPUS && my->monsterSpecialState == BAT_REST ) + { + my->monsterReleaseAttackTarget(); + + if ( myReflex ) // randomly dont check + { + for ( node_t* node2 = map.creatures->first; node2 != nullptr; node2 = node2->next ) + { + Entity* entity = (Entity*)node2->element; + if ( entity == my || entity->flags[PASSABLE] ) + { + continue; + } + Stat* hitstats = entity->getStats(); + if ( hitstats != nullptr ) + { + if ( (my->checkEnemy(entity) || my->monsterTarget == entity->getUID() || ringconflict) ) + { + tangent = atan2(entity->y - my->y, entity->x - my->x); + dir = my->yaw - tangent; + while ( dir >= PI ) + { + dir -= PI * 2; + } + while ( dir < -PI ) + { + dir += PI * 2; + } + + bool visiontest = false; + real_t monsterVisionRange = 40.0; //sightranges[myStats->type]; + int sizex = my->sizex; + int sizey = my->sizey; + my->sizex = std::max(my->sizex, 4); // override size temporarily + my->sizey = std::max(my->sizey, 4); + if ( entityInsideEntity(my, entity) ) + { + visiontest = true; + } + my->sizex = sizex; + my->sizey = sizey; + if ( !visiontest ) + { + if ( local_rng.rand() % 4 != 0 ) + { + continue; // random chance to ignore + } + + // skip if light level is too low and distance is too high + int light = entity->entityLightAfterReductions(*hitstats, my); + double targetdist = sqrt(pow(my->x - entity->x, 2) + pow(my->y - entity->y, 2)); + + if ( targetdist > monsterVisionRange ) + { + continue; + } + if ( targetdist > TOUCHRANGE && targetdist > light ) + { + continue; + } + if ( dir >= -13 * PI / 16 && dir <= 13 * PI / 16 ) + { + visiontest = true; + } + } + + if ( visiontest ) // vision cone + { + lineTrace(my, my->x, my->y, tangent, monsterVisionRange, LINETRACE_IGNORE_ENTITIES, false); + if ( !hit.entity ) + { + lineTrace(my, my->x, my->y, tangent, TOUCHRANGE, 0, false); + } + if ( hit.entity == entity ) + { + // charge state + Entity* attackTarget = hit.entity; + if ( my->disturbBat(attackTarget, false, true) ) + { + my->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_ATTACK); + + if ( MONSTER_SOUND == nullptr ) + { + MONSTER_SOUND = playSoundEntity(my, MONSTER_SPOTSND + local_rng.rand() % MONSTER_SPOTVAR, 128); + } + + my->alertAlliesOnBeingHit(attackTarget); + + if ( entity != nullptr ) + { + if ( entity->behavior == &actPlayer ) + { + assailant[entity->skill[2]] = true; // as long as this is active, combat music doesn't turn off + assailantTimer[entity->skill[2]] = COMBAT_MUSIC_COOLDOWN; + } + } + } + break; + } + } + } + } + } + } + } + else if ( myStats && myStats->type == MIMIC && myStats->EFFECTS[EFF_MIMIC_LOCKED] && !my->isInertMimic() ) { my->monsterHitTime++; if ( my->monsterHitTime >= HITRATE ) @@ -8616,15 +8757,15 @@ void actMonster(Entity* my) { continue; } + if ( entity->behavior != &actPlayer ) + { + continue; + } Stat* hitstats = entity->getStats(); if ( hitstats != nullptr ) { if ( (my->checkEnemy(entity) || my->monsterTarget == entity->getUID() || ringconflict) ) { - if ( entity->behavior != &actPlayer ) - { - continue; - } tangent = atan2(entity->y - my->y, entity->x - my->x); dir = my->yaw - tangent; while ( dir >= PI ) @@ -8870,7 +9011,11 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } } } - if ( monsterIsImmobileTurret(this, myStats) ) + if ( myStats->type == OCTOPUS ) + { + bow = 1.5; + } + else if ( monsterIsImmobileTurret(this, myStats) ) { bow = 2; if ( myStats->type == SPELLBOT ) @@ -12709,6 +12854,37 @@ bool Entity::isInertMimic() const return false; } +bool Entity::isUntargetableBat(real_t* outDist) const +{ + if ( behavior == &actMonster && getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( bodyparts.size() >= 1 ) + { + auto& body = bodyparts[0]; + if ( body->z < -7.5 ) + { + if ( outDist ) + { + *outDist = body->z; + } + return true; + } + } + } + return false; +} + +void batResetIdle(Entity* my) +{ + if ( !my ) { return; } + // reset to inert after wandering with no target + + my->monsterSpecialState = BAT_REST; + serverUpdateEntitySkill(my, 33); + + my->monsterLookDir = (PI / 2) * (local_rng.rand() % 4); +} + void mimicResetIdle(Entity* my) { if ( !my ) { return; } diff --git a/src/collision.cpp b/src/collision.cpp index 297383c86..b65f3cf77 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -369,7 +369,7 @@ bool entityInsideTile(Entity* entity, int x, int y, int z, bool checkSafeTiles) { if ( !checkSafeTiles && !map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] ) { - if ( entity->behavior != &actDeathGhost ) + if ( entity->behavior != &actDeathGhost && !(entity->behavior == &actMonster && entity->getStats() && entity->getStats()->type == OCTOPUS) ) { return true; } @@ -405,6 +405,7 @@ bool entityInsideTile(Entity* entity, int x, int y, int z, bool checkSafeTiles) bool entityInsideEntity(Entity* entity1, Entity* entity2) { + if ( !entity1 || !entity2 ) { return false; } if ( entity1->x + entity1->sizex > entity2->x - entity2->sizex ) { if ( entity1->x - entity1->sizex < entity2->x + entity2->sizex ) @@ -461,7 +462,7 @@ bool entityInsideSomething(Entity* entity) { continue; } - if ( entity->behavior == &actDeathGhost ) + if ( entity->behavior == &actDeathGhost || entity->getMonsterTypeFromSprite() == OCTOPUS ) { if ( testEntity->behavior == &actMonster || testEntity->behavior == &actPlayer || (testEntity->isDamageableCollider() && (testEntity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC)) ) @@ -498,6 +499,85 @@ bool useSmallCollision(Entity& my, Stat& myStats, Entity& your, Stat& yourStats) return false; } +bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) +{ + if ( multiplayer == CLIENT ) { return false; } + if ( !projectile ) { return false; } + if ( (Sint32)getUID() < 0 ) + { + return false; + } + if ( projectile->collisionIgnoreTargets.find(getUID()) != projectile->collisionIgnoreTargets.end() ) + { + return true; + } + if ( behavior == &actMonster ) + { + if ( Stat* myStats = getStats() ) + { + if ( myStats->type == OCTOPUS ) + { + bool miss = false; + if ( isUntargetableBat() ) + { + projectile->collisionIgnoreTargets.insert(getUID()); + return true; + } + if ( monsterSpecialState == BAT_REST ) + { + miss = false; + } + bool backstab = false; + bool flanking = false; + real_t hitAngle = this->yawDifferenceFromEntity(projectile); + if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc + { + if ( monsterState == MONSTER_STATE_WAIT + || monsterState == MONSTER_STATE_PATH + || (monsterState == MONSTER_STATE_HUNT && uidToEntity(monsterTarget) == nullptr) ) + { + // unaware monster, get backstab damage. + backstab = true; + } + else if ( local_rng.rand() % 2 == 0 ) + { + // monster currently engaged in some form of combat maneuver + // 1 in 2 chance to flank defenses. + flanking = true; + } + } + + if ( backstab ) + { + miss = false; + } + else if ( flanking ) + { + miss = local_rng.rand() % 3 != 0; + } + else + { + miss = local_rng.rand() % 5 != 0; + } + + if ( miss ) + { + if ( projectile->collisionIgnoreTargets.find(getUID()) == projectile->collisionIgnoreTargets.end() ) + { + projectile->collisionIgnoreTargets.insert(getUID()); + if ( (parent && parent->behavior == &actPlayer) || (parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader()) ) + { + spawnDamageGib(this, 0, DamageGib::DMG_MISS, true, true); + } + } + } + return miss; + } + } + } + return false; +} + /*------------------------------------------------------------------------------- barony_clear @@ -550,6 +630,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) bool reduceCollisionSize = false; bool tryReduceCollisionSize = false; + bool projectileAttack = false; Entity* parent = nullptr; Stat* parentStats = nullptr; if ( my ) @@ -562,6 +643,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { if ( my->behavior == &actArrow || my->behavior == &actMagicMissile || my->behavior == &actThrown ) { + projectileAttack = true; if ( parent = uidToEntity(my->parent) ) { if ( my->behavior == &actThrown ) @@ -637,6 +719,12 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { entLists = TileEntityList.getEntitiesWithinRadius(static_cast(tx) >> 4, static_cast(ty) >> 4, 2); } + + Monster type = NOTHING; + if ( my && isMonster ) + { + type = my->getMonsterTypeFromSprite(); + } for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) { list_t* currentList = *it; @@ -649,7 +737,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) } if ( entity->flags[PASSABLE] ) { - if ( my->behavior == &actBoulder && entity->sprite == 886 ) + if ( my->behavior == &actBoulder && (entity->sprite == 886) ) { // 886 is gyrobot, as they are passable, force collision here. } @@ -658,20 +746,48 @@ int barony_clear(real_t tx, real_t ty, Entity* my) continue; } } + bool entityDodgeChance = false; if ( entity->behavior == &actParticleTimer && static_cast(entity->particleTimerTarget) == my->getUID() ) { continue; } if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO) - && my->behavior == &actMonster && my->getMonsterTypeFromSprite() == MINOTAUR ) + && my->behavior == &actMonster && type == MINOTAUR ) { continue; } if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC) - && ((my->behavior == &actMonster && my->getMonsterTypeFromSprite() == GYROBOT) || my->behavior == &actDeathGhost) ) + && ((my->behavior == &actMonster && (type == GYROBOT || type == OCTOPUS)) || my->behavior == &actDeathGhost) ) { continue; } + if ( entity->behavior == &actFurniture && type == OCTOPUS ) + { + continue; + } + if ( entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( my->behavior == &actBoulder ) + { + if ( entity->isUntargetableBat() && my->z > -2.0 ) + { + continue; + } + else + { + // force collision here. + } + } + else if ( projectileAttack ) + { + // calculate later if hit + entityDodgeChance = true; + } + else + { + continue; + } + } if ( (my->behavior == &actMonster || my->behavior == &actBoulder) && entity->behavior == &actDoorFrame ) { continue; // monsters don't have hard collision with door frames @@ -744,47 +860,59 @@ int barony_clear(real_t tx, real_t ty, Entity* my) continue; } } - else if ( multiplayer != CLIENT && tryReduceCollisionSize ) + else if ( multiplayer != CLIENT ) { - if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) + if ( entityDodgeChance ) { - if ( my->actmagicEmitter > 0 ) + if ( my->collisionIgnoreTargets.find(entity->getUID()) != my->collisionIgnoreTargets.end() ) { - auto& emitterHit = particleTimerEmitterHitEntities[my->actmagicEmitter]; - auto find = emitterHit.find(entity->getUID()); - if ( find != emitterHit.end() ) + continue; + } + } + if ( tryReduceCollisionSize ) + { + if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) + { + if ( my->actmagicEmitter > 0 ) { - if ( find->second.hits >= 3 || (ticks - find->second.tick) < 5 ) + auto& emitterHit = particleTimerEmitterHitEntities[my->actmagicEmitter]; + auto find = emitterHit.find(entity->getUID()); + if ( find != emitterHit.end() ) { - continue; + if ( find->second.hits >= 3 || (ticks - find->second.tick) < 5 ) + { + continue; + } } } } - } - if ( parent && parentStats && yourStats ) - { - reduceCollisionSize = useSmallCollision(*parent, *parentStats, *entity, *yourStats); - if ( reduceCollisionSize ) + if ( parent && parentStats && yourStats ) { - if ( parent->monsterIsTinkeringCreation() - && yourStats->mask && yourStats->mask->type == MASK_TECH_GOGGLES - && (parentStats->leader_uid == entity->getUID() - || parent->monsterAllyGetPlayerLeader() == entity) ) + reduceCollisionSize = useSmallCollision(*parent, *parentStats, *entity, *yourStats); + if ( reduceCollisionSize ) { - continue; - } - if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) - { - continue; + if ( parent->monsterIsTinkeringCreation() + && yourStats->mask && yourStats->mask->type == MASK_TECH_GOGGLES + && (parentStats->leader_uid == entity->getUID() + || parent->monsterAllyGetPlayerLeader() == entity) ) + { + continue; + } + if ( my->behavior == &actMagicMissile && my->actmagicSpray == 1 ) + { + continue; + } } + + + } + else if ( parent && parent->behavior == &actDeathGhost + && (entity->behavior == &actPlayer + || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) + { + reduceCollisionSize = true; } - } - else if ( parent && parent->behavior == &actDeathGhost - && (entity->behavior == &actPlayer - || (entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader())) ) - { - reduceCollisionSize = true; } } @@ -814,6 +942,17 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { if ( (entity->sizey > 0) && ((tymin >= eymin && tymin < eymax) || (tymax >= eymin && tymax < eymax) || (tymin <= eymin && tymax > eymax)) ) { + if ( multiplayer != CLIENT ) + { + if ( projectileAttack && entityDodgeChance ) + { + if ( entity->collisionProjectileMiss(parent, my) ) + { + continue; + } + } + } + tx2 = std::max(txmin, exmin); ty2 = std::max(tymin, eymin); hit.x = tx2; @@ -1074,7 +1213,8 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en //std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); bool ignoreFurniture = my && my->behavior == &actMonster && myStats && (myStats->type == SHOPKEEPER - || myStats->type == MINOTAUR); + || myStats->type == MINOTAUR + || myStats->type == OCTOPUS); for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) { @@ -1086,15 +1226,29 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en || ((entities == LINETRACE_IGNORE_ENTITIES) && ( (!entity->flags[BLOCKSIGHT] && entity->behavior != &actMonster) || (entity->behavior == &actMonster && (entity->flags[INVISIBLE] - && entity->sprite != 889 && entity->sprite != 1247) ) + && entity->sprite != 889 && entity->sprite != 1247 && entity->sprite != 1408) ) ) ) ) { // if entities == LINETRACE_IGNORE_ENTITIES, then ignore entities that block sight. // 16/11/19 - added exception to monsters. if monster, use the INVISIBLE flag to skip checking. - // 889/1247 is dummybot/mimic "invisible" AI entity. so it's invisible, need to make it shown here. - continue; + // 889/1247/1408 is dummybot/mimic/bat "invisible" AI entity. so it's invisible, need to make it shown here. + if ( entity->behavior == &actMonster && entity->sprite == 1408 ) + { + if ( (entity != target && target != nullptr) || entity == my || entity->flags[PASSABLE] ) + { + continue; + } + else if ( entity->isUntargetableBat() ) + { + continue; + } + } + else + { + continue; + } } if ( entity->behavior == &actParticleTimer ) { @@ -1106,6 +1260,10 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en { continue; // see through furniture cause we'll bust it down } + if ( entity->isUntargetableBat() ) + { + continue; + } int entitymapx = static_cast(entity->x) >> 4; int entitymapy = static_cast(entity->y) >> 4; @@ -1389,7 +1547,7 @@ real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, { ground = false; } - else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT ) + else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT || stats->type == OCTOPUS ) { ground = false; } @@ -1847,6 +2005,10 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity { continue; } + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + continue; + } if ( x >= (int)(entity->x - entity->sizex) && x <= (int)(entity->x + entity->sizex) ) { if ( y >= (int)(entity->y - entity->sizey) && y <= (int)(entity->y + entity->sizey) ) diff --git a/src/entity.cpp b/src/entity.cpp index 43b99eb1c..e80af819a 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -6622,6 +6622,11 @@ bool Entity::isMobile() return false; } + if ( entitystats->type == OCTOPUS && monsterSpecialState == BAT_REST ) + { + return false; + } + if ( (entitystats->type == LICH_FIRE || entitystats->type == LICH_ICE) && monsterLichBattleState < LICH_BATTLE_READY ) { @@ -7008,7 +7013,7 @@ void Entity::attack(int pose, int charge, Entity* target) else { serverUpdateEntitySkill(this, 8); - if (myStats->type != SLIME && myStats->type != RAT && myStats->type != SCARAB) { + if (myStats->type != SLIME && myStats->type != RAT && myStats->type != SCARAB && myStats->type != OCTOPUS) { serverUpdateEntitySkill(this, 9); } } @@ -7747,6 +7752,7 @@ void Entity::attack(int pose, int charge, Entity* target) } } bool whip = myStats->weapon && myStats->weapon->type == TOOL_WHIP; + bool miss = false; // normal attacks if ( target == nullptr ) { @@ -7760,6 +7766,110 @@ void Entity::attack(int pose, int charge, Entity* target) playSoundEntity(this, 23 + local_rng.rand() % 5, 128); // whoosh noise dist = lineTrace(this, x, y, yaw, STRIKERANGE, LINETRACE_ATK_CHECK_FRIENDLYFIRE, false); } + + if ( hit.entity && hit.entity->behavior == &actMonster ) + { + if ( hit.entity->getMonsterTypeFromSprite() == OCTOPUS ) + { + if ( hit.entity->isUntargetableBat() ) + { + miss = true; + } + else if ( hit.entity->monsterSpecialState == BAT_REST ) + { + miss = false; + } + else + { + Sint32 previousMonsterState = hit.entity->monsterState; + bool backstab = false; + bool flanking = false; + real_t hitAngle = hit.entity->yawDifferenceFromEntity(this); + if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc + { + if ( previousMonsterState == MONSTER_STATE_WAIT + || previousMonsterState == MONSTER_STATE_PATH + || (previousMonsterState == MONSTER_STATE_HUNT && uidToEntity(monsterTarget) == nullptr) ) + { + // unaware monster, get backstab damage. + backstab = true; + } + else if ( local_rng.rand() % 2 == 0 ) + { + // monster currently engaged in some form of combat maneuver + // 1 in 2 chance to flank defenses. + flanking = true; + } + } + + if ( backstab ) + { + miss = false; + } + else if ( flanking ) + { + miss = local_rng.rand() % 3 != 0; + } + else + { + miss = local_rng.rand() % 5 != 0; + } + } + + if ( miss ) + { + if ( !hit.entity->isUntargetableBat() ) + { + if ( player >= 0 || (behavior == &actMonster && monsterAllyGetPlayerLeader()) ) + { + spawnDamageGib(hit.entity, 0, DamageGib::DMG_MISS, true, true); + } + + bool doHitAlert = true; + if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) ) + { + // test for friendly fire + if ( checkFriend(hit.entity) ) + { + doHitAlert = false; + } + } + + if ( doHitAlert ) + { + Stat* hitstats = hit.entity->getStats(); + if ( hitstats ) + { + bool alertTarget = true; + if ( behavior == &actMonster && monsterAllyIndex != -1 && hit.entity->monsterAllyIndex != -1 ) + { + // if we're both allies of players, don't alert the hit target. + alertTarget = false; + } + + // alert the monster! + if ( hit.entity->monsterState != MONSTER_STATE_ATTACK ) + { + if ( alertTarget && hit.entity->monsterSpecialState == 0 ) + { + hit.entity->monsterAcquireAttackTarget(*this, MONSTER_STATE_PATH, true); + } + } + + // alert other monsters too + if ( alertTarget ) + { + hit.entity->alertAlliesOnBeingHit(this); + } + hit.entity->updateEntityOnHit(this, alertTarget); + } + } + } + + hit.entity = nullptr; + } + } + } } else { @@ -9711,6 +9821,17 @@ void Entity::attack(int pose, int charge, Entity* target) } } } + else if ( myStats->type == OCTOPUS ) + { + if ( !hitstats->EFFECTS[EFF_BLEEDING] ) + { + if ( hit.entity->setEffect(EFF_BLEEDING, true, 6 * TICKS_PER_SECOND, false) ) + { + statusInflicted = true; + messagePlayer(playerhit, MESSAGE_COMBAT, Language::get(701)); + } + } + } else if ( myStats->type == MIMIC && local_rng.rand() % 4 == 0 ) { Item* armor = nullptr; @@ -9990,7 +10111,7 @@ void Entity::attack(int pose, int charge, Entity* target) bool artifactWeaponProc = parashuProc || dyrnwynSmite || dyrnwynBurn || gugnirProc; // send messages - if ( !strcmp(hitstats->name, "") ) + if ( !strcmp(hitstats->name, "") || monsterNameIsGeneric(*hitstats) ) { Uint32 color = makeColorRGB(0, 255, 0); Uint32 colorSpecial = color;// makeColorRGB(255, 0, 255); @@ -10873,7 +10994,7 @@ void Entity::attack(int pose, int charge, Entity* target) } hitstats->EFFECTS[EFF_BLEEDING] = true; strcpy(playerHitMessage, Language::get(701)); - if ( !strcmp(hitstats->name, "") ) + if ( !strcmp(hitstats->name, "") || monsterNameIsGeneric(*hitstats) ) { strcpy(monsterHitMessage, Language::get(702)); } @@ -10889,7 +11010,7 @@ void Entity::attack(int pose, int charge, Entity* target) hitstats->EFFECTS_TIMERS[EFF_BLEEDING] = std::max(500 + (int)local_rng.rand() % 500 - hit.entity->getCON() * 10, 250); // 5-20 seconds hitstats->EFFECTS[EFF_BLEEDING] = true; strcpy(playerHitMessage, Language::get(2451)); - if ( !strcmp(hitstats->name, "") ) + if ( !strcmp(hitstats->name, "") || monsterNameIsGeneric(*hitstats) ) { strcpy(monsterHitMessage, Language::get(2452)); } @@ -10903,7 +11024,7 @@ void Entity::attack(int pose, int charge, Entity* target) hitstats->EFFECTS_TIMERS[EFF_BLEEDING] += std::max((int)local_rng.rand() % 350 - hit.entity->getCON() * 5, 100); // 2-7 seconds in addition hitstats->EFFECTS[EFF_BLEEDING] = true; strcpy(playerHitMessage, Language::get(2454)); - if ( !strcmp(hitstats->name, "") ) + if ( !strcmp(hitstats->name, "") || monsterNameIsGeneric(*hitstats) ) { strcpy(monsterHitMessage, Language::get(2455)); } @@ -11172,14 +11293,7 @@ void Entity::attack(int pose, int charge, Entity* target) else { Uint32 color = makeColorRGB(0, 255, 0); - if ( !strcmp(hitstats->name, "") ) - { - messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(2440), getMonsterLocalizedName(hitstats->type).c_str()); - } - else - { - messagePlayerColor(player, MESSAGE_COMBAT, color, Language::get(2439), hitstats->name); - } + messagePlayerMonsterEvent(player, color, *hitstats, Language::get(2440), Language::get(2439), MSG_COMBAT); } } } @@ -11276,7 +11390,7 @@ void Entity::attack(int pose, int charge, Entity* target) } else { - if ( (dist != STRIKERANGE && !whip) || (dist != STRIKERANGE * 1.5 && whip) ) + if ( !miss && ((dist != STRIKERANGE && !whip) || (dist != STRIKERANGE * 1.5 && whip)) ) { // hit a wall if ( pose == PLAYER_POSE_GOLEM_SMASH ) @@ -12377,6 +12491,10 @@ void Entity::awardXP(Entity* src, bool share, bool root) // calculate XP gain int baseXp = 10; + if ( srcStats->type == OCTOPUS ) + { + baseXp = 1 + local_rng.rand() % 2; + } int xpGain = baseXp + local_rng.rand() % std::max(1, baseXp) + std::max(0, srcStats->LVL - destStats->LVL) * baseXp; if ( srcStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] > 0 ) { @@ -13230,6 +13348,14 @@ bool Entity::checkEnemy(Entity* your) { result = ShopkeeperPlayerHostility.isPlayerEnemy(this->skill[2]); } + else if ( myStats->type == OCTOPUS && your->behavior == &actPlayer ) + { + result = true; + } + else if ( yourStats->type == OCTOPUS && behavior == &actPlayer ) + { + result = true; + } else if ( behavior == &actPlayer && myStats->type != HUMAN ) { result = swornenemies[HUMAN][yourStats->type]; @@ -15011,7 +15137,7 @@ int Entity::getAttackPose() const type == CREATURE_IMP || type == SUCCUBUS || type == SHOPKEEPER || type == MINOTAUR || type == SHADOW || type == RAT || type == SPIDER || type == CRAB || - type == MIMIC || + type == MIMIC || type == OCTOPUS || type == SLIME || (type == SCARAB && sprite != 1078 && sprite != 1079)) { pose = MONSTER_POSE_MELEE_WINDUP1; @@ -16723,7 +16849,7 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool bool hadOldTarget = (uidToEntity(monsterTarget) != nullptr); Sint32 oldMonsterState = monsterState; - if ( target.getRace() == GYROBOT || target.isInertMimic() ) + if ( target.getRace() == GYROBOT || target.isInertMimic() || target.isUntargetableBat() ) { return; } @@ -16745,6 +16871,10 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool { return; } + else if ( myStats->type == OCTOPUS && monsterSpecialState == BAT_REST ) + { + return; + } else if ( monsterIsImmobileTurret(this, myStats) ) { if ( monsterAllyIndex >= 0 && target.behavior == &actPlayer ) @@ -22055,10 +22185,29 @@ void Entity::alertAlliesOnBeingHit(Entity* attacker, std::unordered_set continue; } } + real_t tangent = atan2(entity->y - this->y, entity->x - this->x); - lineTrace(this, this->x, this->y, tangent, 1024, 0, false); + if ( buddystats->type == OCTOPUS && entity->isUntargetableBat() && entity->bodyparts.size() > 0 && entity->monsterSpecialState == BAT_REST ) + { + real_t oldZ = entity->bodyparts[0]->z; + entity->bodyparts[0]->z = 0.0; // hack to make it linetraceable + lineTrace(this, this->x, this->y, tangent, 64.0, 0, false); + entity->bodyparts[0]->z = oldZ; + } + else + { + lineTrace(this, this->x, this->y, tangent, 1024, 0, false); + } if ( hit.entity == entity ) { + if ( buddystats->type == OCTOPUS ) + { + if ( entity->monsterSpecialState == BAT_REST ) + { + entity->disturbBat(attacker, true, false); + continue; + } + } entity->monsterAcquireAttackTarget(*attacker, MONSTER_STATE_PATH); } } diff --git a/src/entity.hpp b/src/entity.hpp index fc2ae922a..ad5260fcb 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -1016,6 +1016,9 @@ class Entity void addToCreatureList(list_t* list); void addToWorldUIList(list_t *list); std::vector bodyparts; + std::set collisionIgnoreTargets; + + bool collisionProjectileMiss(Entity* parent, Entity* projectile); // special magic functions/trickery void castFallingMagicMissile(int spellID, real_t distFromCaster, real_t angleFromCasterDirection, int heightDelay); @@ -1076,7 +1079,9 @@ class Entity int getColliderLangName() const; static void monsterRollLevelUpStats(int increasestat[3]); bool disturbMimic(Entity* touched, bool takenDamage, bool doMessage); + bool disturbBat(Entity* touched, bool takenDamage, bool doMessage); bool isInertMimic() const; + bool isUntargetableBat(real_t* outDist = nullptr) const; bool entityCanVomit() const; bool doSilkenBowOnAttack(Entity* attacker); }; diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index f6d06151b..b11a48690 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -53,6 +53,7 @@ int checkSpriteType(Sint32 sprite) case 164: case 165: case 166: + case 188: //monsters return 1; break; diff --git a/src/maps.cpp b/src/maps.cpp index f3e05deae..cf549652e 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -81,8 +81,22 @@ Sint32 doorFrameSprite() { -------------------------------------------------------------------------------*/ +static ConsoleVariable cvar_monster_curve("/monster_curve", "nothing"); int monsterCurve(int level) { + if ( svFlags & SV_FLAG_CHEATS ) + { + for ( int i = 0; i < NUMMONSTERS; ++i ) + { + if ( *cvar_monster_curve == monstertypename[i] ) + { + if ( i != NOTHING ) + { + return i; + } + } + } + } if ( !strncmp(map.name, "The Mines", 9) ) // the mines { switch ( map_rng.rand() % 10 ) @@ -5513,6 +5527,7 @@ void assignActions(map_t* map) case 164: case 165: case 166: + case 188: { entity->sizex = 4; entity->sizey = 4; @@ -5569,6 +5584,7 @@ void assignActions(map_t* map) case 164: monsterType = SPELLBOT; break; case 165: monsterType = DUMMYBOT; break; case 166: monsterType = GYROBOT; break; + case 188: monsterType = OCTOPUS; break; default: monsterIsFixedSprite = false; monsterType = static_cast(monsterCurve(currentlevel)); @@ -5592,6 +5608,10 @@ void assignActions(map_t* map) entity->yaw = 90 * (map_rng.rand() % 4) * PI / 180.0; entity->monsterLookDir = entity->yaw; } + else if ( monsterType == OCTOPUS ) + { + entity->monsterSpecialState = BAT_REST; + } entity->seedEntityRNG(map_server_rng.getU32()); diff --git a/src/monster.hpp b/src/monster.hpp index b0dc5e185..6c63e32a6 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -114,6 +114,7 @@ static std::vector monsterSprites[NUMMONSTERS] = { // OCTOPUS { + 1408 }, // SPIDER @@ -296,7 +297,7 @@ static char monstertypename[][15] = "goblin", "slime", "troll", - "octopus", + "bat", "spider", "ghoul", "skeleton", @@ -337,7 +338,7 @@ static char monstertypenamecapitalized[][15] = "Goblin", "Slime", "Troll", - "Octopus", + "Bat", "Spider", "Ghoul", "Skeleton", @@ -429,7 +430,7 @@ static double damagetables[NUMMONSTERS][7] = { 0.9, 1.f, 1.1, 1.1, 1.1, 1.f, 0.8 }, // goblin { 1.4, 0.5, 1.3, 0.7, 0.5, 1.3, 0.5 }, // slime { 1.1, 0.8, 1.1, 0.8, 0.9, 1.f, 0.8 }, // troll - { 1.2, 1.f, 1.1, 0.9, 1.1, 1.f, 1.f }, // octopus + { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f }, // octopus { 1.f, 1.1, 1.f, 1.2, 1.1, 1.f, 1.1 }, // spider { 1.f, 1.2, 0.8, 1.1, 0.6, 0.8, 1.1 }, // ghoul { 0.5, 1.4, 0.8, 1.3, 0.5, 0.8, 1.1 }, // skeleton @@ -697,6 +698,7 @@ void initSentryBot(Entity* my, Stat* myStats); void initGyroBot(Entity* my, Stat* myStats); void initDummyBot(Entity* my, Stat* myStats); void initMimic(Entity* my, Stat* myStats); +void initBat(Entity* my, Stat* myStats); //--act*Limb functions-- void actHumanLimb(Entity* my); @@ -730,6 +732,7 @@ void actSentryBotLimb(Entity* my); void actGyroBotLimb(Entity* my); void actDummyBotLimb(Entity* my); void actMimicLimb(Entity* my); +void actBatLimb(Entity* my); //--*Die functions-- void humanDie(Entity* my); @@ -765,6 +768,7 @@ void sentryBotDie(Entity* my); void gyroBotDie(Entity* my); void dummyBotDie(Entity* my); void mimicDie(Entity* my); +void batDie(Entity* my); void monsterAnimate(Entity* my, Stat* myStats, double dist); //--*MoveBodyparts functions-- @@ -803,6 +807,7 @@ void sentryBotAnimate(Entity* my, Stat* myStats, double dist); void gyroBotAnimate(Entity* my, Stat* myStats, double dist); void dummyBotAnimate(Entity* my, Stat* myStats, double dist); void mimicAnimate(Entity* my, Stat* myStats, double dist); +void batAnimate(Entity* my, Stat* myStats, double dist); //--misc functions-- void actMinotaurTrap(Entity* my); @@ -1069,6 +1074,10 @@ static const int MIMIC_MAGIC = 2; static const int MIMIC_INERT_SECOND = 3; static const int MIMIC_STATUS_IMMOBILE = 4; +//-Bat-- +static const int BAT_REST = 1; +static const int BAT_REST_DISTURBED = 2; + struct MonsterData_t { struct MonsterDataEntry_t diff --git a/src/monster_bat.cpp b/src/monster_bat.cpp new file mode 100644 index 000000000..4b3ce47c4 --- /dev/null +++ b/src/monster_bat.cpp @@ -0,0 +1,940 @@ +/*------------------------------------------------------------------------------- + + BARONY + File: monster_sentrybot.cpp + Desc: implements all of the kobold monster's code + + Copyright 2013-2019 (c) Turning Wheel LLC, all rights reserved. + See LICENSE for details. + +-------------------------------------------------------------------------------*/ + +#include "main.hpp" +#include "game.hpp" +#include "stat.hpp" +#include "entity.hpp" +#include "items.hpp" +#include "monster.hpp" +#include "engine/audio/sound.hpp" +#include "book.hpp" +#include "net.hpp" +#include "collision.hpp" +#include "player.hpp" +#include "magic/magic.hpp" +#include "interface/interface.hpp" +#include "prng.hpp" +#include "mod_tools.hpp" + +void initBat(Entity* my, Stat* myStats) +{ + node_t* node; + + my->z = 0; + my->initMonster(1408); + my->flags[INVISIBLE] = true; // hide the "AI" bodypart + if ( multiplayer != CLIENT ) + { + MONSTER_SPOTSND = 666; + MONSTER_SPOTVAR = 1; + MONSTER_IDLESND = 667; + MONSTER_IDLEVAR = 3; + } + + if ( multiplayer != CLIENT && !MONSTER_INIT ) + { + auto& rng = my->entity_rng ? *my->entity_rng : local_rng; + + if ( myStats != nullptr ) + { + if ( !myStats->leader_uid ) + { + myStats->leader_uid = 0; + } + + // apply random stat increases if set in stat_shared.cpp or editor + setRandomMonsterStats(myStats, rng); + + // generate 6 items max, less if there are any forced items from boss variants + int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT; + + // generates equipment and weapons if available from editor + createMonsterEquipment(myStats, rng); + + // create any custom inventory items from editor if available + createCustomInventory(myStats, customItemsToGenerate, rng); + + // count if any custom inventory items from editor + int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity. + + // count any inventory items set to default in edtior + int defaultItems = countDefaultItems(myStats); + + my->setHardcoreStats(*myStats); + } + } + + // body + Entity* entity = newEntity(1408, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 2; + entity->sizey = 2; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->yaw = my->yaw; + entity->z = 6; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[OCTOPUS][1][0]; + entity->focaly = limbs[OCTOPUS][1][1]; + entity->focalz = limbs[OCTOPUS][1][2]; + entity->behavior = &actBatLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // head + entity = newEntity(1409, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 2; + entity->sizey = 2; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->yaw = my->yaw; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[OCTOPUS][2][0]; + entity->focaly = limbs[OCTOPUS][2][1]; + entity->focalz = limbs[OCTOPUS][2][2]; + entity->behavior = &actBatLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // wingleft + entity = newEntity(1410, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 2; + entity->sizey = 2; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->yaw = my->yaw; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[OCTOPUS][3][0]; + entity->focaly = limbs[OCTOPUS][3][1]; + entity->focalz = limbs[OCTOPUS][3][2]; + entity->behavior = &actBatLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // wingright + entity = newEntity(1411, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 2; + entity->sizey = 2; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->yaw = my->yaw; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[OCTOPUS][4][0]; + entity->focaly = limbs[OCTOPUS][4][1]; + entity->focalz = limbs[OCTOPUS][4][2]; + entity->behavior = &actBatLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + if ( multiplayer == CLIENT || MONSTER_INIT ) + { + return; + } +} + +void actBatLimb(Entity* my) +{ + my->actMonsterLimb(false); +} + +void batDie(Entity* my) +{ + int c; + for ( c = 0; c < 4; c++ ) + { + Entity* entity = spawnGib(my); + if ( entity ) + { + entity->skill[5] = 1; // poof + + switch ( c ) + { + case 0: + entity->sprite = 1408; + break; + case 1: + entity->sprite = 1409; + break; + case 2: + entity->sprite = 1410; + break; + case 3: + entity->sprite = 1411; + break; + default: + break; + } + + serverSpawnGibForClient(entity); + } + } + + my->spawnBlood(); + + playSoundEntity(my, 670 + local_rng.rand() % 2, 128); + + my->removeMonsterDeathNodes(); + + list_RemoveNode(my->mynode); + return; +} + +#define BAT_BODY 2 +#define BAT_HEAD 3 +#define BAT_LEFTWING 4 +#define BAT_RIGHTWING 5 + +#define BAT_FLOAT_X body->fskill[2] +#define BAT_FLOAT_Y body->fskill[3] +#define BAT_FLOAT_Z body->fskill[4] +#define BAT_FLOAT_ATK body->fskill[5] +#define BAT_REST_FLY_Z body->fskill[6] +#define BAT_REST_STATE body->skill[3] +#define BAT_REST_ROTATE body->fskill[7] + +bool Entity::disturbBat(Entity* touched, bool takenDamage, bool doMessage) +{ + if ( monsterSpecialState == BAT_REST ) + { + monsterSpecialState = BAT_REST_DISTURBED; + serverUpdateEntitySkill(this, 33); + + monsterHitTime = HITRATE; + + setEffect(EFF_STUNNED, true, 10, false); + if ( bodyparts.size() >= 1 ) + { + auto& body = bodyparts[0]; + if ( body->z < -15 ) + { + setEffect(EFF_STUNNED, true, 30, false); + } + else if ( body->z < -10 ) + { + setEffect(EFF_STUNNED, true, 20, false); + } + } + + if ( touched ) + { + lookAtEntity(*touched); + if ( !uidToEntity(monsterTarget) ) + { + if ( checkEnemy(touched) ) + { + monsterAcquireAttackTarget(*touched, MONSTER_STATE_PATH, true); + } + if ( doMessage ) + { + if ( touched->behavior == &actPlayer ) + { + messagePlayerColor(touched->skill[2], MESSAGE_INTERACTION, + makeColorRGB(255, 0, 0), Language::get(6252)); + } + } + } + } + return true; + } + return false; +} + +void batAnimate(Entity* my, Stat* myStats, double dist) +{ + node_t* node; + Entity* entity = nullptr; + Entity* head = nullptr; + int bodypart; + + my->flags[INVISIBLE] = true; // hide the "AI" bodypart + //my->flags[PASSABLE] = true; + + my->sizex = 2; + my->sizey = 2; + + my->focalx = limbs[OCTOPUS][0][0]; + my->focaly = limbs[OCTOPUS][0][1]; + my->focalz = limbs[OCTOPUS][0][2]; + if ( multiplayer != CLIENT ) + { + my->z = limbs[OCTOPUS][5][2]; + if ( !myStats->EFFECTS[EFF_LEVITATING] ) + { + myStats->EFFECTS[EFF_LEVITATING] = true; + myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0; + } + if ( !my->isMobile() ) + { + my->monsterRotate(); + } + } + + if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) + { + if ( keystatus[SDLK_KP_5] ) + { + my->yaw += 0.05; + } + if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + MONSTER_ATTACK = MONSTER_POSE_MELEE_WINDUP1; + MONSTER_ATTACKTIME = 0; + } + if ( keystatus[SDLK_h] ) + { + keystatus[SDLK_h] = 0; + myStats->EFFECTS[EFF_STUNNED] = !myStats->EFFECTS[EFF_STUNNED]; + myStats->EFFECTS_TIMERS[EFF_STUNNED] = myStats->EFFECTS[EFF_STUNNED] ? -1 : 0; + } + if ( keystatus[SDLK_j] ) + { + keystatus[SDLK_j] = 0; + my->monsterSpecialState = my->monsterSpecialState == 0 ? BAT_REST : 0; + if ( my->monsterSpecialState == BAT_REST ) + { + my->monsterReleaseAttackTarget(); + } + serverUpdateEntitySkill(my, 33); + } + } + + //Move bodyparts + Entity* leftWing = nullptr; + Entity* body = nullptr; + for ( bodypart = 0, node = my->children.first; node != nullptr; node = node->next, ++bodypart ) + { + if ( bodypart < BAT_BODY ) + { + continue; + } + + entity = (Entity*)node->element; + entity->x = my->x; + entity->y = my->y; + entity->z = my->z; + entity->yaw = my->yaw; + if ( body && bodypart != BAT_BODY ) + { + entity->yaw += PI * sin(BAT_REST_ROTATE * PI / 2); + } + + if ( bodypart == BAT_HEAD ) + { + entity->fskill[0] = fmod(entity->fskill[0], 2 * PI); + while ( entity->fskill[0] >= PI ) + { + entity->fskill[0] -= 2 * PI; + } + while ( entity->fskill[0] < -PI ) + { + entity->fskill[0] += 2 * PI; + } + if ( my->monsterSpecialState == BAT_REST && BAT_REST_STATE == 1 ) + { + entity->fskill[0] = std::max(body->fskill[0], entity->fskill[0]); + real_t speed = 0.2; + if ( limbAngleWithinRange(entity->fskill[0], speed, 3 * PI / 4) ) + { + entity->fskill[0] = 3 * PI / 4; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::min(entity->fskill[0], 3 * PI / 4); + } + } + else if ( MONSTER_ATTACK == 0 ) + { + real_t speed = -0.2; + real_t setpoint = PI / 16; + if ( entity->fskill[0] < (setpoint - 0.01) || limbAngleWithinRange(entity->fskill[0], speed, setpoint) ) + { + entity->fskill[0] = setpoint; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::max(entity->fskill[0], setpoint); + } + } + + if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP1 || MONSTER_ATTACK == 1 ) + { + entity->fskill[0] = PI / 16; + } + } + else if ( bodypart == BAT_BODY ) + { + body = entity; + entity->fskill[0] = fmod(entity->fskill[0], 2 * PI); + while ( entity->fskill[0] >= PI ) + { + entity->fskill[0] -= 2 * PI; + } + while ( entity->fskill[0] < -PI ) + { + entity->fskill[0] += 2 * PI; + } + if ( MONSTER_ATTACK == 0 ) + { + BAT_FLOAT_ATK = 0.0; + } + + if ( my->monsterSpecialState == BAT_REST && BAT_REST_STATE == 1 ) + { + real_t speed = 0.2; + if ( limbAngleWithinRange(entity->fskill[0], speed, PI / 2) ) + { + entity->fskill[0] = PI / 2; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::min(entity->fskill[0], PI / 2); + } + } + else if ( MONSTER_ATTACK == 0 ) + { + real_t speed = -0.2; + if ( entity->fskill[0] < -0.01 || limbAngleWithinRange(entity->fskill[0], speed, 0.0) ) + { + entity->fskill[0] = 0.0; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::max(entity->fskill[0], 0.0); + } + } + + if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP1 || MONSTER_ATTACK == 1 ) + { + if ( MONSTER_ATTACKTIME == 0 ) + { + entity->fskill[0] = 0.0; + entity->skill[1] = 0; + BAT_FLOAT_ATK = 0.0; + } + else + { + if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][15][0] ) + { + if ( MONSTER_ATTACKTIME == (int)limbs[OCTOPUS][15][0] ) + { + if ( multiplayer != CLIENT ) + { + const Sint32 temp = MONSTER_ATTACKTIME; + my->attack(1, 0, nullptr); // slop + MONSTER_ATTACKTIME = temp; + } + } + + if ( entity->skill[1] == 0 ) + { + real_t speed = limbs[OCTOPUS][13][2]; + if ( limbAngleWithinRange(entity->fskill[0], -speed, -((PI / 2) + PI / 4)) ) + { + entity->fskill[0] = -((PI / 2) + PI / 4); + entity->skill[1] = 1; + } + else + { + entity->fskill[0] -= speed; + entity->fskill[0] = std::max(entity->fskill[0], -((PI / 2) + PI / 4)); + } + } + else + { + real_t speed = limbs[OCTOPUS][13][1]; + entity->fskill[0] += speed; + entity->fskill[0] = std::min(entity->fskill[0], 0.0); + } + } + else + { + real_t speed = limbs[OCTOPUS][13][0]; + entity->fskill[0] -= speed; + entity->fskill[0] = std::max(entity->fskill[0], -((PI / 2) + PI / 32)); + } + + if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][18][0] ) + { + BAT_FLOAT_ATK -= limbs[OCTOPUS][18][1]; + BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][18][2]); + } + else if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][17][0] ) + { + BAT_FLOAT_ATK += limbs[OCTOPUS][17][1]; + BAT_FLOAT_ATK = std::min(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][17][2]); + } + else if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][16][0] ) + { + BAT_FLOAT_ATK -= limbs[OCTOPUS][16][1]; + BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][16][2]); + } + } + + if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][15][1] ) + { + MONSTER_ATTACK = 0; + } + } + } + else if ( bodypart == BAT_LEFTWING ) + { + entity->fskill[1] = fmod(entity->fskill[1], 2 * PI); + while ( entity->fskill[1] >= PI ) + { + entity->fskill[1] -= 2 * PI; + } + while ( entity->fskill[1] < -PI ) + { + entity->fskill[1] += 2 * PI; + } + entity->fskill[0] = fmod(entity->fskill[0], 2 * PI); + while ( entity->fskill[0] >= PI ) + { + entity->fskill[0] -= 2 * PI; + } + while ( entity->fskill[0] < -PI ) + { + entity->fskill[0] += 2 * PI; + } + + if ( my->monsterSpecialState == BAT_REST ) + { + { + real_t speed = -0.1; + real_t setpoint = -5 * PI / 8; + if ( limbAngleWithinRange(entity->fskill[1], speed, setpoint) ) + { + entity->fskill[1] = setpoint; + } + else + { + entity->fskill[1] += speed; + entity->fskill[1] = std::max(entity->fskill[1], setpoint); + } + } + { + real_t setpoint = 0.0; + if ( entity->fskill[0] < 0.01 ) + { + real_t speed = 0.1; + if ( limbAngleWithinRange(entity->fskill[0], speed, setpoint) ) + { + entity->fskill[0] = setpoint; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::min(entity->fskill[0], setpoint); + } + } + else if ( entity->fskill[0] > 0.01 ) + { + real_t speed = -0.1; + if ( limbAngleWithinRange(entity->fskill[0], speed, setpoint) ) + { + entity->fskill[0] = setpoint; + } + else + { + entity->fskill[0] += speed; + entity->fskill[0] = std::max(entity->fskill[0], setpoint); + } + } + else + { + entity->fskill[0] = setpoint; + } + } + } + + if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP1 || MONSTER_ATTACK == 1 ) + { + if ( MONSTER_ATTACKTIME == 0 ) + { + entity->fskill[1] = -PI / 2; + entity->skill[1] = 0; + } + + + if ( entity->skill[1] == 0 ) + { + real_t speed = limbs[OCTOPUS][14][0]; + if ( limbAngleWithinRange(entity->fskill[1], speed, 0.0) ) + { + entity->fskill[1] = 0.0; + entity->skill[1] = 1; + } + else + { + if ( entity->fskill[1] > 0.01 ) + { + entity->fskill[1] -= speed; + } + else if ( entity->fskill[1] < -0.01 ) + { + entity->fskill[1] += speed; + } + } + entity->fskill[0] = body->fskill[0]; + } + else if ( entity->skill[1] == 1 ) + { + real_t speed = limbs[OCTOPUS][14][0]; + if ( limbAngleWithinRange(entity->fskill[1], speed, PI / 4) ) + { + entity->fskill[1] = PI / 4; + entity->skill[1] = 2; + } + else + { + entity->fskill[1] += speed; + } + entity->fskill[0] = body->fskill[0]; + } + else if ( entity->skill[1] == 2 ) + { + real_t speed = limbs[OCTOPUS][14][1]; + real_t setpoint = -PI / 2 - PI / 8; + if ( limbAngleWithinRange(entity->fskill[1], -speed, setpoint) ) + { + entity->fskill[1] = setpoint; + entity->skill[1] = 3; + } + else + { + entity->fskill[1] -= speed; + } + entity->fskill[0] = body->fskill[0]; + } + else if ( entity->skill[1] == 3 ) + { + real_t speed = limbs[OCTOPUS][14][2]; + if ( limbAngleWithinRange(entity->fskill[1], speed, 0.0) ) + { + entity->fskill[1] = 0.0; + entity->skill[1] = 4; + + entity->skill[0] = 0; // reset flap to raise + } + else + { + entity->fskill[1] += speed; + } + if ( limbAngleWithinRange(entity->fskill[0], speed * 2, 0.0) ) + { + entity->fskill[0] = 0.0; + } + else + { + entity->fskill[0] += speed * 2; + } + } + } + } + + switch ( bodypart ) + { + case BAT_BODY: + { + if ( my->monsterSpecialState == BAT_REST && BAT_REST_STATE == 1 ) + { + real_t diff = std::max(0.002, (1.0 - BAT_REST_ROTATE) / 20.0); + BAT_REST_ROTATE += diff; + BAT_REST_ROTATE = std::min(1.0, BAT_REST_ROTATE); + } + else + { + real_t diff = std::max(0.05, BAT_REST_ROTATE / 20.0); + BAT_REST_ROTATE -= diff; + BAT_REST_ROTATE = std::max(0.0, BAT_REST_ROTATE); + } + entity->yaw += PI * sin(BAT_REST_ROTATE * PI / 2); + + entity->x += limbs[OCTOPUS][6][0] * cos(entity->yaw); + entity->y += limbs[OCTOPUS][6][1] * sin(entity->yaw); + entity->z += limbs[OCTOPUS][6][2]; + entity->focalx = limbs[OCTOPUS][1][0]; + entity->focaly = limbs[OCTOPUS][1][1]; + entity->focalz = limbs[OCTOPUS][1][2]; + + if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) + { + if ( keystatus[SDLK_KP_6] ) + { + entity->fskill[0] -= 0.05; + } + else if ( keystatus[SDLK_KP_4] ) + { + entity->fskill[0] += 0.05; + } + } + entity->pitch = entity->fskill[0]; + + if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) + { + if ( keystatus[SDLK_KP_PLUS] ) + { + keystatus[SDLK_KP_PLUS] = 0; + entity->skill[0] = entity->skill[0] == 0 ? 1 : 0; + } + } + if ( entity->skill[0] == 0 ) + { + entity->fskill[1] += 0.1; + } + + if ( my->monsterSpecialState == BAT_REST ) + { + BAT_FLOAT_X = 0.0; + BAT_FLOAT_Y = 0.0; + BAT_FLOAT_Z = 0.0; + + int mapx = static_cast(my->x) >> 4; + int mapy = static_cast(my->y) >> 4; + if ( mapx >= 0 && mapx < map.width && mapy >= 0 && mapy < map.height ) + { + int mapIndex = (mapy)*MAPLAYERS + (mapx) * MAPLAYERS * map.height; + if ( !map.tiles[(MAPLAYERS - 1) + mapIndex] ) + { + // no ceiling + if ( BAT_REST_FLY_Z <= -19.0 ) + { + BAT_REST_FLY_Z = -19.0; + BAT_REST_STATE = 1; + } + else + { + real_t diff = std::max(0.05, (BAT_REST_FLY_Z - -19.0) / 25.0); + BAT_REST_FLY_Z -= diff; + } + } + else + { + if ( BAT_REST_FLY_Z <= -3.0 ) + { + BAT_REST_FLY_Z = -3.0; + BAT_REST_STATE = 1; + } + else + { + real_t diff = std::max(0.05, (BAT_REST_FLY_Z - -3.0) / 25.0); + BAT_REST_FLY_Z -= diff; + } + } + } + } + else + { + BAT_REST_STATE = 0; + + if ( multiplayer != CLIENT ) + { + if ( my->monsterSpecialState == BAT_REST_DISTURBED ) + { + my->monsterSpecialState = 0; + } + } + + if ( BAT_REST_FLY_Z >= 0.0 ) + { + BAT_REST_FLY_Z = 0.0; + } + else + { + real_t diff = std::max(0.1, (-BAT_REST_FLY_Z) / 25.0); + BAT_REST_FLY_Z += diff; + } + + BAT_FLOAT_X = limbs[OCTOPUS][10][0] * sin(body->fskill[1] * limbs[OCTOPUS][11][0]) * cos(entity->yaw + PI / 2); + BAT_FLOAT_Y = limbs[OCTOPUS][10][1] * sin(body->fskill[1] * limbs[OCTOPUS][11][1]) * sin(entity->yaw + PI / 2); + BAT_FLOAT_Z = limbs[OCTOPUS][10][2] * sin(body->fskill[1] * limbs[OCTOPUS][11][2]); + real_t floatAtkZ = BAT_FLOAT_ATK < 0 ? 2 * sin(BAT_FLOAT_ATK * PI / 8) : 0.5 * sin(BAT_FLOAT_ATK * PI / 8); + BAT_FLOAT_Z += floatAtkZ; + } + + BAT_FLOAT_X += BAT_FLOAT_ATK * cos(entity->yaw); + BAT_FLOAT_Y += BAT_FLOAT_ATK * sin(entity->yaw); + + + entity->x += BAT_FLOAT_X; + entity->y += BAT_FLOAT_Y; + entity->z += BAT_FLOAT_Z; + entity->z += BAT_REST_FLY_Z; + break; + } + case BAT_HEAD: + entity->x += limbs[OCTOPUS][7][0] * cos(entity->yaw); + entity->y += limbs[OCTOPUS][7][1] * sin(entity->yaw); + entity->z += limbs[OCTOPUS][7][2]; + entity->focalx = limbs[OCTOPUS][2][0]; + entity->focaly = limbs[OCTOPUS][2][1]; + entity->focalz = limbs[OCTOPUS][2][2]; + if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) + { + if ( keystatus[SDLK_KP_7] ) + { + entity->fskill[0] -= 0.05; + } + else if ( keystatus[SDLK_KP_9] ) + { + entity->fskill[0] += 0.05; + } + } + entity->pitch = entity->fskill[0]; + + if ( body ) + { + entity->x += BAT_FLOAT_X; + entity->y += BAT_FLOAT_Y; + entity->z += BAT_FLOAT_Z; + entity->z += BAT_REST_FLY_Z; + } + break; + case BAT_LEFTWING: + entity->x += limbs[OCTOPUS][8][0] * cos(entity->yaw + PI / 2); + entity->y += limbs[OCTOPUS][8][1] * sin(entity->yaw + PI / 2); + entity->z += limbs[OCTOPUS][8][2]; + entity->focalx = limbs[OCTOPUS][3][0]; + entity->focaly = limbs[OCTOPUS][3][1]; + entity->focalz = limbs[OCTOPUS][3][2]; + + if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) + { + if ( keystatus[SDLK_KP_1] ) + { + entity->fskill[0] -= 0.05; + } + else if ( keystatus[SDLK_KP_3] ) + { + entity->fskill[0] += 0.05; + } + if ( keystatus[SDLK_KP_0] ) + { + entity->fskill[1] -= 0.05; + } + else if ( keystatus[SDLK_KP_ENTER] ) + { + entity->fskill[1] += 0.05; + } + + if ( keystatus[SDLK_KP_MINUS] ) + { + keystatus[SDLK_KP_MINUS] = 0; + entity->skill[3] = entity->skill[3] == 0 ? 1 : 0; + } + } + + if ( (entity->skill[3] == 0) && MONSTER_ATTACK == 0 && !(my->monsterSpecialState == BAT_REST && BAT_REST_STATE == 1) ) + { + if ( entity->fskill[1] >= 0.8 ) + { + entity->skill[0] = 1; + } + if ( entity->fskill[1] <= -1.3 ) + { + entity->skill[0] = 0; + } + if ( entity->skill[0] == 0 ) + { + entity->fskill[1] += 0.35; + } + else if ( entity->skill[0] == 1) + { + entity->fskill[1] -= 0.35; + } + } + entity->pitch = entity->fskill[0]; + entity->roll = entity->fskill[1]; + leftWing = entity; + + if ( body ) + { + entity->x += BAT_FLOAT_X; + entity->y += BAT_FLOAT_Y; + entity->z += BAT_FLOAT_Z; + entity->z += BAT_REST_FLY_Z; + + entity->x += limbs[OCTOPUS][12][0] * sin(body->pitch) * cos(entity->yaw); + entity->y += limbs[OCTOPUS][12][1] * sin(body->pitch) * sin(entity->yaw); + entity->z += limbs[OCTOPUS][12][2] * sin(body->pitch); + } + break; + case BAT_RIGHTWING: + entity->x += limbs[OCTOPUS][9][0] * cos(entity->yaw + PI / 2); + entity->y += limbs[OCTOPUS][9][1] * sin(entity->yaw + PI / 2); + entity->z += limbs[OCTOPUS][9][2]; + entity->focalx = limbs[OCTOPUS][4][0]; + entity->focaly = limbs[OCTOPUS][4][1]; + entity->focalz = limbs[OCTOPUS][4][2]; + if ( leftWing ) + { + entity->fskill[0] = leftWing->fskill[0]; + entity->fskill[1] = -leftWing->fskill[1]; + } + entity->pitch = entity->fskill[0]; + entity->roll = entity->fskill[1]; + + if ( body ) + { + entity->x += BAT_FLOAT_X; + entity->y += BAT_FLOAT_Y; + entity->z += BAT_FLOAT_Z; + entity->z += BAT_REST_FLY_Z; + + entity->x += limbs[OCTOPUS][12][0] * sin(body->pitch) * cos(entity->yaw); + entity->y += limbs[OCTOPUS][12][1] * sin(body->pitch) * sin(entity->yaw); + entity->z += limbs[OCTOPUS][12][2] * sin(body->pitch); + + } + break; + default: + break; + } + } + + if ( MONSTER_ATTACK > 0 ) + { + MONSTER_ATTACKTIME++; + } + else if ( MONSTER_ATTACK == 0 ) + { + MONSTER_ATTACKTIME = 0; + } + else + { + // do nothing, don't reset attacktime or increment it. + } +} \ No newline at end of file diff --git a/src/player.cpp b/src/player.cpp index 3b93c7f7c..e421c0e1d 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3415,6 +3415,10 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) return 0.0; } } + if ( parent->getMonsterTypeFromSprite() == OCTOPUS && !selectInteract ) + { + return 0.0; + } if ( parent->behavior == &actPlayer || (parent->behavior == &actMonster && !(parent->isInertMimic())) ) { @@ -3567,7 +3571,7 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) particle->y = previousy; particle->z = 7.5;*/ } - else if ( parent && parent->behavior == &actBoulderTrapHole ) + else if ( parent && (parent->behavior == &actBoulderTrapHole || parent->isUntargetableBat()) ) { // more accurate line of sight, look up real_t startx = cameras[player.playernum].x * 16.0; @@ -3586,6 +3590,10 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) const real_t yaw = cameras[player.playernum].ang; bool onCeilingLayer = parent->z > -11.0 && parent->z < -10; + if ( parent->isUntargetableBat() ) + { + onCeilingLayer = false; + } real_t endz = onCeilingLayer ? -8.0 : -8.0 - 16.0; for ( ; startz > endz; startz -= abs((0.1) * tan(pitch)) ) { @@ -4456,7 +4464,7 @@ void Player::WorldUI_t::handleTooltips() parent = uidToEntity(tooltip->parent); if ( parent && parent->flags[INVISIBLE] && !(parent->behavior == &actMonster && - (parent->getMonsterTypeFromSprite() == DUMMYBOT || parent->getMonsterTypeFromSprite() == MIMIC)) ) + (parent->getMonsterTypeFromSprite() == DUMMYBOT || parent->getMonsterTypeFromSprite() == MIMIC || parent->getMonsterTypeFromSprite() == OCTOPUS)) ) { continue; } diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index d81b4f251..adf05d7b6 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1302,6 +1302,26 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->GOLD = 0; stats->RANDOM_GOLD = 0; break; + case 188: + case (1000 + OCTOPUS): + stats->type = OCTOPUS; + stats->appearance = local_rng.rand(); + stats->inventory.first = NULL; + stats->inventory.last = NULL; + stats->MAXHP = 10; + stats->HP = stats->MAXHP; + stats->OLDHP = stats->HP; + stats->STR = 0; + stats->DEX = 0; + stats->CON = 0; + stats->INT = -5; + stats->PER = 10; + stats->CHR = 0; + stats->EXP = 0; + stats->LVL = 1; + stats->GOLD = 0; + stats->RANDOM_GOLD = 0; + break; case 10: default: break; From 1c962789cab03d316a0106e0508d2ccd7f1e894b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:12:50 +1000 Subject: [PATCH 120/244] * update path debug msg to include sprite --- src/paths.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/paths.cpp b/src/paths.cpp index 59920c0c8..998bec76c 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -896,8 +896,8 @@ list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, auto now = std::chrono::high_resolution_clock::now(); ms = std::chrono::duration_cast(now - pathtime); DebugStats.gui2 = DebugStats.gui2 + ms; - messagePlayer(0, MESSAGE_DEBUG, "FAIL (%d) uid: %d : path tries: %d (%d, %d) to (%d, %d)", - (int)pathingType, my->getUID(), tries, x1, y1, x2, y2); + messagePlayer(0, MESSAGE_DEBUG, "FAIL (%d) sprite: %d uid: %d : path tries: %d (%d, %d) to (%d, %d)", + (int)pathingType, my->sprite, my->getUID(), tries, x1, y1, x2, y2); } lastGeneratePathTries = tries; if (my->behavior == &actMonster) { From 8ecbc2f0acd1a9a50313ff37e60bbc97ce3b8ebd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:14:14 +1000 Subject: [PATCH 121/244] * slime tweak stats a little, lower dex/hp. add scaling to spray atk --- src/magic/actmagic.cpp | 7 ++++++- src/monster_slime.cpp | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 97b0a7563..857fec0f8 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -3000,6 +3000,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int damage = element->damage; damage += (spellbookDamageBonus * damage); damage /= (1 + (int)resistance); + damage = std::max(2, damage); hit.entity->chestHandleDamageMagic(damage, *my, parent); if ( my->actmagicProjectileArc > 0 ) { @@ -6224,7 +6225,11 @@ void actParticleTimer(Entity* my) { elementNode = element->elements.first; element = (spellElement_t*)elementNode->element; - element->damage = 3; + element->damage = 2; + if ( Stat* stats = parent->getStats() ) + { + element->damage += stats->getProficiency(PRO_MAGIC) / 10; + } element->mana = 5; } diff --git a/src/monster_slime.cpp b/src/monster_slime.cpp index afa2d11da..6a8319b74 100644 --- a/src/monster_slime.cpp +++ b/src/monster_slime.cpp @@ -163,21 +163,30 @@ void slimeSetStats(Entity& my, Stat& myStats) int level = std::max(currentlevel, 0) / LENGTH_OF_LEVEL_REGION; myStats.LVL += 3 * level; myStats.STR += 3 * level; - + myStats.HP += 20 * level; + if ( currentlevel >= 20 ) + { + myStats.HP += 10 * level; + } if ( color == "slime metal" ) { myStats.CON = 20 + 5 * level; myStats.STR += 2 * level; + myStats.DEX += 2 * level; + myStats.DEX = std::min(myStats.DEX, 0); } else { myStats.CON += 3 * level; + myStats.DEX += 2 * level; + myStats.DEX = std::min(myStats.DEX, 4); } - myStats.DEX += 2 * level; myStats.PER += 1 * level; myStats.MAXHP = myStats.HP; myStats.OLDHP = myStats.HP; + + myStats.setProficiency(PRO_MAGIC, level * 10); } void initSlime(Entity* my, Stat* myStats) @@ -571,7 +580,10 @@ void slimeAnimate(Entity* my, Stat* myStats, double dist) my->sprite = getSlimeFrame(color, 4); my->focalz = -2.5; const Sint32 temp = MONSTER_ATTACKTIME; - my->attack(1, 0, nullptr); // slop + if ( multiplayer != CLIENT ) + { + my->attack(1, 0, nullptr); // slop + } MONSTER_ATTACKTIME = temp; } else if (MONSTER_ATTACKTIME == frame * 7) { // frame 5 From 145391fdd82a558138287cf2ccd0649ab5c833df Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:14:44 +1000 Subject: [PATCH 122/244] * compendium hardcore mode stat calcs --- src/mod_tools.cpp | 261 ++++++++++++++++++++++++++++++++++++++++++++++ src/mod_tools.hpp | 4 + 2 files changed, 265 insertions(+) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index a3fb8c6a6..caf198c32 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -12214,6 +12214,18 @@ void Compendium_t::readMonstersFromFile() jsonVecToVec(stats["atk"], monster.atk); jsonVecToVec(stats["rangeatk"], monster.rangeatk); jsonVecToVec(stats["pwr"], monster.pwr); + if ( stats.HasMember("str") ) + { + jsonVecToVec(stats["str"], monster.str); + } + if ( stats.HasMember("con") ) + { + jsonVecToVec(stats["con"], monster.con); + } + if ( stats.HasMember("dex") ) + { + jsonVecToVec(stats["dex"], monster.dex); + } if ( stats.HasMember("lvl") ) { jsonVecToVec(stats["lvl"], monster.lvl); @@ -16248,6 +16260,255 @@ void Compendium_t::PointsAnim_t::pointsChangeEvent(Sint32 amount) pointsCurrent = balance; } } + +std::vector Compendium_t::CompendiumMonsters_t::Monster_t::getDisplayStat(const char* name) +{ + std::vector retVal; + if ( !name ) + { + return retVal; + } + + bool ignoreHardcore = + (monsterType == DUMMYBOT + || monsterType == GYROBOT + || monsterType == SENTRYBOT + || monsterType == SPELLBOT + || monsterType == NOTHING + || monsterType == HUMAN + ); + bool hardcore = !intro && (svFlags & SV_FLAG_HARDCORE); + + Stat stats(1000 + monsterType); + if ( !strcmp(name, "hp") ) + { + if ( !hardcore || ignoreHardcore ) + { + return hp; + } + if ( hp.size() > 0 ) + { + Sint32 statMin = hp[0]; + Sint32 statMax = statMin; + if ( hp.size() > 1 ) + { + statMax = hp[1]; + } + + int statIncrease = ((abs(statMin) / 20) * 20); + statMin += statIncrease - (statIncrease / 5); + statIncrease = ((abs(statMax) / 20) * 20); + statMax += statIncrease; + retVal.push_back(statMin); + if ( statMax != statMin ) + { + retVal.push_back(statMax); + } + } + } + else if ( !strcmp(name, "spd") ) + { + if ( !hardcore || ignoreHardcore ) + { + return spd; + } + if ( spd.size() > 0 ) + { + Sint32 statMin = spd[0]; + Sint32 statMax = statMin; + if ( spd.size() > 1 ) + { + statMax = spd[1]; + } + + int statIncrease = std::min((abs(statMin) / 4) * 1, 8); + statMin += statIncrease - (statIncrease / 2); + statIncrease = std::min((abs(statMax) / 4) * 1, 8); + statMax += statIncrease; + retVal.push_back(statMin); + if ( statMax != statMin ) + { + retVal.push_back(statMax); + } + } + } + else if ( !strcmp(name, "ac") ) + { + if ( !hardcore || ignoreHardcore ) + { + return ac; + } + + Sint32 statMin = stats.CON; + Sint32 statMax = stats.CON + stats.RANDOM_CON; + if ( con.size() > 0 ) + { + statMin = con[0]; + if ( con.size() > 1 ) + { + statMax = con[1]; + } + else + { + statMax = con[0] + stats.RANDOM_CON; + } + } + + int statIncrease = (abs(statMin) / 5) * 1; + int minIncrease = statIncrease - (statIncrease / 2); + statIncrease = (abs(statMax) / 5) * 1; + int maxIncrease = statIncrease; + + if ( ac.size() > 0 ) + { + retVal.push_back(ac[0] + minIncrease); + if ( ac.size() > 1 ) + { + if ( ac[1] + maxIncrease != retVal[0] ) + { + retVal.push_back(ac[1] + maxIncrease); + } + } + else + { + if ( ac[0] + maxIncrease != retVal[0] ) + { + retVal.push_back(ac[0] + maxIncrease); + } + } + } + } + else if ( !strcmp(name, "atk") ) + { + if ( !hardcore || ignoreHardcore ) + { + return atk; + } + + Sint32 statMin = stats.STR; + Sint32 statMax = stats.STR + stats.RANDOM_STR; + if ( str.size() > 0 ) + { + statMin = str[0]; + if ( str.size() > 1 ) + { + statMax = str[1]; + } + else + { + statMax = str[0] + stats.RANDOM_STR; + } + } + + int statIncrease = (abs(statMin) / 5) * 5; + int minIncrease = statIncrease - (statIncrease / 4); + statIncrease = (abs(statMax) / 5) * 5; + int maxIncrease = statIncrease; + + if ( atk.size() > 0 ) + { + retVal.push_back(atk[0] + minIncrease); + if ( atk.size() > 1 ) + { + if ( atk[1] + maxIncrease != retVal[0] ) + { + retVal.push_back(atk[1] + maxIncrease); + } + } + else + { + if ( atk[0] + maxIncrease != retVal[0] ) + { + retVal.push_back(atk[0] + maxIncrease); + } + } + } + } + else if ( !strcmp(name, "rangeatk") ) + { + if ( !hardcore || ignoreHardcore ) + { + return rangeatk; + } + + Sint32 statMinIncrease = 0; + Sint32 statMaxIncrease = 0; + + { + Sint32 statMinDEX = stats.DEX; + Sint32 statMaxDEX = stats.DEX + stats.RANDOM_DEX; + int statIncrease = std::min((abs(statMinDEX) / 4) * 1, 8); + int minIncrease = (statIncrease / 2); + statIncrease = std::min((abs(statMaxDEX) / 4) * 1, 8); + int maxIncrease = statIncrease; + + statMinIncrease += minIncrease; + statMaxIncrease += maxIncrease; + } + { + Sint32 statMinPER = stats.PER; + Sint32 statMaxPER = stats.PER + stats.RANDOM_PER; + int statIncrease = (abs(statMinPER) / 5) * 5; + int minIncrease = statIncrease - (statIncrease / 4); + statIncrease = (abs(statMaxPER) / 5) * 5; + int maxIncrease = statIncrease; + + statMinIncrease += minIncrease; + statMaxIncrease += maxIncrease; + } + + if ( rangeatk.size() > 0 ) + { + retVal.push_back(rangeatk[0] + statMinIncrease); + if ( rangeatk.size() > 1 ) + { + if ( rangeatk[1] + statMaxIncrease != retVal[0] ) + { + retVal.push_back(rangeatk[1] + statMaxIncrease); + } + } + else + { + if ( rangeatk[0] + statMaxIncrease != retVal[0] ) + { + retVal.push_back(rangeatk[0] + statMaxIncrease); + } + } + } + } + else if ( !strcmp(name, "pwr") ) + { + return pwr; + } + else if ( !strcmp(name, "lvl") ) + { + if ( !hardcore || ignoreHardcore ) + { + return lvl; + } + + if ( lvl.size() > 0 ) + { + Sint32 statMin = lvl[0]; + Sint32 statMax = statMin; + if ( lvl.size() > 1 ) + { + statMax = lvl[1] + 1; + } + else + { + statMax = statMin + 1; + } + retVal.push_back(statMin); + if ( statMax != statMin ) + { + retVal.push_back(statMax); + } + } + } + + return retVal; +} #endif std::unordered_map Compendium_t::achievements; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index fae7d3399..98a164a28 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3946,6 +3946,9 @@ struct Compendium_t std::vector atk; std::vector rangeatk; std::vector pwr; + std::vector str; + std::vector con; + std::vector dex; MonsterSpecies species; std::vector lvl; std::array resistances; @@ -3955,6 +3958,7 @@ struct Compendium_t std::vector models; std::set unlockAchievements; int lorePoints = 0; + std::vector getDisplayStat(const char* name); }; static std::map>> contents; static std::map contentsMap; From f50c12e9967170bb18770b2495ce462d8b12f4d4 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:15:04 +1000 Subject: [PATCH 123/244] * tinker bombs - check if entity still alive before spawning a projectile --- src/actbeartrap.cpp | 105 +++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index e577e23d7..f7baa400d 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -361,6 +361,21 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa doVertical = true; } + int oldHP = stat->HP; + if ( stat ) + { + damage *= Entity::getDamageTableMultiplier(triggered, *stat, DAMAGE_TABLE_MAGIC); // reduce/increase by magic table. + } + bool wasAsleep = false; + if ( stat ) + { + wasAsleep = stat->EFFECTS[EFF_ASLEEP]; + } + if ( damage > 0 ) + { + triggered->modHP(-damage); + } + // stumbled into the trap! Uint32 color = makeColorRGB(0, 255, 0); if ( parent && parent->behavior == &actPlayer && triggered != parent ) @@ -495,61 +510,53 @@ void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spa } else if ( doSpell != SPELL_NONE ) { - Entity* spell = castSpell(my->getUID(), getSpellFromID(doSpell), false, true); - spell->parent = my->parent; - spell->x = triggered->x; - spell->y = triggered->y; - if ( !doVertical ) - { - real_t speed = 1.f; - real_t ticksToHit = (entityDistance / speed); - /*real_t predictx = triggered->x + (triggered->vel_x * ticksToHit); - real_t predicty = triggered->y + (triggered->vel_y * ticksToHit); - double tangent = atan2(predicty - my->y, predictx - my->x);*/ - double tangent = atan2(triggered->y - my->y, triggered->x - my->x); - spell->yaw = tangent; - spell->vel_x = speed * cos(spell->yaw); - spell->vel_y = speed * sin(spell->yaw); - } - else - { - spell->x = my->x; - spell->y = my->y; - real_t speed = 3.f; - real_t ticksToHit = (entityDistance / speed); - real_t predictx = triggered->x + (triggered->vel_x * ticksToHit); - real_t predicty = triggered->y + (triggered->vel_y * ticksToHit); - double tangent = atan2(predicty - my->y, predictx - my->x); - spell->yaw = tangent; - spell->vel_z = -2.f; - spell->vel_x = speed * cos(spell->yaw); - spell->vel_y = speed * sin(spell->yaw); - spell->pitch = atan2(spell->vel_z, speed); - spell->actmagicIsVertical = MAGIC_ISVERTICAL_XYZ; - } - spell->actmagicCastByTinkerTrap = 1; - if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL ) + if ( !stat || (stat && stat->HP > 0) ) { - spell->actmagicTinkerTrapFriendlyFire = 1; - if ( triggered == parent ) + Entity* spell = castSpell(my->getUID(), getSpellFromID(doSpell), false, true); + spell->parent = my->parent; + spell->x = triggered->x; + spell->y = triggered->y; + if ( !doVertical ) { - spell->parent = 0; + real_t speed = 1.f; + real_t ticksToHit = (entityDistance / speed); + /*real_t predictx = triggered->x + (triggered->vel_x * ticksToHit); + real_t predicty = triggered->y + (triggered->vel_y * ticksToHit); + double tangent = atan2(predicty - my->y, predictx - my->x);*/ + double tangent = atan2(triggered->y - my->y, triggered->x - my->x); + spell->yaw = tangent; + spell->vel_x = speed * cos(spell->yaw); + spell->vel_y = speed * sin(spell->yaw); } + else + { + spell->x = my->x; + spell->y = my->y; + real_t speed = 3.f; + real_t ticksToHit = (entityDistance / speed); + real_t predictx = triggered->x + (triggered->vel_x * ticksToHit); + real_t predicty = triggered->y + (triggered->vel_y * ticksToHit); + double tangent = atan2(predicty - my->y, predictx - my->x); + spell->yaw = tangent; + spell->vel_z = -2.f; + spell->vel_x = speed * cos(spell->yaw); + spell->vel_y = speed * sin(spell->yaw); + spell->pitch = atan2(spell->vel_z, speed); + spell->actmagicIsVertical = MAGIC_ISVERTICAL_XYZ; + } + spell->actmagicCastByTinkerTrap = 1; + if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL ) + { + spell->actmagicTinkerTrapFriendlyFire = 1; + if ( triggered == parent ) + { + spell->parent = 0; + } + } + spell->skill[5] = 10; // travel time } - spell->skill[5] = 10; // travel time } // set obituary - int oldHP = stat->HP; - if ( stat ) - { - damage *= Entity::getDamageTableMultiplier(triggered, *stat, DAMAGE_TABLE_MAGIC); // reduce/increase by magic table. - } - bool wasAsleep = false; - if ( stat ) - { - wasAsleep = stat->EFFECTS[EFF_ASLEEP]; - } - triggered->modHP(-damage); triggered->setObituary(Language::get(3496)); triggered->updateEntityOnHit(parent, true); stat->killer = KilledBy::TRAP_BOMB; From 72fe9bd958086b9ae7c574b5ecfaefc9cc89ca6a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 10 Sep 2024 02:15:58 +1000 Subject: [PATCH 124/244] * breakables cycle monster inside if possible, now can spawn more as dungeon depth goes on --- src/maps.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index cf549652e..000199530 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4456,6 +4456,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int breakableGoodies = breakableLocations.size() * 80 / 100; int breakableMonsters = 0; + const int breakableMonsterLimit = 2 + (currentlevel / LENGTH_OF_LEVEL_REGION) * (1 + map_rng.rand() % 2); if ( findBreakables != EditorEntityData_t::colliderRandomGenPool.end() && findBreakables->second.size() > 0 && breakableGoodies > 0 ) { int breakableItemsFromGround = 0; @@ -4466,6 +4467,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple ids.push_back(pair.first); chances.push_back(pair.second); } + Monster lastMonsterEvent = NOTHING; while ( !breakableLocations.empty() ) { auto& top = breakableLocations.top(); @@ -4487,7 +4489,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple breakable->colliderDamageTypes = ids[picked]; } - Monster monsterEvent = NOTHING; + bool monsterEventExists = false; auto findData = EditorEntityData_t::colliderData.find(breakable->colliderDamageTypes); if ( findData != EditorEntityData_t::colliderData.end() ) { @@ -4496,14 +4498,16 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { if ( findMap->second.size() > 0 ) { - int picked = findMap->second[map_rng.rand() % findMap->second.size()]; - if ( picked > NOTHING && picked < NUMMONSTERS ) + for ( auto m : findMap->second ) { - monsterEvent = (Monster)picked; + if ( m > NOTHING && m < NUMMONSTERS ) + { + monsterEventExists = true; } } } } + } if ( breakableGoodies > 0 ) { @@ -4515,9 +4519,45 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { // nothing over pits 50% } - else if ( (breakableMonsters < 2 && monsterEvent != NOTHING && map_rng.rand() % 10 == 0) + else if ( (breakableMonsters < breakableMonsterLimit && monsterEventExists && map_rng.rand() % 10 == 0) && map.monsterexcludelocations[x + y * map.width] == false ) // 10% monster inside { + Monster monsterEvent = NOTHING; + auto findMap = findData->second.hideMonsters.find(map.name); + if ( findMap != findData->second.hideMonsters.end() ) + { + if ( findMap->second.size() > 0 ) + { + std::vector chances; + bool avoidLastMonster = false; + for ( auto m : findMap->second ) + { + chances.push_back(1); + if ( lastMonsterEvent != NOTHING && m != lastMonsterEvent ) + { + avoidLastMonster = true; + } + } + if ( avoidLastMonster ) + { + for ( size_t i = 0; i < chances.size(); ++i ) + { + if ( findMap->second[i] == lastMonsterEvent ) + { + chances[i] = 0; + } + } + } + int pickIndex = map_rng.discrete(chances.data(), chances.size()); + int picked = findMap->second[pickIndex]; + if ( picked > NOTHING && picked < NUMMONSTERS ) + { + monsterEvent = (Monster)picked; + lastMonsterEvent = monsterEvent; + } + } + } + if ( (svFlags & SV_FLAG_TRAPS) ) { if ( map_rng.rand() % 2 == 0 ) From 2bb437defe2a5be6d4da24edad8acf04511029b8 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:40:16 +1000 Subject: [PATCH 125/244] * water/lava/trap/fountain sfx now loop, cull if <=2 tiles --- src/actfountain.cpp | 34 ++++++++++++++++++++++++++++++++- src/engine/audio/init_audio.cpp | 2 +- src/engine/audio/sound_game.cpp | 4 ++-- src/files.cpp | 3 ++- src/main.hpp | 1 + 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/actfountain.cpp b/src/actfountain.cpp index fff195b74..8111297a5 100644 --- a/src/actfountain.cpp +++ b/src/actfountain.cpp @@ -119,12 +119,29 @@ void actFountain(Entity* my) if ( my->skill[0] > 0 ) { #define FOUNTAIN_AMBIENCE my->skill[7] +#ifdef USE_FMOD + if ( FOUNTAIN_AMBIENCE == 0 ) + { + FOUNTAIN_AMBIENCE--; + my->entity_sound = playSoundEntityLocal(my, 672, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else FOUNTAIN_AMBIENCE--; if ( FOUNTAIN_AMBIENCE <= 0 ) { FOUNTAIN_AMBIENCE = TICKS_PER_SECOND * 6; - playSoundEntityLocal(my, 135, 32 ); + playSoundEntityLocal(my, 672, 32 ); } +#endif entity = spawnGib(my); entity->flags[INVISIBLE] = false; entity->y -= 2; @@ -142,6 +159,21 @@ void actFountain(Entity* my) entity->vel_z = .25; entity->fskill[3] = 0.03; } + else + { +#ifdef USE_FMOD + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( playing ) + { + my->entity_sound->stop(); + my->entity_sound = nullptr; + } + } +#endif + } if ( my->ticks == 1 ) { diff --git a/src/engine/audio/init_audio.cpp b/src/engine/audio/init_audio.cpp index 5177c6b2d..24911d9a6 100644 --- a/src/engine/audio/init_audio.cpp +++ b/src/engine/audio/init_audio.cpp @@ -206,7 +206,7 @@ int loadSoundResources(real_t base_load_percent, real_t top_load_percent) fp->gets2(name, 128); completePath(full_path, name); FMOD_MODE flags = FMOD_DEFAULT | FMOD_3D | FMOD_LOWMEM; - if ( c == 133 ) + if ( c == 133 || c == 672 || c == 135 || c == 155 ) { flags |= FMOD_LOOP_NORMAL; } diff --git a/src/engine/audio/sound_game.cpp b/src/engine/audio/sound_game.cpp index 05f6c2198..fafc18cce 100644 --- a/src/engine/audio/sound_game.cpp +++ b/src/engine/audio/sound_game.cpp @@ -235,7 +235,7 @@ FMOD::Channel* playSoundPosLocal(real_t x, real_t y, Uint16 snd, Uint8 vol) //printlog("Channel index: %d, audibility: %f, vol: %f, pos x: %.2f | y: %.2f", i, audibility, volume, playingPosition.z, playingPosition.x); if ( abs(volume - (vol / 255.f)) < 0.05 ) { - if ( (pow(playingPosition.x - position.x, 2) + pow(playingPosition.z - position.z, 2)) <= 2.25 ) + if ( (pow(playingPosition.x - position.x, 2) + pow(playingPosition.z - position.z, 2)) <= 4.5 ) { //printlog("Culling sound due to proximity, pos x: %.2f | y: %.2f", position.z, position.x); return nullptr; @@ -389,7 +389,7 @@ OPENAL_CHANNELGROUP* getChannelGroupForSoundIndex(Uint32 snd) { return soundEnvironment_group; } - if ( snd == 149 ) + if ( snd == 149 || snd == 133 ) { return soundAmbient_group; } diff --git a/src/files.cpp b/src/files.cpp index 1e49dfd50..949f22dd7 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -1953,6 +1953,7 @@ int loadMap(const char* filename2, map_t* destmap, list_t* entlist, list_t* crea { list_FreeAll(map.worldUI); } + destmap->liquidSfxPlayedTiles.clear(); } if ( destmap->tiles != nullptr ) { @@ -4775,7 +4776,7 @@ void physfsReloadSounds(bool reloadAll) sounds[c] = nullptr; } FMOD_MODE flags = FMOD_DEFAULT | FMOD_3D | FMOD_LOWMEM; - if ( c == 133 ) + if ( c == 133 || c == 672 || c == 135 || c == 155 || c == 149 ) { flags |= FMOD_LOOP_NORMAL; } diff --git a/src/main.hpp b/src/main.hpp index 2004f8ff6..c5ccbc857 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -462,6 +462,7 @@ typedef struct map_t bool* trapexcludelocations = nullptr; bool* monsterexcludelocations = nullptr; bool* lootexcludelocations = nullptr; + std::set liquidSfxPlayedTiles; char filename[256]; ~map_t() { From 7414d84edd2d3a5a2df8ff1e4b50d4ab31cb194c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:43:27 +1000 Subject: [PATCH 126/244] * ambient trap sounds make loop --- src/actarrowtrap.cpp | 19 +++++- src/actboulder.cpp | 115 ++++++++++++++++++++++++++------ src/actcampfire.cpp | 14 +--- src/actchest.cpp | 18 +++++ src/actfountain.cpp | 14 +--- src/actgold.cpp | 18 +++++ src/actheadstone.cpp | 18 +++++ src/actladder.cpp | 49 ++++++++++++-- src/actmagictrap.cpp | 47 +++++++++++-- src/actpedestal.cpp | 18 +++++ src/actsink.cpp | 18 +++++ src/actspeartrap.cpp | 18 +++++ src/actteleporter.cpp | 18 +++++ src/engine/audio/init_audio.cpp | 2 +- src/entity.hpp | 16 +++++ 15 files changed, 347 insertions(+), 55 deletions(-) diff --git a/src/actarrowtrap.cpp b/src/actarrowtrap.cpp index 1aadae3a7..32bc49040 100644 --- a/src/actarrowtrap.cpp +++ b/src/actarrowtrap.cpp @@ -114,12 +114,29 @@ void actArrowTrap(Entity* my) return; } +#ifdef USE_FMOD + if ( ARROWTRAP_AMBIENCE == 0 ) + { + ARROWTRAP_AMBIENCE--; + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else ARROWTRAP_AMBIENCE--; if ( ARROWTRAP_AMBIENCE <= 0 ) { ARROWTRAP_AMBIENCE = TICKS_PER_SECOND * 30; - playSoundEntity( my, 149, 64 ); + playSoundEntityLocal( my, 149, 64 ); } +#endif if ( !my->skill[28] ) { diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 36fa193c1..e09ae5af1 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -1477,15 +1477,30 @@ void actBoulderTrap(Entity* my) int x, y; int c; - if ( !BOULDERTRAP_FIRED ) - { +#ifdef USE_FMOD + if ( BOULDERTRAP_AMBIENCE == 0 ) + { + BOULDERTRAP_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else BOULDERTRAP_AMBIENCE--; if ( BOULDERTRAP_AMBIENCE <= 0 ) { BOULDERTRAP_AMBIENCE = TICKS_PER_SECOND * 30; - playSoundEntity(my, 149, 64); + playSoundEntityLocal(my, 149, 64); } - } +#endif if ( !my->skill[28] ) { @@ -1594,15 +1609,30 @@ void actBoulderTrapEast(Entity* my) int x, y; int c; - if ( !my->boulderTrapFired ) - { +#ifdef USE_FMOD + if ( my->boulderTrapAmbience == 0 ) + { + my->boulderTrapAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else my->boulderTrapAmbience--; if ( my->boulderTrapAmbience <= 0 ) { my->boulderTrapAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(my, 149, 64); + playSoundEntityLocal(my, 149, 64); } - } +#endif if ( my->boulderTrapRefireCounter > 0 ) { @@ -1689,15 +1719,30 @@ void actBoulderTrapSouth(Entity* my) int x, y; int c; - if ( !my->boulderTrapFired ) - { +#ifdef USE_FMOD + if ( my->boulderTrapAmbience == 0 ) + { + my->boulderTrapAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else my->boulderTrapAmbience--; if ( my->boulderTrapAmbience <= 0 ) { my->boulderTrapAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(my, 149, 64); + playSoundEntityLocal(my, 149, 64); } - } +#endif if ( my->boulderTrapRefireCounter > 0 ) { @@ -1784,15 +1829,30 @@ void actBoulderTrapWest(Entity* my) int x, y; int c; - if ( !my->boulderTrapFired ) - { +#ifdef USE_FMOD + if ( my->boulderTrapAmbience == 0 ) + { + my->boulderTrapAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else my->boulderTrapAmbience--; if ( my->boulderTrapAmbience <= 0 ) { my->boulderTrapAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(my, 149, 64); + playSoundEntityLocal(my, 149, 64); } - } +#endif if ( my->boulderTrapRefireCounter > 0 ) { @@ -1879,15 +1939,30 @@ void actBoulderTrapNorth(Entity* my) int x, y; int c; - if ( !my->boulderTrapFired ) - { +#ifdef USE_FMOD + if ( my->boulderTrapAmbience == 0 ) + { + my->boulderTrapAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else my->boulderTrapAmbience--; if ( my->boulderTrapAmbience <= 0 ) { my->boulderTrapAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(my, 149, 64); + playSoundEntityLocal(my, 149, 64); } - } +#endif if ( my->boulderTrapRefireCounter > 0 ) { diff --git a/src/actcampfire.cpp b/src/actcampfire.cpp index cf7f62f9b..cd335619c 100644 --- a/src/actcampfire.cpp +++ b/src/actcampfire.cpp @@ -52,6 +52,7 @@ void actCampfire(Entity* my) if ( CAMPFIRE_SOUNDTIME == 0 ) { CAMPFIRE_SOUNDTIME--; + my->stopEntitySound(); my->entity_sound = playSoundEntityLocal(my, 133, 32); } if ( my->entity_sound ) @@ -133,18 +134,7 @@ void actCampfire(Entity* my) my->removeLightField(); my->light = NULL; -#ifdef USE_FMOD - if ( my->entity_sound ) - { - bool playing = false; - my->entity_sound->isPlaying(&playing); - if ( playing ) - { - my->entity_sound->stop(); - my->entity_sound = nullptr; - } - } -#endif + my->stopEntitySound(); } if ( multiplayer != CLIENT ) diff --git a/src/actchest.cpp b/src/actchest.cpp index b90e2e54f..8e64acfe5 100644 --- a/src/actchest.cpp +++ b/src/actchest.cpp @@ -622,12 +622,30 @@ void createChestInventory(Entity* my, int chestType) void Entity::actChest() { +#ifdef USE_FMOD + if ( chestAmbience == 0 ) + { + chestAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 32); + } + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else chestAmbience--; if ( chestAmbience <= 0 ) { chestAmbience = TICKS_PER_SECOND * 30; playSoundEntityLocal(this, 149, 32); } +#endif if ( ticks == 1 ) { diff --git a/src/actfountain.cpp b/src/actfountain.cpp index 8111297a5..789043532 100644 --- a/src/actfountain.cpp +++ b/src/actfountain.cpp @@ -123,6 +123,7 @@ void actFountain(Entity* my) if ( FOUNTAIN_AMBIENCE == 0 ) { FOUNTAIN_AMBIENCE--; + my->stopEntitySound(); my->entity_sound = playSoundEntityLocal(my, 672, 32); } if ( my->entity_sound ) @@ -161,18 +162,7 @@ void actFountain(Entity* my) } else { -#ifdef USE_FMOD - if ( my->entity_sound ) - { - bool playing = false; - my->entity_sound->isPlaying(&playing); - if ( playing ) - { - my->entity_sound->stop(); - my->entity_sound = nullptr; - } - } -#endif + my->stopEntitySound(); } if ( my->ticks == 1 ) diff --git a/src/actgold.cpp b/src/actgold.cpp index a35d176f8..a8d504884 100644 --- a/src/actgold.cpp +++ b/src/actgold.cpp @@ -68,12 +68,30 @@ void actGoldBag(Entity* my) } } +#ifdef USE_FMOD + if ( my->goldAmbience == 0 ) + { + my->goldAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 16); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else my->goldAmbience--; if ( my->goldAmbience <= 0 ) { my->goldAmbience = TICKS_PER_SECOND * 30; playSoundEntityLocal( my, 149, 16 ); } +#endif // pick up gold if ( multiplayer != CLIENT ) diff --git a/src/actheadstone.cpp b/src/actheadstone.cpp index 0e50fadea..8b2d1063b 100644 --- a/src/actheadstone.cpp +++ b/src/actheadstone.cpp @@ -72,12 +72,30 @@ void actHeadstone(Entity* my) } } +#ifdef USE_FMOD + if ( HEADSTONE_AMBIENCE == 0 ) + { + HEADSTONE_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else HEADSTONE_AMBIENCE--; if ( HEADSTONE_AMBIENCE <= 0 ) { HEADSTONE_AMBIENCE = TICKS_PER_SECOND * 30; playSoundEntityLocal( my, 149, 32 ); } +#endif if ( multiplayer == CLIENT ) { diff --git a/src/actladder.cpp b/src/actladder.cpp index 138938264..a625a1d4a 100644 --- a/src/actladder.cpp +++ b/src/actladder.cpp @@ -52,12 +52,30 @@ void actLadder(Entity* my) memset(mpPokeCooldown, 0, sizeof(mpPokeCooldown)); } +#ifdef USE_FMOD + if ( LADDER_AMBIENCE == 0 ) + { + LADDER_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else LADDER_AMBIENCE--; if (LADDER_AMBIENCE <= 0) { LADDER_AMBIENCE = TICKS_PER_SECOND * 30; playSoundEntityLocal(my, 149, 64); } +#endif // use ladder (climb) if (multiplayer != CLIENT) @@ -1318,21 +1336,44 @@ void actCustomPortal(Entity* my) } } - my->portalAmbience--; - if ( my->portalAmbience <= 0 ) + if ( my->portalCustomSpriteAnimationFrames > 0 ) { - if ( my->portalCustomSpriteAnimationFrames > 0 ) + my->portalAmbience--; + if ( my->portalAmbience <= 0 ) { my->portalAmbience = TICKS_PER_SECOND * 2; // portal whirr playSoundEntityLocal(my, 154, 128); } - else + } + else + { +#ifdef USE_FMOD + if ( my->portalAmbience == 0 ) + { + my->portalAmbience--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else + my->portalAmbience--; + if ( my->portalAmbience <= 0 ) { my->portalAmbience = TICKS_PER_SECOND * 30; // trap hum playSoundEntityLocal(my, 149, 64); } +#endif } + if ( my->portalCustomSpriteAnimationFrames > 0 ) { my->yaw += 0.01; // rotate slowly on my axis diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp index 986854e52..69cdfd15c 100644 --- a/src/actmagictrap.cpp +++ b/src/actmagictrap.cpp @@ -44,12 +44,30 @@ void actMagicTrapCeiling(Entity* my) void Entity::actMagicTrapCeiling() { +#ifdef USE_FMOD + if ( spellTrapAmbience == 0 ) + { + spellTrapAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 16); + } + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else spellTrapAmbience--; if ( spellTrapAmbience <= 0 ) { spellTrapAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(this, 149, 16); + playSoundEntityLocal(this, 149, 16); } +#endif if ( multiplayer == CLIENT ) { @@ -298,17 +316,36 @@ void Entity::actTeleportShrine() spawnAmbientParticles(80, 576, 10 + local_rng.rand() % 40, 1.0, false); } - if ( multiplayer == CLIENT ) +#ifdef USE_FMOD + if ( shrineAmbience == 0 ) { - return; + shrineAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 16); } - + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else shrineAmbience--; if ( shrineAmbience <= 0 ) { shrineAmbience = TICKS_PER_SECOND * 30; - playSoundEntity(this, 149, 16); + playSoundEntityLocal(this, 149, 16); } +#endif + + if ( multiplayer == CLIENT ) + { + return; + } + if ( !shrineInit ) { diff --git a/src/actpedestal.cpp b/src/actpedestal.cpp index 830c8a7f1..2fde2db46 100644 --- a/src/actpedestal.cpp +++ b/src/actpedestal.cpp @@ -64,12 +64,30 @@ void Entity::actPedestalBase() pedestalInit = 1; } +#ifdef USE_FMOD + if ( pedestalAmbience == 0 ) + { + pedestalAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 64); + } + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else pedestalAmbience--; if ( pedestalAmbience <= 0 ) { pedestalAmbience = TICKS_PER_SECOND * 30; playSoundEntityLocal(this, 149, 64); } +#endif if ( !light ) { diff --git a/src/actsink.cpp b/src/actsink.cpp index 01d313365..e14ac5885 100644 --- a/src/actsink.cpp +++ b/src/actsink.cpp @@ -37,12 +37,30 @@ void actSink(Entity* my) { +#ifdef USE_FMOD + if ( SINK_AMBIENCE == 0 ) + { + SINK_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 32); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else SINK_AMBIENCE--; if ( SINK_AMBIENCE <= 0 ) { SINK_AMBIENCE = TICKS_PER_SECOND * 30; playSoundEntityLocal( my, 149, 32 ); } +#endif if ( my->ticks == 1 ) { diff --git a/src/actspeartrap.cpp b/src/actspeartrap.cpp index 82f1b6782..d5b359ec3 100644 --- a/src/actspeartrap.cpp +++ b/src/actspeartrap.cpp @@ -37,12 +37,30 @@ void actSpearTrap(Entity* my) { +#ifdef USE_FMOD + if ( SPEARTRAP_AMBIENCE == 0 ) + { + SPEARTRAP_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 64); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else SPEARTRAP_AMBIENCE--; if ( SPEARTRAP_AMBIENCE <= 0 ) { SPEARTRAP_AMBIENCE = TICKS_PER_SECOND * 30; playSoundEntityLocal( my, 149, 64 ); } +#endif if ( multiplayer != CLIENT ) { diff --git a/src/actteleporter.cpp b/src/actteleporter.cpp index 11932ee57..1f0df753e 100644 --- a/src/actteleporter.cpp +++ b/src/actteleporter.cpp @@ -47,12 +47,30 @@ void Entity::actTeleporter() createWorldUITooltip(); } +#ifdef USE_FMOD + if ( teleporterAmbience == 0 ) + { + teleporterAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 64); + } + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else teleporterAmbience--; if ( teleporterAmbience <= 0 ) { teleporterAmbience = TICKS_PER_SECOND * 30; playSoundEntityLocal(this, 149, 64); } +#endif // use teleporter if ( multiplayer != CLIENT ) diff --git a/src/engine/audio/init_audio.cpp b/src/engine/audio/init_audio.cpp index 24911d9a6..b0ad004fa 100644 --- a/src/engine/audio/init_audio.cpp +++ b/src/engine/audio/init_audio.cpp @@ -206,7 +206,7 @@ int loadSoundResources(real_t base_load_percent, real_t top_load_percent) fp->gets2(name, 128); completePath(full_path, name); FMOD_MODE flags = FMOD_DEFAULT | FMOD_3D | FMOD_LOWMEM; - if ( c == 133 || c == 672 || c == 135 || c == 155 ) + if ( c == 133 || c == 672 || c == 135 || c == 155 || c == 149 ) { flags |= FMOD_LOOP_NORMAL; } diff --git a/src/entity.hpp b/src/entity.hpp index ad5260fcb..a15fbf0d3 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -105,6 +105,22 @@ class Entity void* entity_sound = nullptr; #endif + void stopEntitySound() + { +#ifdef USE_FMOD + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( playing ) + { + entity_sound->stop(); + entity_sound = nullptr; + } + } +#endif + } + Uint32 getUID() const {return uid;} void setUID(Uint32 new_uid); Uint32 ticks; // duration of the entity's existence From 23a2b7dbe6fd3618d632dcdd8626c190f90df29e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:43:51 +1000 Subject: [PATCH 127/244] * water/lava now spawn only 1 sfx while map is active --- src/game.cpp | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 2b9150d64..cdcef96df 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1383,15 +1383,20 @@ void gameLogic(void) // water and lava noises if ( ticks % (TICKS_PER_SECOND * 4) == (y + x * map.height) % (TICKS_PER_SECOND * 4) && local_rng.rand() % 3 == 0 ) { - if ( lavatiles[map.tiles[index]] ) + int coord = x + y * 1000; + if ( map.liquidSfxPlayedTiles.find(coord) == map.liquidSfxPlayedTiles.end() ) { - // bubbling lava - playSoundPosLocal( x * 16 + 8, y * 16 + 8, 155, 100 ); - } - else if ( swimmingtiles[map.tiles[index]] ) - { - // running water - playSoundPosLocal( x * 16 + 8, y * 16 + 8, 135, 32 ); + if ( lavatiles[map.tiles[index]] ) + { + // bubbling lava + playSoundPosLocal( x * 16 + 8, y * 16 + 8, 155, 100 ); + } + else if ( swimmingtiles[map.tiles[index]] ) + { + // running water + playSoundPosLocal( x * 16 + 8, y * 16 + 8, 135, 32 ); + } + map.liquidSfxPlayedTiles.insert(coord); } } @@ -2970,17 +2975,22 @@ void gameLogic(void) if ( z == 0 ) { // water and lava noises - if ( ticks % TICKS_PER_SECOND == (y + x * map.height) % TICKS_PER_SECOND && local_rng.rand() % 3 == 0 ) + if ( ticks % (TICKS_PER_SECOND * 4) == (y + x * map.height) % (TICKS_PER_SECOND * 4) && local_rng.rand() % 3 == 0 ) { - if ( lavatiles[map.tiles[index]] ) + int coord = x + y * 1000; + if ( map.liquidSfxPlayedTiles.find(coord) == map.liquidSfxPlayedTiles.end() ) { - // bubbling lava - playSoundPosLocal( x * 16 + 8, y * 16 + 8, 155, 100 ); - } - else if ( swimmingtiles[map.tiles[index]] ) - { - // running water - playSoundPosLocal( x * 16 + 8, y * 16 + 8, 135, 32 ); + if ( lavatiles[map.tiles[index]] ) + { + // bubbling lava + playSoundPosLocal(x * 16 + 8, y * 16 + 8, 155, 100); + } + else if ( swimmingtiles[map.tiles[index]] ) + { + // running water + playSoundPosLocal(x * 16 + 8, y * 16 + 8, 135, 32); + } + map.liquidSfxPlayedTiles.insert(coord); } } From 0792fc6c1643c74c159bbd1e8de9c47573294d7c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:44:02 +1000 Subject: [PATCH 128/244] * bat reduce chance bleed when blocking --- src/entity.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index e80af819a..7f41c4306 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -9825,10 +9825,13 @@ void Entity::attack(int pose, int charge, Entity* target) { if ( !hitstats->EFFECTS[EFF_BLEEDING] ) { - if ( hit.entity->setEffect(EFF_BLEEDING, true, 6 * TICKS_PER_SECOND, false) ) + if ( !hitstats->defending || (hitstats->defending && local_rng.rand() % 4 == 0) ) { - statusInflicted = true; - messagePlayer(playerhit, MESSAGE_COMBAT, Language::get(701)); + if ( hit.entity->setEffect(EFF_BLEEDING, true, 6 * TICKS_PER_SECOND, false) ) + { + statusInflicted = true; + messagePlayer(playerhit, MESSAGE_COMBAT, Language::get(701)); + } } } } From f6f56a51ee08d7c885f00706228b5a734003cb26 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:45:25 +1000 Subject: [PATCH 129/244] * bat no rest on tiles with ceiling models or colliders/furniture for visuals --- src/actmonster.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 0060076b4..821b1b7b8 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -12879,11 +12879,41 @@ void batResetIdle(Entity* my) if ( !my ) { return; } // reset to inert after wandering with no target + bool canRest = true; + int x = my->x / 16; + int y = my->y / 16; + std::vector entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && canRest; ++it ) + { + list_t* currentList = *it; + for ( node_t* node = currentList->first; node != nullptr; node = node->next ) //Can't convert to map.creatures because of doorframes. + { + Entity* entity = (Entity*)node->element; + if ( entity == my ) + { + continue; + } + if ( entity->behavior == &actCeilingTile || entity->behavior == &actColliderDecoration || entity->behavior == &actFurniture ) + { + int x2 = entity->x / 16; + int y2 = entity->y / 16; + if ( x == x2 && y == y2 ) + { + canRest = false; + break; + } + } + } + } + + if ( canRest ) + { my->monsterSpecialState = BAT_REST; serverUpdateEntitySkill(my, 33); my->monsterLookDir = (PI / 2) * (local_rng.rand() % 4); } +} void mimicResetIdle(Entity* my) { From fdfbde87f34747a0a3a7ca582bf25cefaa737770 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:52:10 +1000 Subject: [PATCH 130/244] * reduce frequency monster sounds idling --- src/actmonster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 821b1b7b8..548e12f78 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -5098,7 +5098,7 @@ void actMonster(Entity* my) { if ( myStats->type != MINOTAUR ) { - if ( !my->monsterAllyGetPlayerLeader() || (my->monsterAllyGetPlayerLeader() && local_rng.rand() % 8 == 0) || myStats->type == DUMMYBOT ) + if ( (local_rng.rand() % 3 == 0 && !my->monsterAllyGetPlayerLeader()) || (my->monsterAllyGetPlayerLeader() && local_rng.rand() % 8 == 0) || myStats->type == DUMMYBOT ) { // idle sounds. if player follower, reduce noise frequency by 66%. MONSTER_SOUND = playSoundEntity(my, MONSTER_IDLESND + (local_rng.rand() % MONSTER_IDLEVAR), 128); From 1c87833b7cea8f703feed489529d6ce4799945f4 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:52:18 +1000 Subject: [PATCH 131/244] * can sneak under bat --- src/actmonster.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 548e12f78..314751611 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -8646,7 +8646,7 @@ void actMonster(Entity* my) int sizey = my->sizey; my->sizex = std::max(my->sizex, 4); // override size temporarily my->sizey = std::max(my->sizey, 4); - if ( entityInsideEntity(my, entity) ) + if ( entityInsideEntity(my, entity) && !hitstats->sneaking ) { visiontest = true; } @@ -12908,11 +12908,11 @@ void batResetIdle(Entity* my) if ( canRest ) { - my->monsterSpecialState = BAT_REST; - serverUpdateEntitySkill(my, 33); + my->monsterSpecialState = BAT_REST; + serverUpdateEntitySkill(my, 33); - my->monsterLookDir = (PI / 2) * (local_rng.rand() % 4); -} + my->monsterLookDir = (PI / 2) * (local_rng.rand() % 4); + } } void mimicResetIdle(Entity* my) From 528d4b1319ea1120aee785f71255064422167ecd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:52:34 +1000 Subject: [PATCH 132/244] * new lang --- lang/en.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lang/en.txt b/lang/en.txt index 4f0bb6d9d..18695ddd5 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -131,7 +131,7 @@ 93 goblin# 94 slime# 95 troll# -96 octopus# +96 bat# 97 spider# 98 ghoul# 99 skeleton# @@ -156,7 +156,7 @@ 114 goblins# 115 slimes# 116 trolls# -117 octopuses# +117 bats# 118 spiders# 119 ghouls# 120 skeletons# @@ -181,7 +181,7 @@ 135 hits# 136 slimes you# 137 thumps you# -138 wrestles you# +138 bites# 139 bites# 140 hits# 141 hits# @@ -6475,5 +6475,9 @@ Magic Required: %d (%s)# 6246 Your shield slips through your fingers!# 6247 SORT BY LOCKED FIRST# 6248 A %s jumps out!# +6249 Miss!# +6250 STATS (HARDCORE)# +6251 Open Compendium# +6252 A nearby bat awakens!# -6250 END# +6300 END# From 3c4b6ca4a7eb9644db73ad51cf76d7312bdbe281 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:54:28 +1000 Subject: [PATCH 133/244] * slimebushes tweak min distance, fix not deleting old tiles --- src/maps.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/maps.cpp b/src/maps.cpp index 000199530..d73448b32 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4065,7 +4065,9 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { numSlimebushes += 4 + map_rng.rand() % 3; } - if ( waterEmptyTiles.size() > 0 || lavaEmptyTiles.size() > 0 ) + + std::set visited; + while ( numSlimebushes > 0 && (waterEmptyTiles.size() > 0 || lavaEmptyTiles.size() > 0) ) { while ( numSlimebushes > 0 ) { @@ -4078,12 +4080,37 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { x = (waterEmptyTiles[pick]) % 1000; y = (waterEmptyTiles[pick]) / 1000; + waterEmptyTiles.erase(waterEmptyTiles.begin() + pick); } else if ( pick >= waterEmptyTiles.size() && ((pick - waterEmptyTiles.size()) < lavaEmptyTiles.size()) ) { x = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) % 1000; y = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) / 1000; + lavaEmptyTiles.erase(lavaEmptyTiles.begin() + (pick - waterEmptyTiles.size())); + } + + bool skip = false; + for ( auto coord : visited ) + { + int tx = coord % 1000; + int ty = coord / 1000; + + real_t dx, dy; + dx = tx - x; + dy = ty - y; + if ( sqrt(dx * dx + dy * dy) < 4.0 ) // too close to other regions, within X tiles + { + skip = true; + break; } + } + + visited.insert(x + y * 1000); + + if ( skip ) + { + continue; + } if ( x != 0 && y != 0 ) { @@ -4093,6 +4120,13 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple setSpriteAttributes(summonTrap, nullptr, nullptr); summonTrap->skill[9] = 3; // x tile auto activate summonTrap->skill[0] = SLIME; + + //Entity* ent = newEntity(245, 0, map.entities, nullptr); + ////ent->behavior = &actBoulder; + //ent->x = x * 16.0 + 8; + //ent->y = y * 16.0 + 8; + //ent->z = 24.0; + //ent->flags[PASSABLE] = true; } --numSlimebushes; From 3fce8ee930254f6cb7884958acf9744812ec653d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:55:11 +1000 Subject: [PATCH 134/244] * bats spawn in darkmaps --- src/maps.cpp | 235 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 202 insertions(+), 33 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index d73448b32..38a3dbc20 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4069,25 +4069,23 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple std::set visited; while ( numSlimebushes > 0 && (waterEmptyTiles.size() > 0 || lavaEmptyTiles.size() > 0) ) { - while ( numSlimebushes > 0 ) - { - size_t totalSize = waterEmptyTiles.size() + lavaEmptyTiles.size(); - int pick = map_rng.rand() % totalSize; + size_t totalSize = waterEmptyTiles.size() + lavaEmptyTiles.size(); + int pick = map_rng.rand() % totalSize; - int x = 0; - int y = 0; - if ( pick < waterEmptyTiles.size() ) - { - x = (waterEmptyTiles[pick]) % 1000; - y = (waterEmptyTiles[pick]) / 1000; + int x = 0; + int y = 0; + if ( pick < waterEmptyTiles.size() ) + { + x = (waterEmptyTiles[pick]) % 1000; + y = (waterEmptyTiles[pick]) / 1000; waterEmptyTiles.erase(waterEmptyTiles.begin() + pick); - } - else if ( pick >= waterEmptyTiles.size() && ((pick - waterEmptyTiles.size()) < lavaEmptyTiles.size()) ) - { - x = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) % 1000; - y = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) / 1000; + } + else if ( pick >= waterEmptyTiles.size() && ((pick - waterEmptyTiles.size()) < lavaEmptyTiles.size()) ) + { + x = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) % 1000; + y = (lavaEmptyTiles[pick - waterEmptyTiles.size()]) / 1000; lavaEmptyTiles.erase(lavaEmptyTiles.begin() + (pick - waterEmptyTiles.size())); - } + } bool skip = false; for ( auto coord : visited ) @@ -4112,14 +4110,14 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple continue; } - if ( x != 0 && y != 0 ) - { - Entity* summonTrap = newEntity(97, 1, map.entities, nullptr); - summonTrap->x = x * 16.0; - summonTrap->y = y * 16.0; - setSpriteAttributes(summonTrap, nullptr, nullptr); - summonTrap->skill[9] = 3; // x tile auto activate - summonTrap->skill[0] = SLIME; + if ( x != 0 && y != 0 ) + { + Entity* summonTrap = newEntity(97, 1, map.entities, nullptr); + summonTrap->x = x * 16.0; + summonTrap->y = y * 16.0; + setSpriteAttributes(summonTrap, nullptr, nullptr); + summonTrap->skill[9] = 3; // x tile auto activate + summonTrap->skill[0] = SLIME; //Entity* ent = newEntity(245, 0, map.entities, nullptr); ////ent->behavior = &actBoulder; @@ -4127,15 +4125,9 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple //ent->y = y * 16.0 + 8; //ent->z = 24.0; //ent->flags[PASSABLE] = true; - } - - --numSlimebushes; - --totalSize; - if ( totalSize < 10 ) - { - break; - } } + + --numSlimebushes; } } @@ -4537,11 +4529,11 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( m > NOTHING && m < NUMMONSTERS ) { monsterEventExists = true; + } } } } } - } if ( breakableGoodies > 0 ) { @@ -4678,6 +4670,183 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } } + if ( darkmap && map.skybox == 0 ) + { + std::vector>> batAreasGood; + std::vector>> batAreasOk; + for ( int x = 1; x < map.width - 1; ++x ) + { + for ( int y = 1; y < map.height - 1; ++y ) + { + if ( possiblelocations[y + x * map.height] ) + { + std::vector testAreas = { + (x - 1) + 1000 * (y + 0), + (x + 1) + 1000 * (y + 0), + (x + 0) + 1000 * (y + 1), + (x + 0) + 1000 * (y - 1), + (x + 0) + 1000 * (y + 0), + (x + 1) + 1000 * (y + 1), + (x - 1) + 1000 * (y + 1), + (x + 1) + 1000 * (y - 1), + (x - 1) + 1000 * (y - 1) + }; + std::map> goodSpots; + int openCeilings = 0; + for ( auto coord : testAreas ) + { + int tx = coord % 1000; + int ty = coord / 1000; + if ( tx >= 1 && tx < map.width - 1 && ty >= 1 && ty < map.height - 1 ) + { + if ( possiblelocations[ty + tx * map.height] ) + { + int mapIndex = (ty)*MAPLAYERS + (tx)*MAPLAYERS * map.height; + if ( !map.tiles[OBSTACLELAYER + mapIndex] ) + { + if ( !map.tiles[(MAPLAYERS - 1) + mapIndex] ) + { + ++openCeilings; + goodSpots[0].push_back(coord); + } + else + { + goodSpots[1].push_back(coord); + } + } + } + } + } + if ( openCeilings >= 5 ) + { + batAreasGood.push_back(goodSpots); + } + else if ( (goodSpots[0].size() + goodSpots[1].size()) >= 5 ) + { + batAreasOk.push_back(goodSpots); + } + } + } + } + + std::unordered_set visited; + std::vector previousAreas; + int numBatAreas = std::max(2, std::min(5, 1 + (currentlevel / LENGTH_OF_LEVEL_REGION))); + while ( numBatAreas > 0 ) + { + if ( batAreasGood.size() == 0 && batAreasOk.size() == 0 ) + { + break; + } + + auto& areas = batAreasGood.size() > 0 ? batAreasGood : batAreasOk; + if ( areas.size() > 0 ) + { + size_t picked = map_rng.rand() % areas.size(); + auto& coords = areas[picked]; + + bool skip = false; + for ( auto coord : coords[0] ) + { + if ( visited.find(coord) != visited.end() ) + { + // no good + skip = true; + } + else + { + visited.insert(coord); + } + } + for ( auto coord : coords[1] ) + { + if ( visited.find(coord) != visited.end() ) + { + // no good + skip = true; + } + else + { + visited.insert(coord); + } + } + + int currentCoord = 0; + if ( coords[0].size() > 0 ) + { + currentCoord = coords[0][0]; + } + else if ( coords[1].size() > 0 ) + { + currentCoord = coords[1][0]; + } + + int checkx = currentCoord % 1000; + int checky = currentCoord / 1000; + for ( auto previousCoord : previousAreas ) + { + int ox = previousCoord % 1000; + int oy = previousCoord / 1000; + + real_t dx, dy; + dx = checkx - ox; + dy = checky - oy; + if ( sqrt(dx * dx + dy * dy) < 8.0 ) // too close to other regions, within 8 tiles + { + skip = true; + break; + } + } + + if ( skip ) + { + areas.erase(areas.begin() + picked); + continue; + } + + if ( coords[0].size() > 0 ) + { + previousAreas.push_back(coords[0][0]); + } + else if ( coords[1].size() > 0 ) + { + previousAreas.push_back(coords[1][0]); + } + + int numSpawns = std::min(4, 1 + (currentlevel / LENGTH_OF_LEVEL_REGION)); + for ( size_t i = 0; i < (coords[0].size() + coords[1].size()) && numSpawns > 0; ++i ) + { + auto coord = (i < coords[0].size()) ? coords[0][i] : coords[1][i - coords[0].size()]; + int tx = coord % 1000; + int ty = coord / 1000; + + { + Entity* ent = newEntity(188, 0, map.entities, nullptr); + ent->x = tx * 16.0; + ent->y = ty * 16.0; + } + + //Entity* ent = newEntity(245, 0, map.entities, nullptr); + ////ent->behavior = &actBoulder; + //ent->x = tx * 16.0 + 8; + //ent->y = ty * 16.0 + 8; + //ent->z = 24.0; + //ent->flags[PASSABLE] = true; + visited.insert(coord); + --numSpawns; + + possiblelocations[ty + tx * map.height] = false; + --numpossiblelocations; + } + + --numBatAreas; + + areas.erase(areas.begin() + picked); + continue; + } + } + } + // on hell levels, lava doesn't bubble. helps performance /*if( !strcmp(map.name,"Hell") ) { for( node=map.entities->first; node!=NULL; node=node->next ) { From 83b98eaf9daa3b3283da268339f9168172c74d63 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 11 Sep 2024 02:57:57 +1000 Subject: [PATCH 135/244] * map hash update --- src/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files.cpp b/src/files.cpp index 949f22dd7..6d1fbe6ff 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -698,7 +698,7 @@ std::unordered_map mapHashes = { { "mine15a.lmp", 14464 }, { "mine15b.lmp", 16268 }, { "mine15c.lmp", 15070 }, - { "mine15d.lmp", 10819 }, + { "mine15d.lmp", 11959 }, { "mine15e.lmp", 6270 }, { "mine16.lmp", 47683 }, { "mine16a.lmp", 5567 }, From 0b1eec68e234cf99d1ebc96f5bc32e36fd31106c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 08:40:03 +1000 Subject: [PATCH 136/244] * fmod set stream to not update automatically, fix perf --- src/engine/audio/init_audio.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/audio/init_audio.cpp b/src/engine/audio/init_audio.cpp index b0ad004fa..8e7e401b4 100644 --- a/src/engine/audio/init_audio.cpp +++ b/src/engine/audio/init_audio.cpp @@ -62,7 +62,7 @@ bool initSoundEngine() if (!no_sound) { - fmod_result = fmod_system->init(fmod_maxchannels, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_VOL0_BECOMES_VIRTUAL/*| FMOD_INIT_THREAD_UNSAFE | FMOD_INIT_PROFILE_ENABLE*/, fmod_extraDriverData); + fmod_result = fmod_system->init(fmod_maxchannels, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_VOL0_BECOMES_VIRTUAL | FMOD_INIT_STREAM_FROM_UPDATE| FMOD_INIT_THREAD_UNSAFE/* | FMOD_INIT_PROFILE_ENABLE */ , fmod_extraDriverData); if (FMODErrorCheck()) { printlog("[FMOD]: Failed to initialize FMOD. DISABLING AUDIO.\n"); From 5eb1395b418b63382c505d9209af42fa7dbaea6b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 08:40:23 +1000 Subject: [PATCH 137/244] * slime spray reduce reflect degrade chance --- src/magic/actmagic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 857fec0f8..2a2aa4ed9 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -1277,7 +1277,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. bool chance = false; if ( my->actmagicSpray == 1 ) { - chance = local_rng.rand() % 4 == 0; + chance = local_rng.rand() % 10 == 0; } else { From e1ae1787963959a1ebefb64c5e69c462a7899797 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 08:41:19 +1000 Subject: [PATCH 138/244] * bat spawn in 2x pairs from breakables --- src/actgeneral.cpp | 122 ++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index bea898481..3c51057e6 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -667,37 +667,52 @@ void Entity::colliderOnDestroy() if ( colliderHideMonster != 0 ) { int type = colliderHideMonster % 1000; - auto monster = summonMonster((Monster)type, ((int)(x / 16)) * 16 + 8, ((int)(y / 16)) * 16 + 8); - if ( monster ) + int numSpawns = type == OCTOPUS ? 2 : 1; + int successes = 0; + for ( int i = 0; i < numSpawns; ++i ) { - monster->yaw = yaw; - monster->lookAtEntity(*monster); - monster->monsterLookDir = yaw; - if ( Stat* stats = monster->getStats() ) + auto monster = summonMonster((Monster)type, ((int)(x / 16)) * 16 + 8, ((int)(y / 16)) * 16 + 8); + if ( monster ) { - stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; - if ( stats->type == GHOUL && currentlevel >= 15 ) + monster->yaw = yaw; + monster->lookAtEntity(*monster); + monster->monsterLookDir = yaw; + if ( Stat* stats = monster->getStats() ) { - strcpy(stats->name, "enslaved ghoul"); - stats->setAttribute("special_npc", "enslaved ghoul"); - } - if ( stats->type == AUTOMATON ) - { - monster->monsterStoreType = 1; // damaged + stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; + if ( stats->type == GHOUL && currentlevel >= 15 ) + { + strcpy(stats->name, "enslaved ghoul"); + stats->setAttribute("special_npc", "enslaved ghoul"); + } + if ( stats->type == AUTOMATON ) + { + monster->monsterStoreType = 1; // damaged + } + ++successes; } - if ( colliderKillerUid != 0 ) + //monster->attack(monster->getAttackPose(), 0, nullptr); + } + } + + if ( colliderKillerUid != 0 ) + { + if ( Entity* killer = uidToEntity(colliderKillerUid) ) + { + if ( killer->behavior == &actPlayer ) { - if ( Entity* killer = uidToEntity(colliderKillerUid) ) + if ( successes == 1 ) { - if ( killer->behavior == &actPlayer ) - { - messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234), - getMonsterLocalizedName(stats->type).c_str(), Language::get(getColliderLangName())); - } + messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName((Monster)type).c_str(), Language::get(getColliderLangName())); + } + else if ( successes > 1 ) + { + messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6253), + getMonsterLocalizedPlural((Monster)type).c_str(), Language::get(getColliderLangName())); } } } - //monster->attack(monster->getAttackPose(), 0, nullptr); } } if ( colliderContainedEntity != 0 ) @@ -971,37 +986,54 @@ void actColliderDecoration(Entity* my) my->colliderHideMonster = 0; bool bOldFlag = my->flags[PASSABLE]; my->flags[PASSABLE] = true; - auto monster = summonMonster((Monster)type, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8); - if ( monster ) - { - monster->yaw = my->yaw; - monster->lookAtEntity(*found); - if ( monster->checkEnemy(found) ) - { - monster->monsterAcquireAttackTarget(*found, MONSTER_STATE_PATH); - } - my->colliderCurrentHP = 0; - my->colliderKillerUid = 0; - if ( Stat* stats = monster->getStats() ) + int numSpawns = type == OCTOPUS ? 2 : 1; + int successes = 0; + for ( int i = 0; i < numSpawns; ++i ) + { + auto monster = summonMonster((Monster)type, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8); + if ( monster ) { - stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; - if ( stats->type == GHOUL && currentlevel >= 15 ) + monster->yaw = my->yaw; + monster->lookAtEntity(*found); + if ( monster->checkEnemy(found) ) { - strcpy(stats->name, "enslaved ghoul"); - stats->setAttribute("special_npc", "enslaved ghoul"); + monster->monsterAcquireAttackTarget(*found, MONSTER_STATE_PATH); } - if ( stats->type == AUTOMATON ) - { - monster->monsterStoreType = 1; // damaged - } - if ( found->behavior == &actPlayer ) + my->colliderCurrentHP = 0; + my->colliderKillerUid = 0; + + if ( Stat* stats = monster->getStats() ) { - messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234), - getMonsterLocalizedName(stats->type).c_str(), Language::get(my->getColliderLangName())); + stats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; + if ( stats->type == GHOUL && currentlevel >= 15 ) + { + strcpy(stats->name, "enslaved ghoul"); + stats->setAttribute("special_npc", "enslaved ghoul"); + } + if ( stats->type == AUTOMATON ) + { + monster->monsterStoreType = 1; // damaged + } + ++successes; } } } + + if ( found->behavior == &actPlayer ) + { + if ( successes == 1 ) + { + messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName((Monster)type).c_str(), Language::get(my->getColliderLangName())); + } + else if ( successes > 1 ) + { + messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6253), + getMonsterLocalizedPlural((Monster)type).c_str(), Language::get(my->getColliderLangName())); + } + } + my->flags[PASSABLE] = bOldFlag; } } From 0b796944d0bcbbdb11ba9ee115ac229f1e7474fb Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 08:45:23 +1000 Subject: [PATCH 139/244] * improve bat hit rates * bat spawn in pairs minimum on darkmap --- src/collision.cpp | 7 ++++--- src/entity.cpp | 6 +++--- src/maps.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index b65f3cf77..4ce0485af 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -547,17 +547,18 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) } } + bool accuracyBonus = projectile->behavior == &actMagicMissile; if ( backstab ) { miss = false; } else if ( flanking ) { - miss = local_rng.rand() % 3 != 0; + miss = local_rng.rand() % 10 < 6 + (accuracyBonus ? 3 : 0); } else { - miss = local_rng.rand() % 5 != 0; + miss = local_rng.rand() % 10 < 4 + (accuracyBonus ? 3 : 0); } if ( miss ) @@ -565,7 +566,7 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) if ( projectile->collisionIgnoreTargets.find(getUID()) == projectile->collisionIgnoreTargets.end() ) { projectile->collisionIgnoreTargets.insert(getUID()); - if ( (parent && parent->behavior == &actPlayer) || (parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader()) ) + if ( (parent && parent->behavior == &actPlayer) || (parent && parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader()) ) { spawnDamageGib(this, 0, DamageGib::DMG_MISS, true, true); } diff --git a/src/entity.cpp b/src/entity.cpp index 7f41c4306..b32fcb3f4 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7808,11 +7808,11 @@ void Entity::attack(int pose, int charge, Entity* target) } else if ( flanking ) { - miss = local_rng.rand() % 3 != 0; + miss = local_rng.rand() % 10 < 6; } else { - miss = local_rng.rand() % 5 != 0; + miss = local_rng.rand() % 10 < 4; } } @@ -9825,7 +9825,7 @@ void Entity::attack(int pose, int charge, Entity* target) { if ( !hitstats->EFFECTS[EFF_BLEEDING] ) { - if ( !hitstats->defending || (hitstats->defending && local_rng.rand() % 4 == 0) ) + if ( !hitstats->defending ) { if ( hit.entity->setEffect(EFF_BLEEDING, true, 6 * TICKS_PER_SECOND, false) ) { diff --git a/src/maps.cpp b/src/maps.cpp index 38a3dbc20..1b39e37d2 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4813,7 +4813,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple previousAreas.push_back(coords[1][0]); } - int numSpawns = std::min(4, 1 + (currentlevel / LENGTH_OF_LEVEL_REGION)); + int numSpawns = std::max(2, std::min(4, 1 + (currentlevel / LENGTH_OF_LEVEL_REGION))); for ( size_t i = 0; i < (coords[0].size() + coords[1].size()) && numSpawns > 0; ++i ) { auto coord = (i < coords[0].size()) ? coords[0][i] : coords[1][i - coords[0].size()]; From 1ff438a846fd7e7e56c55930254cae938ea6b240 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 10:22:38 +1000 Subject: [PATCH 140/244] * reduce stealth chance on backstab rest bats --- src/entity.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index b32fcb3f4..4983d65cf 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7896,6 +7896,7 @@ void Entity::attack(int pose, int charge, Entity* target) } Sint32 previousMonsterState = -1; + Sint32 previousMonsterSpecialState = -1; if ( hit.entity->behavior == &actBoulder ) { @@ -8092,6 +8093,7 @@ void Entity::attack(int pose, int charge, Entity* target) else if ( hit.entity->behavior == &actMonster && !mimic ) { previousMonsterState = hit.entity->monsterState; + previousMonsterSpecialState = hit.entity->monsterSpecialState; hitstats = hit.entity->getStats(); if ( hitstats ) { @@ -8800,9 +8802,22 @@ void Entity::attack(int pose, int charge, Entity* target) } } damage += bonus; - if ( local_rng.rand() % 4 > 0 && hit.entity->behavior != &actPlayer ) + if ( hit.entity->behavior != &actPlayer ) { - this->increaseSkill(PRO_STEALTH); + if ( hitstats->type == OCTOPUS && previousMonsterSpecialState == BAT_REST ) + { + if ( local_rng.rand() % 10 == 0 ) + { + this->increaseSkill(PRO_STEALTH); + } + } + else + { + if ( local_rng.rand() % 4 > 0 ) + { + this->increaseSkill(PRO_STEALTH); + } + } } } else if ( local_rng.rand() % 2 == 0 ) From 756111c83761309380834dfeaac5c8da7b12c4f9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 12 Sep 2024 10:25:53 +1000 Subject: [PATCH 141/244] * lang update --- lang/en.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en.txt b/lang/en.txt index 18695ddd5..125912651 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6479,5 +6479,6 @@ Magic Required: %d (%s)# 6250 STATS (HARDCORE)# 6251 Open Compendium# 6252 A nearby bat awakens!# +6253 Some %s jump out from the %s!# 6300 END# From ebc05f2af77e1329ea87c8a33c38778c0f34518d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 13 Sep 2024 07:24:08 +1000 Subject: [PATCH 142/244] * gungnir no miss bat --- src/entity.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 4983d65cf..d2165342c 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7814,6 +7814,14 @@ void Entity::attack(int pose, int charge, Entity* target) { miss = local_rng.rand() % 10 < 4; } + + if ( myStats->weapon ) + { + if ( myStats->weapon->type == ARTIFACT_SPEAR && !shapeshifted ) + { + miss = false; + } + } } if ( miss ) @@ -8834,15 +8842,6 @@ void Entity::attack(int pose, int charge, Entity* target) } } - bool gungnir = false; - if ( myStats->weapon ) - { - if ( myStats->weapon->type == ARTIFACT_SPEAR ) - { - gungnir = true; - } - } - static ConsoleVariable cvar_atkonhit("/enemy_debugatkonhit", false); if ( (weaponskill >= PRO_SWORD && weaponskill < PRO_SHIELD) || (weaponskill == PRO_UNARMED && behavior != &actMonster) || weaponskill == PRO_RANGED ) { From 17174b4a2b0fda4100794abec0a86058cb2cb4ae Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 13 Sep 2024 07:25:04 +1000 Subject: [PATCH 143/244] * update solution --- VS.2015/Barony/Barony.vcxproj | 3 ++- VS.2015/Barony/Barony.vcxproj.filters | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index 29ddf95a9..e703e1818 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -909,7 +909,7 @@ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe" true true true - OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmod_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win64-Shipping.lib;steam_api64.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;XPlatCppWindows_debug.lib;lib_json_debug.lib;%(AdditionalDependencies) + OpenGL32.lib;glu32.lib;SDL2.lib;SDL2main.lib;SDL2_net.lib;SDL2_image.lib;fmodL_vc.lib;libpng16.lib;zlib.lib;SDL2_ttf.lib;physfs.lib;EOSSDK-Win64-Shipping.lib;steam_api64.lib;libtheoraplayer.lib;glew32.lib;nfd.lib;libcurl.lib;ws2_32.lib;Normaliz.lib;Crypt32.lib;Wldap32.lib;XPlatCppWindows_debug.lib;lib_json_debug.lib;%(AdditionalDependencies) false true @@ -1183,6 +1183,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% + diff --git a/VS.2015/Barony/Barony.vcxproj.filters b/VS.2015/Barony/Barony.vcxproj.filters index 11839f1c9..fb6282010 100644 --- a/VS.2015/Barony/Barony.vcxproj.filters +++ b/VS.2015/Barony/Barony.vcxproj.filters @@ -495,6 +495,9 @@ Source Files + + Source Files + @@ -674,6 +677,9 @@ Header Files + + Header Files + From b8fab36aee379d660f319a4f153fec0eb7750b93 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 13 Sep 2024 07:25:19 +1000 Subject: [PATCH 144/244] * debug config.hpp temp stuff --- VS-includes/Config.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/VS-includes/Config.hpp b/VS-includes/Config.hpp index a2bd053e4..8d1e48ba9 100644 --- a/VS-includes/Config.hpp +++ b/VS-includes/Config.hpp @@ -9,8 +9,10 @@ #define BASE_DATA_DIR "./" -//#define DEBUG_ACHIEVEMENTS -//#define DEBUG_EVENT_TIMERS +#ifndef NDEBUG +#define DEBUG_ACHIEVEMENTS +#define DEBUG_EVENT_TIMERS +#endif #ifdef BARONY_DRM_FREE From 998e36bb6f1f26a7c7b2f185bdfc57558386f535 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 13 Sep 2024 07:29:02 +1000 Subject: [PATCH 145/244] * rename bat enum --- src/actgeneral.cpp | 4 +- src/actmonster.cpp | 36 +++++----- src/actsummontrap.cpp | 2 +- src/collision.cpp | 18 ++--- src/draw.cpp | 6 +- src/entity.cpp | 24 +++---- src/entity.hpp | 2 +- src/interface/interface.cpp | 8 +-- src/magic/magic.cpp | 8 +-- src/maps.cpp | 4 +- src/monster.hpp | 8 +-- src/monster_bat.cpp | 136 ++++++++++++++++++------------------ src/monster_shared.cpp | 2 +- src/player.cpp | 4 +- src/stat_editor.hpp | 2 +- src/stat_shared.cpp | 4 +- src/ui/MainMenu.cpp | 2 +- 17 files changed, 135 insertions(+), 135 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 3c51057e6..c56bdb84c 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -667,7 +667,7 @@ void Entity::colliderOnDestroy() if ( colliderHideMonster != 0 ) { int type = colliderHideMonster % 1000; - int numSpawns = type == OCTOPUS ? 2 : 1; + int numSpawns = type == BAT_SMALL ? 2 : 1; int successes = 0; for ( int i = 0; i < numSpawns; ++i ) { @@ -987,7 +987,7 @@ void actColliderDecoration(Entity* my) bool bOldFlag = my->flags[PASSABLE]; my->flags[PASSABLE] = true; - int numSpawns = type == OCTOPUS ? 2 : 1; + int numSpawns = type == BAT_SMALL ? 2 : 1; int successes = 0; for ( int i = 0; i < numSpawns; ++i ) { diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 314751611..ca6fd3f95 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -41,7 +41,7 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GOBLIN { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // SLIME { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // TROLL - { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // OCTOPUS + { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // BAT_SMALL { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SPIDER { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // GHOUL { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SKELETON @@ -83,7 +83,7 @@ bool monsterally[NUMMONSTERS][NUMMONSTERS] = { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // GOBLIN { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // SLIME { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL - { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // OCTOPUS + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, // GHOUL { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // SKELETON @@ -125,7 +125,7 @@ double sightranges[NUMMONSTERS] = 256, // GOBLIN 80, // SLIME 32, // TROLL - 128, // OCTOPUS + 128, // BAT_SMALL 96, // SPIDER 128, // GHOUL 192, // SKELETON @@ -630,7 +630,7 @@ void Entity::updateEntityOnHit(Entity* attacker, bool alertTarget) { disturbMimic(attacker, true, true); } - else if ( myStats->type == OCTOPUS ) + else if ( myStats->type == BAT_SMALL ) { disturbBat(attacker, true, true); } @@ -2299,7 +2299,7 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist) case GYROBOT: gyroBotAnimate(my, myStats, dist); break; case DUMMYBOT: dummyBotAnimate(my, myStats, dist); break; case MIMIC: mimicAnimate(my, myStats, dist); break; - case OCTOPUS: batAnimate(my, myStats, dist); break; + case BAT_SMALL: batAnimate(my, myStats, dist); break; default: break; } @@ -2391,7 +2391,7 @@ void actMonster(Entity* my) case GYROBOT: initGyroBot(my, nullptr); break; case DUMMYBOT: initDummyBot(my, nullptr); break; case MIMIC: initMimic(my, nullptr); break; - case OCTOPUS: initBat(my, nullptr); break; + case BAT_SMALL: initBat(my, nullptr); break; default: printlog("Unknown monster, can't init!"); break; } } @@ -2485,7 +2485,7 @@ void actMonster(Entity* my) case GYROBOT: initGyroBot(my, myStats); break; case DUMMYBOT: initDummyBot(my, myStats); break; case MIMIC: initMimic(my, myStats); break; - case OCTOPUS: initBat(my, myStats); break; + case BAT_SMALL: initBat(my, myStats); break; default: break; //This should never be reached. } } @@ -3606,7 +3606,7 @@ void actMonster(Entity* my) case MIMIC: mimicDie(my); break; - case OCTOPUS: + case BAT_SMALL: batDie(my); default: break; //This should never be reached. @@ -3923,7 +3923,7 @@ void actMonster(Entity* my) messagePlayer(monsterclicked, MESSAGE_INTERACTION, Language::get(6081)); } } - else if ( myStats->type == OCTOPUS && my->monsterSpecialState == BAT_REST ) + else if ( myStats->type == BAT_SMALL && my->monsterSpecialState == BAT_REST ) { my->disturbBat(players[monsterclicked]->entity, false, false); } @@ -4323,7 +4323,7 @@ void actMonster(Entity* my) continue; } bool entityInside = entityInsideEntity(my, entity); - /*if ( my->getRace() == OCTOPUS && entity->getRace() == OCTOPUS ) + /*if ( my->getRace() == BAT_SMALL && entity->getRace() == BAT_SMALL ) { int x1 = my->sizex; int y1 = my->sizey; @@ -5133,7 +5133,7 @@ void actMonster(Entity* my) { std::vector> possibleCoordinates; my->monsterMoveTime = local_rng.rand() % 30; - if ( myStats->type == MIMIC || myStats->type == OCTOPUS ) + if ( myStats->type == MIMIC || myStats->type == BAT_SMALL ) { my->monsterMoveTime = 2 + local_rng.rand() % 4; } @@ -5148,7 +5148,7 @@ void actMonster(Entity* my) searchLimitX = 5; searchLimitY = 5; } - else if ( myStats->type == OCTOPUS ) + else if ( myStats->type == BAT_SMALL ) { searchLimitX = 7; searchLimitY = 7; @@ -5661,7 +5661,7 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != OCTOPUS ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != BAT_SMALL ) { // break it down! my->monsterHitTime++; @@ -6770,7 +6770,7 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, 28, 64); } } - else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != OCTOPUS ) + else if ( hit.entity->isDamageableCollider() && myStats->type != GYROBOT && myStats->type != BAT_SMALL ) { // break it down! my->monsterHitTime++; @@ -7060,7 +7060,7 @@ void actMonster(Entity* my) { mimicResetIdle(my); } - else if ( !target && myStats->type == OCTOPUS ) + else if ( !target && myStats->type == BAT_SMALL ) { batResetIdle(my); } @@ -8611,7 +8611,7 @@ void actMonster(Entity* my) } else { - if ( myStats && myStats->type == OCTOPUS && my->monsterSpecialState == BAT_REST ) + if ( myStats && myStats->type == BAT_SMALL && my->monsterSpecialState == BAT_REST ) { my->monsterReleaseAttackTarget(); @@ -9011,7 +9011,7 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } } } - if ( myStats->type == OCTOPUS ) + if ( myStats->type == BAT_SMALL ) { bow = 1.5; } @@ -12856,7 +12856,7 @@ bool Entity::isInertMimic() const bool Entity::isUntargetableBat(real_t* outDist) const { - if ( behavior == &actMonster && getMonsterTypeFromSprite() == OCTOPUS ) + if ( behavior == &actMonster && getMonsterTypeFromSprite() == BAT_SMALL ) { if ( bodyparts.size() >= 1 ) { diff --git a/src/actsummontrap.cpp b/src/actsummontrap.cpp index 4091188e8..155c6434f 100644 --- a/src/actsummontrap.cpp +++ b/src/actsummontrap.cpp @@ -112,7 +112,7 @@ void actSummonTrap(Entity* my) // pick a completely random monster (barring some exceptions) const std::set typesToSkip { - LICH, SHOPKEEPER, DEVIL, MIMIC, CRAB, OCTOPUS, + LICH, SHOPKEEPER, DEVIL, MIMIC, CRAB, BAT_SMALL, MINOTAUR, LICH_FIRE, LICH_ICE, NOTHING, SENTRYBOT, SPELLBOT, GYROBOT, DUMMYBOT }; diff --git a/src/collision.cpp b/src/collision.cpp index 4ce0485af..15b1a689b 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -369,7 +369,7 @@ bool entityInsideTile(Entity* entity, int x, int y, int z, bool checkSafeTiles) { if ( !checkSafeTiles && !map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] ) { - if ( entity->behavior != &actDeathGhost && !(entity->behavior == &actMonster && entity->getStats() && entity->getStats()->type == OCTOPUS) ) + if ( entity->behavior != &actDeathGhost && !(entity->behavior == &actMonster && entity->getStats() && entity->getStats()->type == BAT_SMALL) ) { return true; } @@ -462,7 +462,7 @@ bool entityInsideSomething(Entity* entity) { continue; } - if ( entity->behavior == &actDeathGhost || entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actDeathGhost || entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( testEntity->behavior == &actMonster || testEntity->behavior == &actPlayer || (testEntity->isDamageableCollider() && (testEntity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC)) ) @@ -515,7 +515,7 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { if ( Stat* myStats = getStats() ) { - if ( myStats->type == OCTOPUS ) + if ( myStats->type == BAT_SMALL ) { bool miss = false; if ( isUntargetableBat() ) @@ -758,15 +758,15 @@ int barony_clear(real_t tx, real_t ty, Entity* my) continue; } if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_NPC) - && ((my->behavior == &actMonster && (type == GYROBOT || type == OCTOPUS)) || my->behavior == &actDeathGhost) ) + && ((my->behavior == &actMonster && (type == GYROBOT || type == BAT_SMALL)) || my->behavior == &actDeathGhost) ) { continue; } - if ( entity->behavior == &actFurniture && type == OCTOPUS ) + if ( entity->behavior == &actFurniture && type == BAT_SMALL ) { continue; } - if ( entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( my->behavior == &actBoulder ) { @@ -1215,7 +1215,7 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en bool ignoreFurniture = my && my->behavior == &actMonster && myStats && (myStats->type == SHOPKEEPER || myStats->type == MINOTAUR - || myStats->type == OCTOPUS); + || myStats->type == BAT_SMALL); for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) { @@ -1548,7 +1548,7 @@ real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, { ground = false; } - else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT || stats->type == OCTOPUS ) + else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT || stats->type == BAT_SMALL ) { ground = false; } @@ -2006,7 +2006,7 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity { continue; } - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == BAT_SMALL ) { continue; } diff --git a/src/draw.cpp b/src/draw.cpp index f78d8347f..d7100246f 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2382,11 +2382,11 @@ void drawEntities3D(view_t* camera, int mode) } else { - snprintf(buf, sizeof(buf), "%d", entity->skill[0]); - glDrawSpriteFromImage(camera, entity, buf, mode); + snprintf(buf, sizeof(buf), "%d", entity->skill[0]); + glDrawSpriteFromImage(camera, entity, buf, mode); + } } } - } else { glDrawSprite(camera, entity, mode); diff --git a/src/entity.cpp b/src/entity.cpp index d2165342c..a0fb3c335 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -6622,7 +6622,7 @@ bool Entity::isMobile() return false; } - if ( entitystats->type == OCTOPUS && monsterSpecialState == BAT_REST ) + if ( entitystats->type == BAT_SMALL && monsterSpecialState == BAT_REST ) { return false; } @@ -7013,7 +7013,7 @@ void Entity::attack(int pose, int charge, Entity* target) else { serverUpdateEntitySkill(this, 8); - if (myStats->type != SLIME && myStats->type != RAT && myStats->type != SCARAB && myStats->type != OCTOPUS) { + if (myStats->type != SLIME && myStats->type != RAT && myStats->type != SCARAB && myStats->type != BAT_SMALL) { serverUpdateEntitySkill(this, 9); } } @@ -7769,7 +7769,7 @@ void Entity::attack(int pose, int charge, Entity* target) if ( hit.entity && hit.entity->behavior == &actMonster ) { - if ( hit.entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( hit.entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( hit.entity->isUntargetableBat() ) { @@ -8812,7 +8812,7 @@ void Entity::attack(int pose, int charge, Entity* target) damage += bonus; if ( hit.entity->behavior != &actPlayer ) { - if ( hitstats->type == OCTOPUS && previousMonsterSpecialState == BAT_REST ) + if ( hitstats->type == BAT_SMALL && previousMonsterSpecialState == BAT_REST ) { if ( local_rng.rand() % 10 == 0 ) { @@ -9835,7 +9835,7 @@ void Entity::attack(int pose, int charge, Entity* target) } } } - else if ( myStats->type == OCTOPUS ) + else if ( myStats->type == BAT_SMALL ) { if ( !hitstats->EFFECTS[EFF_BLEEDING] ) { @@ -12508,7 +12508,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) // calculate XP gain int baseXp = 10; - if ( srcStats->type == OCTOPUS ) + if ( srcStats->type == BAT_SMALL ) { baseXp = 1 + local_rng.rand() % 2; } @@ -13365,11 +13365,11 @@ bool Entity::checkEnemy(Entity* your) { result = ShopkeeperPlayerHostility.isPlayerEnemy(this->skill[2]); } - else if ( myStats->type == OCTOPUS && your->behavior == &actPlayer ) + else if ( myStats->type == BAT_SMALL && your->behavior == &actPlayer ) { result = true; } - else if ( yourStats->type == OCTOPUS && behavior == &actPlayer ) + else if ( yourStats->type == BAT_SMALL && behavior == &actPlayer ) { result = true; } @@ -15154,7 +15154,7 @@ int Entity::getAttackPose() const type == CREATURE_IMP || type == SUCCUBUS || type == SHOPKEEPER || type == MINOTAUR || type == SHADOW || type == RAT || type == SPIDER || type == CRAB || - type == MIMIC || type == OCTOPUS || + type == MIMIC || type == BAT_SMALL || type == SLIME || (type == SCARAB && sprite != 1078 && sprite != 1079)) { pose = MONSTER_POSE_MELEE_WINDUP1; @@ -16888,7 +16888,7 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool { return; } - else if ( myStats->type == OCTOPUS && monsterSpecialState == BAT_REST ) + else if ( myStats->type == BAT_SMALL && monsterSpecialState == BAT_REST ) { return; } @@ -22204,7 +22204,7 @@ void Entity::alertAlliesOnBeingHit(Entity* attacker, std::unordered_set } real_t tangent = atan2(entity->y - this->y, entity->x - this->x); - if ( buddystats->type == OCTOPUS && entity->isUntargetableBat() && entity->bodyparts.size() > 0 && entity->monsterSpecialState == BAT_REST ) + if ( buddystats->type == BAT_SMALL && entity->isUntargetableBat() && entity->bodyparts.size() > 0 && entity->monsterSpecialState == BAT_REST ) { real_t oldZ = entity->bodyparts[0]->z; entity->bodyparts[0]->z = 0.0; // hack to make it linetraceable @@ -22217,7 +22217,7 @@ void Entity::alertAlliesOnBeingHit(Entity* attacker, std::unordered_set } if ( hit.entity == entity ) { - if ( buddystats->type == OCTOPUS ) + if ( buddystats->type == BAT_SMALL ) { if ( entity->monsterSpecialState == BAT_REST ) { diff --git a/src/entity.hpp b/src/entity.hpp index a15fbf0d3..151832599 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -1325,7 +1325,7 @@ class TextSourceScript TO_GOBLIN, TO_SLIME, TO_TROLL, - TO_OCTOPUS, + TO_BAT_SMALL, TO_SPIDER, TO_GHOUL, TO_SKELETON, diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index df8b65ac3..dafda794c 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -11113,7 +11113,7 @@ void EnemyHPDamageBarHandler::EnemyHPDetails::updateWorldCoordinates() worldX = entity->lerpRenderState.x.position * 16.0; worldY = entity->lerpRenderState.y.position * 16.0; worldZ = entity->lerpRenderState.z.position + enemyBarSettings.getHeightOffset(entity); - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( entity->bodyparts.size() > 0 ) { @@ -11126,7 +11126,7 @@ void EnemyHPDamageBarHandler::EnemyHPDetails::updateWorldCoordinates() worldX = entity->x; worldY = entity->y; worldZ = entity->z + enemyBarSettings.getHeightOffset(entity); - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( entity->bodyparts.size() > 0 ) { @@ -24586,7 +24586,7 @@ void CalloutRadialMenu::update() callout.y = entity->lerpRenderState.y.position * 16.0; callout.z = entity->lerpRenderState.z.position + enemyBarSettings.getHeightOffset(entity); callout.z -= 4; - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( entity->bodyparts.size() > 0 ) { @@ -24600,7 +24600,7 @@ void CalloutRadialMenu::update() callout.y = entity->y; callout.z = entity->z + enemyBarSettings.getHeightOffset(entity); callout.z -= 4; - if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == OCTOPUS ) + if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == BAT_SMALL ) { if ( entity->bodyparts.size() > 0 ) { diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 09b35e2dd..ecc11ab69 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -131,7 +131,7 @@ bool spellEffectDominate(Entity& my, spellElement_t& element, Entity& caster, En || hitstats->type == LICH_FIRE || hitstats->type == SHADOW || hitstats->type == MIMIC - || hitstats->type == OCTOPUS + || hitstats->type == BAT_SMALL || (hitstats->type == VAMPIRE && MonsterData_t::nameMatchesSpecialNPCName(*hitstats, "bram kindly")) || (hitstats->type == COCKATRICE && !strncmp(map.name, "Cockatrice Lair", 15)) ) @@ -1308,7 +1308,7 @@ int getCharmMonsterDifficulty(Entity& my, Stat& myStats) case LICH_FIRE: case MINOTAUR: case MIMIC: - case OCTOPUS: + case BAT_SMALL: difficulty = 666; break; } @@ -1708,7 +1708,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell if ( targetStats->type == LICH || targetStats->type == SHOPKEEPER || targetStats->type == DEVIL || targetStats->type == MINOTAUR || targetStats->type == LICH_FIRE || targetStats->type == LICH_ICE || (target->behavior == &actMonster && target->monsterAllySummonRank != 0) - || targetStats->type == MIMIC || targetStats->type == OCTOPUS + || targetStats->type == MIMIC || targetStats->type == BAT_SMALL || (targetStats->type == INCUBUS && !strncmp(targetStats->name, "inner demon", strlen("inner demon"))) || targetStats->type == SENTRYBOT || targetStats->type == SPELLBOT || targetStats->type == GYROBOT || targetStats->type == DUMMYBOT @@ -1734,7 +1734,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell { std::set typesToSkip { - LICH, SHOPKEEPER, DEVIL, MIMIC, CRAB, OCTOPUS, + LICH, SHOPKEEPER, DEVIL, MIMIC, CRAB, BAT_SMALL, MINOTAUR, LICH_FIRE, LICH_ICE, NOTHING, HUMAN, SENTRYBOT, SPELLBOT, GYROBOT, DUMMYBOT diff --git a/src/maps.cpp b/src/maps.cpp index 1b39e37d2..122a75593 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -5827,7 +5827,7 @@ void assignActions(map_t* map) case 164: monsterType = SPELLBOT; break; case 165: monsterType = DUMMYBOT; break; case 166: monsterType = GYROBOT; break; - case 188: monsterType = OCTOPUS; break; + case 188: monsterType = BAT_SMALL; break; default: monsterIsFixedSprite = false; monsterType = static_cast(monsterCurve(currentlevel)); @@ -5851,7 +5851,7 @@ void assignActions(map_t* map) entity->yaw = 90 * (map_rng.rand() % 4) * PI / 180.0; entity->monsterLookDir = entity->yaw; } - else if ( monsterType == OCTOPUS ) + else if ( monsterType == BAT_SMALL ) { entity->monsterSpecialState = BAT_REST; } diff --git a/src/monster.hpp b/src/monster.hpp index 6c63e32a6..41a30a1c2 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -27,7 +27,7 @@ enum Monster : int GOBLIN, SLIME, TROLL, - OCTOPUS, + BAT_SMALL, SPIDER, GHOUL, SKELETON, @@ -112,7 +112,7 @@ static std::vector monsterSprites[NUMMONSTERS] = { 1132, // thumpus }, - // OCTOPUS + // BAT_SMALL { 1408 }, @@ -386,7 +386,7 @@ static char gibtype[NUMMONSTERS] = 1, //GOBLIN, 3, //SLIME, 1, //TROLL, - 1, //OCTOPUS, + 1, //BAT_SMALL, 2, //SPIDER, 2, //GHOUL, 5, //SKELETON, @@ -430,7 +430,7 @@ static double damagetables[NUMMONSTERS][7] = { 0.9, 1.f, 1.1, 1.1, 1.1, 1.f, 0.8 }, // goblin { 1.4, 0.5, 1.3, 0.7, 0.5, 1.3, 0.5 }, // slime { 1.1, 0.8, 1.1, 0.8, 0.9, 1.f, 0.8 }, // troll - { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f }, // octopus + { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f }, // bat { 1.f, 1.1, 1.f, 1.2, 1.1, 1.f, 1.1 }, // spider { 1.f, 1.2, 0.8, 1.1, 0.6, 0.8, 1.1 }, // ghoul { 0.5, 1.4, 0.8, 1.3, 0.5, 0.8, 1.1 }, // skeleton diff --git a/src/monster_bat.cpp b/src/monster_bat.cpp index 4b3ce47c4..3be78328f 100644 --- a/src/monster_bat.cpp +++ b/src/monster_bat.cpp @@ -83,9 +83,9 @@ void initBat(Entity* my, Stat* myStats) entity->yaw = my->yaw; entity->z = 6; entity->flags[USERFLAG2] = my->flags[USERFLAG2]; - entity->focalx = limbs[OCTOPUS][1][0]; - entity->focaly = limbs[OCTOPUS][1][1]; - entity->focalz = limbs[OCTOPUS][1][2]; + entity->focalx = limbs[BAT_SMALL][1][0]; + entity->focaly = limbs[BAT_SMALL][1][1]; + entity->focalz = limbs[BAT_SMALL][1][2]; entity->behavior = &actBatLimb; entity->parent = my->getUID(); node = list_AddNodeLast(&my->children); @@ -103,9 +103,9 @@ void initBat(Entity* my, Stat* myStats) entity->flags[NOUPDATE] = true; entity->yaw = my->yaw; entity->flags[USERFLAG2] = my->flags[USERFLAG2]; - entity->focalx = limbs[OCTOPUS][2][0]; - entity->focaly = limbs[OCTOPUS][2][1]; - entity->focalz = limbs[OCTOPUS][2][2]; + entity->focalx = limbs[BAT_SMALL][2][0]; + entity->focaly = limbs[BAT_SMALL][2][1]; + entity->focalz = limbs[BAT_SMALL][2][2]; entity->behavior = &actBatLimb; entity->parent = my->getUID(); node = list_AddNodeLast(&my->children); @@ -123,9 +123,9 @@ void initBat(Entity* my, Stat* myStats) entity->flags[NOUPDATE] = true; entity->yaw = my->yaw; entity->flags[USERFLAG2] = my->flags[USERFLAG2]; - entity->focalx = limbs[OCTOPUS][3][0]; - entity->focaly = limbs[OCTOPUS][3][1]; - entity->focalz = limbs[OCTOPUS][3][2]; + entity->focalx = limbs[BAT_SMALL][3][0]; + entity->focaly = limbs[BAT_SMALL][3][1]; + entity->focalz = limbs[BAT_SMALL][3][2]; entity->behavior = &actBatLimb; entity->parent = my->getUID(); node = list_AddNodeLast(&my->children); @@ -143,9 +143,9 @@ void initBat(Entity* my, Stat* myStats) entity->flags[NOUPDATE] = true; entity->yaw = my->yaw; entity->flags[USERFLAG2] = my->flags[USERFLAG2]; - entity->focalx = limbs[OCTOPUS][4][0]; - entity->focaly = limbs[OCTOPUS][4][1]; - entity->focalz = limbs[OCTOPUS][4][2]; + entity->focalx = limbs[BAT_SMALL][4][0]; + entity->focaly = limbs[BAT_SMALL][4][1]; + entity->focalz = limbs[BAT_SMALL][4][2]; entity->behavior = &actBatLimb; entity->parent = my->getUID(); node = list_AddNodeLast(&my->children); @@ -280,12 +280,12 @@ void batAnimate(Entity* my, Stat* myStats, double dist) my->sizex = 2; my->sizey = 2; - my->focalx = limbs[OCTOPUS][0][0]; - my->focaly = limbs[OCTOPUS][0][1]; - my->focalz = limbs[OCTOPUS][0][2]; + my->focalx = limbs[BAT_SMALL][0][0]; + my->focaly = limbs[BAT_SMALL][0][1]; + my->focalz = limbs[BAT_SMALL][0][2]; if ( multiplayer != CLIENT ) { - my->z = limbs[OCTOPUS][5][2]; + my->z = limbs[BAT_SMALL][5][2]; if ( !myStats->EFFECTS[EFF_LEVITATING] ) { myStats->EFFECTS[EFF_LEVITATING] = true; @@ -446,9 +446,9 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } else { - if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][15][0] ) + if ( MONSTER_ATTACKTIME >= (int)limbs[BAT_SMALL][15][0] ) { - if ( MONSTER_ATTACKTIME == (int)limbs[OCTOPUS][15][0] ) + if ( MONSTER_ATTACKTIME == (int)limbs[BAT_SMALL][15][0] ) { if ( multiplayer != CLIENT ) { @@ -460,7 +460,7 @@ void batAnimate(Entity* my, Stat* myStats, double dist) if ( entity->skill[1] == 0 ) { - real_t speed = limbs[OCTOPUS][13][2]; + real_t speed = limbs[BAT_SMALL][13][2]; if ( limbAngleWithinRange(entity->fskill[0], -speed, -((PI / 2) + PI / 4)) ) { entity->fskill[0] = -((PI / 2) + PI / 4); @@ -474,36 +474,36 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } else { - real_t speed = limbs[OCTOPUS][13][1]; + real_t speed = limbs[BAT_SMALL][13][1]; entity->fskill[0] += speed; entity->fskill[0] = std::min(entity->fskill[0], 0.0); } } else { - real_t speed = limbs[OCTOPUS][13][0]; + real_t speed = limbs[BAT_SMALL][13][0]; entity->fskill[0] -= speed; entity->fskill[0] = std::max(entity->fskill[0], -((PI / 2) + PI / 32)); } - if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][18][0] ) + if ( MONSTER_ATTACKTIME >= (int)limbs[BAT_SMALL][18][0] ) { - BAT_FLOAT_ATK -= limbs[OCTOPUS][18][1]; - BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][18][2]); + BAT_FLOAT_ATK -= limbs[BAT_SMALL][18][1]; + BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[BAT_SMALL][18][2]); } - else if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][17][0] ) + else if ( MONSTER_ATTACKTIME >= (int)limbs[BAT_SMALL][17][0] ) { - BAT_FLOAT_ATK += limbs[OCTOPUS][17][1]; - BAT_FLOAT_ATK = std::min(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][17][2]); + BAT_FLOAT_ATK += limbs[BAT_SMALL][17][1]; + BAT_FLOAT_ATK = std::min(BAT_FLOAT_ATK, (real_t)limbs[BAT_SMALL][17][2]); } - else if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][16][0] ) + else if ( MONSTER_ATTACKTIME >= (int)limbs[BAT_SMALL][16][0] ) { - BAT_FLOAT_ATK -= limbs[OCTOPUS][16][1]; - BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[OCTOPUS][16][2]); + BAT_FLOAT_ATK -= limbs[BAT_SMALL][16][1]; + BAT_FLOAT_ATK = std::max(BAT_FLOAT_ATK, (real_t)limbs[BAT_SMALL][16][2]); } } - if ( MONSTER_ATTACKTIME >= (int)limbs[OCTOPUS][15][1] ) + if ( MONSTER_ATTACKTIME >= (int)limbs[BAT_SMALL][15][1] ) { MONSTER_ATTACK = 0; } @@ -591,7 +591,7 @@ void batAnimate(Entity* my, Stat* myStats, double dist) if ( entity->skill[1] == 0 ) { - real_t speed = limbs[OCTOPUS][14][0]; + real_t speed = limbs[BAT_SMALL][14][0]; if ( limbAngleWithinRange(entity->fskill[1], speed, 0.0) ) { entity->fskill[1] = 0.0; @@ -612,7 +612,7 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } else if ( entity->skill[1] == 1 ) { - real_t speed = limbs[OCTOPUS][14][0]; + real_t speed = limbs[BAT_SMALL][14][0]; if ( limbAngleWithinRange(entity->fskill[1], speed, PI / 4) ) { entity->fskill[1] = PI / 4; @@ -626,7 +626,7 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } else if ( entity->skill[1] == 2 ) { - real_t speed = limbs[OCTOPUS][14][1]; + real_t speed = limbs[BAT_SMALL][14][1]; real_t setpoint = -PI / 2 - PI / 8; if ( limbAngleWithinRange(entity->fskill[1], -speed, setpoint) ) { @@ -641,7 +641,7 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } else if ( entity->skill[1] == 3 ) { - real_t speed = limbs[OCTOPUS][14][2]; + real_t speed = limbs[BAT_SMALL][14][2]; if ( limbAngleWithinRange(entity->fskill[1], speed, 0.0) ) { entity->fskill[1] = 0.0; @@ -683,12 +683,12 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } entity->yaw += PI * sin(BAT_REST_ROTATE * PI / 2); - entity->x += limbs[OCTOPUS][6][0] * cos(entity->yaw); - entity->y += limbs[OCTOPUS][6][1] * sin(entity->yaw); - entity->z += limbs[OCTOPUS][6][2]; - entity->focalx = limbs[OCTOPUS][1][0]; - entity->focaly = limbs[OCTOPUS][1][1]; - entity->focalz = limbs[OCTOPUS][1][2]; + entity->x += limbs[BAT_SMALL][6][0] * cos(entity->yaw); + entity->y += limbs[BAT_SMALL][6][1] * sin(entity->yaw); + entity->z += limbs[BAT_SMALL][6][2]; + entity->focalx = limbs[BAT_SMALL][1][0]; + entity->focaly = limbs[BAT_SMALL][1][1]; + entity->focalz = limbs[BAT_SMALL][1][2]; if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) { @@ -778,9 +778,9 @@ void batAnimate(Entity* my, Stat* myStats, double dist) BAT_REST_FLY_Z += diff; } - BAT_FLOAT_X = limbs[OCTOPUS][10][0] * sin(body->fskill[1] * limbs[OCTOPUS][11][0]) * cos(entity->yaw + PI / 2); - BAT_FLOAT_Y = limbs[OCTOPUS][10][1] * sin(body->fskill[1] * limbs[OCTOPUS][11][1]) * sin(entity->yaw + PI / 2); - BAT_FLOAT_Z = limbs[OCTOPUS][10][2] * sin(body->fskill[1] * limbs[OCTOPUS][11][2]); + BAT_FLOAT_X = limbs[BAT_SMALL][10][0] * sin(body->fskill[1] * limbs[BAT_SMALL][11][0]) * cos(entity->yaw + PI / 2); + BAT_FLOAT_Y = limbs[BAT_SMALL][10][1] * sin(body->fskill[1] * limbs[BAT_SMALL][11][1]) * sin(entity->yaw + PI / 2); + BAT_FLOAT_Z = limbs[BAT_SMALL][10][2] * sin(body->fskill[1] * limbs[BAT_SMALL][11][2]); real_t floatAtkZ = BAT_FLOAT_ATK < 0 ? 2 * sin(BAT_FLOAT_ATK * PI / 8) : 0.5 * sin(BAT_FLOAT_ATK * PI / 8); BAT_FLOAT_Z += floatAtkZ; } @@ -796,12 +796,12 @@ void batAnimate(Entity* my, Stat* myStats, double dist) break; } case BAT_HEAD: - entity->x += limbs[OCTOPUS][7][0] * cos(entity->yaw); - entity->y += limbs[OCTOPUS][7][1] * sin(entity->yaw); - entity->z += limbs[OCTOPUS][7][2]; - entity->focalx = limbs[OCTOPUS][2][0]; - entity->focaly = limbs[OCTOPUS][2][1]; - entity->focalz = limbs[OCTOPUS][2][2]; + entity->x += limbs[BAT_SMALL][7][0] * cos(entity->yaw); + entity->y += limbs[BAT_SMALL][7][1] * sin(entity->yaw); + entity->z += limbs[BAT_SMALL][7][2]; + entity->focalx = limbs[BAT_SMALL][2][0]; + entity->focaly = limbs[BAT_SMALL][2][1]; + entity->focalz = limbs[BAT_SMALL][2][2]; if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) { if ( keystatus[SDLK_KP_7] ) @@ -824,12 +824,12 @@ void batAnimate(Entity* my, Stat* myStats, double dist) } break; case BAT_LEFTWING: - entity->x += limbs[OCTOPUS][8][0] * cos(entity->yaw + PI / 2); - entity->y += limbs[OCTOPUS][8][1] * sin(entity->yaw + PI / 2); - entity->z += limbs[OCTOPUS][8][2]; - entity->focalx = limbs[OCTOPUS][3][0]; - entity->focaly = limbs[OCTOPUS][3][1]; - entity->focalz = limbs[OCTOPUS][3][2]; + entity->x += limbs[BAT_SMALL][8][0] * cos(entity->yaw + PI / 2); + entity->y += limbs[BAT_SMALL][8][1] * sin(entity->yaw + PI / 2); + entity->z += limbs[BAT_SMALL][8][2]; + entity->focalx = limbs[BAT_SMALL][3][0]; + entity->focaly = limbs[BAT_SMALL][3][1]; + entity->focalz = limbs[BAT_SMALL][3][2]; if ( enableDebugKeys && (svFlags & SV_FLAG_CHEATS) ) { @@ -887,18 +887,18 @@ void batAnimate(Entity* my, Stat* myStats, double dist) entity->z += BAT_FLOAT_Z; entity->z += BAT_REST_FLY_Z; - entity->x += limbs[OCTOPUS][12][0] * sin(body->pitch) * cos(entity->yaw); - entity->y += limbs[OCTOPUS][12][1] * sin(body->pitch) * sin(entity->yaw); - entity->z += limbs[OCTOPUS][12][2] * sin(body->pitch); + entity->x += limbs[BAT_SMALL][12][0] * sin(body->pitch) * cos(entity->yaw); + entity->y += limbs[BAT_SMALL][12][1] * sin(body->pitch) * sin(entity->yaw); + entity->z += limbs[BAT_SMALL][12][2] * sin(body->pitch); } break; case BAT_RIGHTWING: - entity->x += limbs[OCTOPUS][9][0] * cos(entity->yaw + PI / 2); - entity->y += limbs[OCTOPUS][9][1] * sin(entity->yaw + PI / 2); - entity->z += limbs[OCTOPUS][9][2]; - entity->focalx = limbs[OCTOPUS][4][0]; - entity->focaly = limbs[OCTOPUS][4][1]; - entity->focalz = limbs[OCTOPUS][4][2]; + entity->x += limbs[BAT_SMALL][9][0] * cos(entity->yaw + PI / 2); + entity->y += limbs[BAT_SMALL][9][1] * sin(entity->yaw + PI / 2); + entity->z += limbs[BAT_SMALL][9][2]; + entity->focalx = limbs[BAT_SMALL][4][0]; + entity->focaly = limbs[BAT_SMALL][4][1]; + entity->focalz = limbs[BAT_SMALL][4][2]; if ( leftWing ) { entity->fskill[0] = leftWing->fskill[0]; @@ -914,9 +914,9 @@ void batAnimate(Entity* my, Stat* myStats, double dist) entity->z += BAT_FLOAT_Z; entity->z += BAT_REST_FLY_Z; - entity->x += limbs[OCTOPUS][12][0] * sin(body->pitch) * cos(entity->yaw); - entity->y += limbs[OCTOPUS][12][1] * sin(body->pitch) * sin(entity->yaw); - entity->z += limbs[OCTOPUS][12][2] * sin(body->pitch); + entity->x += limbs[BAT_SMALL][12][0] * sin(body->pitch) * cos(entity->yaw); + entity->y += limbs[BAT_SMALL][12][1] * sin(body->pitch) * sin(entity->yaw); + entity->z += limbs[BAT_SMALL][12][2] * sin(body->pitch); } break; diff --git a/src/monster_shared.cpp b/src/monster_shared.cpp index b50ba20d6..d86a2563b 100644 --- a/src/monster_shared.cpp +++ b/src/monster_shared.cpp @@ -130,7 +130,7 @@ void Entity::initMonster(int mySprite) monsterFootstepType = MONSTER_FOOTSTEP_STOMP; monsterSpellAnimation = MONSTER_SPELLCAST_NONE; break; - case OCTOPUS: + case BAT_SMALL: // unused break; case SPIDER: diff --git a/src/player.cpp b/src/player.cpp index e421c0e1d..221ee6771 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3415,7 +3415,7 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) return 0.0; } } - if ( parent->getMonsterTypeFromSprite() == OCTOPUS && !selectInteract ) + if ( parent->getMonsterTypeFromSprite() == BAT_SMALL && !selectInteract ) { return 0.0; } @@ -4464,7 +4464,7 @@ void Player::WorldUI_t::handleTooltips() parent = uidToEntity(tooltip->parent); if ( parent && parent->flags[INVISIBLE] && !(parent->behavior == &actMonster && - (parent->getMonsterTypeFromSprite() == DUMMYBOT || parent->getMonsterTypeFromSprite() == MIMIC || parent->getMonsterTypeFromSprite() == OCTOPUS)) ) + (parent->getMonsterTypeFromSprite() == DUMMYBOT || parent->getMonsterTypeFromSprite() == MIMIC || parent->getMonsterTypeFromSprite() == BAT_SMALL)) ) { continue; } diff --git a/src/stat_editor.hpp b/src/stat_editor.hpp index 7bb52db47..d0ddb03f0 100644 --- a/src/stat_editor.hpp +++ b/src/stat_editor.hpp @@ -30,7 +30,7 @@ typedef enum GOBLIN, SLIME, TROLL, - OCTOPUS, + BAT_SMALL, SPIDER, GHOUL, SKELETON, diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index adf05d7b6..c60355ad8 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1303,8 +1303,8 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->RANDOM_GOLD = 0; break; case 188: - case (1000 + OCTOPUS): - stats->type = OCTOPUS; + case (1000 + BAT_SMALL): + stats->type = BAT_SMALL; stats->appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 715f69bae..dcf19310d 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -33211,7 +33211,7 @@ namespace MainMenu { || monsterType == LICH_FIRE || monsterType == SHADOW || monsterType == MIMIC - || monsterType == OCTOPUS + || monsterType == BAT_SMALL || monsterType == SPELLBOT || monsterType == SENTRYBOT || monsterType == GYROBOT From 5c32d888401cdd92fa9d3e42c3beeeab3a57e65d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:46:01 +1000 Subject: [PATCH 146/244] * initial bugbear commit --- lang/compendium_lang/contents_monsters.json | 4 + lang/en.txt | 6 + src/actarrow.cpp | 1 + src/entity.hpp | 10 +- src/entity_shared.cpp | 11 +- src/game.hpp | 4 +- src/interface/consolecommand.cpp | 5 +- src/interface/interface.cpp | 1 + src/magic/magic.cpp | 3 + src/maps.cpp | 8 +- src/monster.hpp | 31 +- src/monster_bugbear.cpp | 1407 +++++++++++++++++++ src/monster_shared.cpp | 4 + src/net.cpp | 1 + src/scores.cpp | 19 + src/stat_shared.cpp | 33 + 16 files changed, 1532 insertions(+), 16 deletions(-) create mode 100644 src/monster_bugbear.cpp diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json index 1630f11f8..094f9bac1 100644 --- a/lang/compendium_lang/contents_monsters.json +++ b/lang/compendium_lang/contents_monsters.json @@ -17,6 +17,7 @@ {"BUBBLES": "bubbles"}, {"TROLL": "troll"}, {"THUMPUS": "thumpus"}, + {"BAT": "bat"}, {"SCORPION": "scorpion"}, {"SKRABBLAG": "skrabblag"}, {"SCARAB": "scarab"}, @@ -25,6 +26,7 @@ {"MINOTAUR": "minotaur"}, {" BEASTFOLK": "-"}, {"INSECTOID": "insectoid"}, + {"BUGBEAR": "bugbear"}, {"KOBOLD": "kobold"}, {"GOATMAN": "goatman"}, {"GHARBAD": "gharbad"}, @@ -69,8 +71,10 @@ {"BAPHOMET": "devil"}, {"BARATHEON": "baratheon"}, {"BARON HERX": "lich"}, + {"BAT": "bat"}, {"BRAM KINDLY": "bram kindly"}, {"BUBBLES": "bubbles"}, + {"BUGBEAR": "bugbear"}, {"COCKATRICE": "cockatrice"}, {"CORAL GRIMES": "coral grimes"}, {"CRAB": "crab"}, diff --git a/lang/en.txt b/lang/en.txt index 125912651..b4f741d1a 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6480,5 +6480,11 @@ Magic Required: %d (%s)# 6251 Open Compendium# 6252 A nearby bat awakens!# 6253 Some %s jump out from the %s!# +6254 was fangoriously drained by a bat.# +6255 gets roughhoused by a bugbear.# +6256 bugbear# +6257 bugbears# +6258 strikes# +6259 *grunt*# 6300 END# diff --git a/src/actarrow.cpp b/src/actarrow.cpp index c0e6b12de..c8a1152b5 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -546,6 +546,7 @@ void actArrow(Entity* my) case KOBOLD: case COCKATRICE: case TROLL: + case BUGBEAR: // more damage to these creatures huntingDamage = true; for ( int gibs = 0; gibs < 10; ++gibs ) diff --git a/src/entity.hpp b/src/entity.hpp index 151832599..b63fd33ca 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -968,6 +968,9 @@ class Entity case SLIME: slimeChooseWeapon(target, dist); break; + case BUGBEAR: + bugbearChooseWeapon(target, dist); + break; case SHOPKEEPER: if ( target ) { @@ -992,6 +995,7 @@ class Entity void shadowChooseWeapon(const Entity* target, double dist); void succubusChooseWeapon(const Entity* target, double dist); void slimeChooseWeapon(const Entity* target, double dist); + void bugbearChooseWeapon(const Entity* target, double dist); void skeletonSummonSetEquipment(Stat* myStats, int rank); static void tinkerBotSetStats(Stat* myStats, int rank); static void mimicSetStats(Stat* myStats); @@ -1100,6 +1104,7 @@ class Entity bool isUntargetableBat(real_t* outDist = nullptr) const; bool entityCanVomit() const; bool doSilkenBowOnAttack(Entity* attacker); + void setBugbearStrafeDir(bool forceDirection); }; Monster getMonsterFromPlayerRace(int playerRace); // convert playerRace into the relevant monster type @@ -1219,7 +1224,7 @@ void actTextSource(Entity* my); static const int NUM_ITEM_STRINGS = 333; static const int NUM_ITEM_STRINGS_BY_TYPE = 129; -static const int NUM_EDITOR_SPRITES = 188; +static const int NUM_EDITOR_SPRITES = 190; static const int NUM_EDITOR_TILES = 350; // furniture types. @@ -1355,7 +1360,8 @@ class TextSourceScript TO_SENTRYBOT, TO_SPELLBOT, TO_GYROBOT, - TO_DUMMYBOT + TO_DUMMYBOT, + TO_BUGBEAR }; enum ScriptType : int { diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index b11a48690..bed9861e9 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -54,6 +54,7 @@ int checkSpriteType(Sint32 sprite) case 165: case 166: case 188: + case 189: //monsters return 1; break; @@ -1012,7 +1013,9 @@ char spriteEditorNameStrings[NUM_EDITOR_SPRITES][64] = "NOT USED", "AND GATE", "AND GATE", - "AND GATE" + "AND GATE", + "BAT", + "BUGBEAR" }; char monsterEditorNameStrings[NUMMONSTERS][16] = @@ -1023,7 +1026,7 @@ char monsterEditorNameStrings[NUMMONSTERS][16] = "goblin", "slime", "troll", - "invalid", + "bat", "spider", "ghoul", "skeleton", @@ -1053,7 +1056,8 @@ char monsterEditorNameStrings[NUMMONSTERS][16] = "sentrybot", "spellbot", "gyrobot", - "dummybot" + "dummybot", + "bugbear" }; char tileEditorNameStrings[NUM_EDITOR_TILES][44] = @@ -1356,6 +1360,7 @@ int canWearEquip(Entity* entity, int category) break; //monsters with cloak/weapon/shield/boots/mask/gloves (no helm) + case BUGBEAR: case GNOME: case INCUBUS: case SUCCUBUS: diff --git a/src/game.hpp b/src/game.hpp index 622fd944e..ade57a37f 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -25,9 +25,9 @@ // REMEMBER TO CHANGE THIS WITH EVERY NEW OFFICIAL VERSION!!! #ifdef NINTENDO -static const char VERSION[] = "v4.2.1"; +static const char VERSION[] = "v4.2.2"; #else -static const char VERSION[] = "v4.2.1"; +static const char VERSION[] = "v4.2.2"; #endif #define GAME_CODE diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 4fc0a22c8..526dc9ad7 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4235,7 +4235,8 @@ namespace ConsoleCommands { COCKATRICE, INSECTOID, GOATMAN, - AUTOMATON + AUTOMATON, + BUGBEAR }; std::vector* set = nullptr; if (setToChoose == 1) @@ -5039,6 +5040,8 @@ namespace ConsoleCommands { case 164: monsterType = SPELLBOT; break; case 165: monsterType = DUMMYBOT; break; case 166: monsterType = GYROBOT; break; + case 188: monsterType = BAT_SMALL; break; + case 189: monsterType = BUGBEAR; break; default: break; } diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index dafda794c..a5d055784 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -4326,6 +4326,7 @@ int FollowerRadialMenu::optionDisabledForCreature(int playerSkillLVL, int monste case INCUBUS: case INSECTOID: case GOATMAN: + case BUGBEAR: creatureTier = 2; break; case CRYSTALGOLEM: diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index ecc11ab69..0d66b7e35 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -1294,6 +1294,7 @@ int getCharmMonsterDifficulty(Entity& my, Stat& myStats) case INCUBUS: case INSECTOID: case GOATMAN: + case BUGBEAR: difficulty = 2; break; case CRYSTALGOLEM: @@ -1774,6 +1775,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell case CRYSTALGOLEM: case SHADOW: case COCKATRICE: + case BUGBEAR: summonCanEquipItems = false; break; default: @@ -1795,6 +1797,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell case CRYSTALGOLEM: case SHADOW: case COCKATRICE: + case BUGBEAR: hitMonsterCanTransferEquipment = false; break; default: diff --git a/src/maps.cpp b/src/maps.cpp index 122a75593..82b0ea348 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -192,16 +192,16 @@ int monsterCurve(int level) case 1: case 2: case 3: - return GNOME; case 4: + return GNOME; case 5: case 6: case 7: - return TROLL; + return BUGBEAR; case 8: if ( map_rng.rand() % 10 > 0 ) { - return TROLL; + return BUGBEAR; } else { @@ -5771,6 +5771,7 @@ void assignActions(map_t* map) case 165: case 166: case 188: + case 189: { entity->sizex = 4; entity->sizey = 4; @@ -5828,6 +5829,7 @@ void assignActions(map_t* map) case 165: monsterType = DUMMYBOT; break; case 166: monsterType = GYROBOT; break; case 188: monsterType = BAT_SMALL; break; + case 189: monsterType = BUGBEAR; break; default: monsterIsFixedSprite = false; monsterType = static_cast(monsterCurve(currentlevel)); diff --git a/src/monster.hpp b/src/monster.hpp index 41a30a1c2..ae68dce2b 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -58,6 +58,7 @@ enum Monster : int SPELLBOT, GYROBOT, DUMMYBOT, + BUGBEAR, MAX_MONSTER }; const int NUMMONSTERS = MAX_MONSTER; @@ -287,6 +288,11 @@ static std::vector monsterSprites[NUMMONSTERS] = { { 889, }, + + // BUGBEAR + { + 1412, + }, }; static char monstertypename[][15] = @@ -327,7 +333,8 @@ static char monstertypename[][15] = "sentrybot", "spellbot", "gyrobot", - "dummybot" + "dummybot", + "bugbear" }; static char monstertypenamecapitalized[][15] = @@ -368,7 +375,8 @@ static char monstertypenamecapitalized[][15] = "Sentrybot", "Spellbot", "Gyrobot", - "Dummybot" + "Dummybot", + "Bugbear" }; // body part focal points @@ -416,7 +424,8 @@ static char gibtype[NUMMONSTERS] = 0, //SENTRYBOT 0, //SPELLBOT 0, //GYROBOT - 0 //DUMMYBOT + 0, //DUMMYBOT + 1 //BUGBEAR }; // columns go like this: @@ -430,7 +439,7 @@ static double damagetables[NUMMONSTERS][7] = { 0.9, 1.f, 1.1, 1.1, 1.1, 1.f, 0.8 }, // goblin { 1.4, 0.5, 1.3, 0.7, 0.5, 1.3, 0.5 }, // slime { 1.1, 0.8, 1.1, 0.8, 0.9, 1.f, 0.8 }, // troll - { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f }, // bat + { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.5 }, // bat { 1.f, 1.1, 1.f, 1.2, 1.1, 1.f, 1.1 }, // spider { 1.f, 1.2, 0.8, 1.1, 0.6, 0.8, 1.1 }, // ghoul { 0.5, 1.4, 0.8, 1.3, 0.5, 0.8, 1.1 }, // skeleton @@ -460,7 +469,8 @@ static double damagetables[NUMMONSTERS][7] = { 1.f, 1.f, 1.f, 1.f, 0.5, 0.5, 1.f }, // sentrybot { 1.f, 1.f, 1.f, 1.f, 0.5, 0.5, 1.f }, // sentrybot { 1.f, 1.f, 1.f, 1.f, 0.5, 0.5, 1.f }, // gyrobot - { 1.f, 1.f, 1.f, 1.f, 0.5, 1.2, 0.5 } // dummybot + { 1.f, 1.f, 1.f, 1.f, 0.5, 1.2, 0.5 }, // dummybot + { 1.3, 1.2, 1.2, 0.7, 0.8, 1.f, 0.7 } // bugbear }; enum DamageTableType : int @@ -699,6 +709,7 @@ void initGyroBot(Entity* my, Stat* myStats); void initDummyBot(Entity* my, Stat* myStats); void initMimic(Entity* my, Stat* myStats); void initBat(Entity* my, Stat* myStats); +void initBugbear(Entity* my, Stat* myStats); //--act*Limb functions-- void actHumanLimb(Entity* my); @@ -733,6 +744,7 @@ void actGyroBotLimb(Entity* my); void actDummyBotLimb(Entity* my); void actMimicLimb(Entity* my); void actBatLimb(Entity* my); +void actBugbearLimb(Entity* my); //--*Die functions-- void humanDie(Entity* my); @@ -769,6 +781,7 @@ void gyroBotDie(Entity* my); void dummyBotDie(Entity* my); void mimicDie(Entity* my); void batDie(Entity* my); +void bugbearDie(Entity* my); void monsterAnimate(Entity* my, Stat* myStats, double dist); //--*MoveBodyparts functions-- @@ -808,6 +821,7 @@ void gyroBotAnimate(Entity* my, Stat* myStats, double dist); void dummyBotAnimate(Entity* my, Stat* myStats, double dist); void mimicAnimate(Entity* my, Stat* myStats, double dist); void batAnimate(Entity* my, Stat* myStats, double dist); +void bugbearMoveBodyparts(Entity* my, Stat* myStats, double dist); //--misc functions-- void actMinotaurTrap(Entity* my); @@ -846,6 +860,8 @@ static const Sint32 MONSTER_STATE_LICHICE_TELEPORT_STATIONARY = 17; static const Sint32 MONSTER_STATE_LICHICE_DODGE = 13; static const Sint32 MONSTER_STATE_LICHFIRE_DIE = 18; static const Sint32 MONSTER_STATE_LICHICE_DIE = 18; +static const Sint32 MONSTER_STATE_GENERIC_DODGE = 19; +static const Sint32 MONSTER_STATE_GENERIC_CHARGE = 20; //--special monster attack constants static const int MONSTER_POSE_MELEE_WINDUP1 = 4; @@ -887,6 +903,7 @@ static const int MONSTER_POSE_MIMIC_LOCKED = 37; static const int MONSTER_POSE_MIMIC_LOCKED2 = 38; static const int MONSTER_POSE_MIMIC_MAGIC1 = 39; static const int MONSTER_POSE_MIMIC_MAGIC2 = 40; +static const int MONSTER_POSE_BUGBEAR_SHIELD = 41; //--monster special cooldowns static const int MONSTER_SPECIAL_COOLDOWN_GOLEM = 150; @@ -914,6 +931,7 @@ static const int MONSTER_SPECIAL_COOLDOWN_VAMPIRE_DRAIN = 300; static const int MONSTER_SPECIAL_COOLDOWN_SUCCUBUS_CHARM = 400; static const int MONSTER_SPECIAL_COOLDOWN_MIMIC_EAT = 500; static const int MONSTER_SPECIAL_COOLDOWN_SLIME_SPRAY = 250; +static const int MONSTER_SPECIAL_COOLDOWN_BUGBEAR = 500; //--monster target search types static const int MONSTER_TARGET_ENEMY = 0; @@ -1078,6 +1096,9 @@ static const int MIMIC_STATUS_IMMOBILE = 4; static const int BAT_REST = 1; static const int BAT_REST_DISTURBED = 2; +//-Bugbear-- +static const int BUGBEAR_DEFENSE = 1; + struct MonsterData_t { struct MonsterDataEntry_t diff --git a/src/monster_bugbear.cpp b/src/monster_bugbear.cpp new file mode 100644 index 000000000..2a70388c2 --- /dev/null +++ b/src/monster_bugbear.cpp @@ -0,0 +1,1407 @@ +/*------------------------------------------------------------------------------- + + BARONY + File: monster_bugbear.cpp + Desc: implements all of the bugbear monster's code + + Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved. + See LICENSE for details. + +-------------------------------------------------------------------------------*/ + +#include "main.hpp" +#include "game.hpp" +#include "stat.hpp" +#include "entity.hpp" +#include "items.hpp" +#include "monster.hpp" +#include "engine/audio/sound.hpp" +#include "net.hpp" +#include "collision.hpp" +#include "player.hpp" +#include "prng.hpp" +#include "scores.hpp" + +void initBugbear(Entity* my, Stat* myStats) +{ + int c; + node_t* node; + + my->flags[BURNABLE] = true; + my->initMonster(1412); + my->z = limbs[BUGBEAR][11][2]; + + if ( multiplayer != CLIENT ) + { + MONSTER_SPOTSND = 679; + MONSTER_SPOTVAR = 2; + MONSTER_IDLESND = 673; + MONSTER_IDLEVAR = 3; + } + if ( multiplayer != CLIENT && !MONSTER_INIT ) + { + auto& rng = my->entity_rng ? *my->entity_rng : local_rng; + + if ( myStats != NULL ) + { + if ( !myStats->leader_uid ) + { + myStats->leader_uid = 0; + } + + // apply random stat increases if set in stat_shared.cpp or editor + setRandomMonsterStats(myStats, rng); + + // generate 6 items max, less if there are any forced items from boss variants + int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT; + + // generates equipment and weapons if available from editor + createMonsterEquipment(myStats, rng); + + // create any custom inventory items from editor if available + createCustomInventory(myStats, customItemsToGenerate, rng); + + // count if any custom inventory items from editor + int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity. + + // count any inventory items set to default in edtior + int defaultItems = countDefaultItems(myStats); + + my->setHardcoreStats(*myStats); + + // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots + switch ( defaultItems ) + { + case 6: + case 5: + case 4: + case 3: + case 2: + case 1: + break; + default: + break; + } + + bool hasAlly = false; + if ( myStats->leader_uid == 0 && !my->flags[USERFLAG2] && rng.rand() % 5 == 0 ) + { + Entity* entity = summonMonster(BUGBEAR, my->x, my->y); + if ( entity ) + { + hasAlly = true; + entity->parent = my->getUID(); + if ( Stat* followerStats = entity->getStats() ) + { + followerStats->leader_uid = entity->parent; + } + my->parent = entity->getUID(); // so I know my ally + entity->seedEntityRNG(rng.getU32()); + } + } + + if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 ) + { + if ( myStats->leader_uid != 0 ) // minion + { + if ( Entity* leader = uidToEntity(myStats->leader_uid) ) + { + if ( leader->hasRangedWeapon() || rng.rand() % 2 == 0 ) + { + if ( rng.rand() % 2 == 0 ) + { + myStats->weapon = newItem(STEEL_SWORD, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + else + { + myStats->weapon = newItem(STEEL_AXE, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + } + else + { + myStats->weapon = newItem(HEAVY_CROSSBOW, static_cast(rng.rand() % 2 + WORN), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + } + else + { + if ( rng.rand() % 4 == 0 && !hasAlly ) + { + myStats->weapon = newItem(HEAVY_CROSSBOW, static_cast(rng.rand() % 2 + WORN), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + else + { + if ( rng.rand() % 2 == 0 ) + { + myStats->weapon = newItem(STEEL_SWORD, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + else + { + myStats->weapon = newItem(STEEL_AXE, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + } + } + } + + if ( myStats->shield == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 ) + { + if ( myStats->leader_uid != 0 ) // minion + { + if ( Entity* leader = uidToEntity(myStats->leader_uid) ) + { + if ( !leader->hasRangedWeapon() && rng.rand() % 4 == 0 ) + { + myStats->shield = newItem(STEEL_SHIELD, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + } + } + else + { + if ( (hasAlly && rng.rand() % 4 == 0) || (!hasAlly && rng.rand() % 2 == 0) ) + { + myStats->shield = newItem(STEEL_SHIELD, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); + } + } + } + } + } + + // torso + Entity* entity = newEntity(1413, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[BUGBEAR][1][0]; // 0 + entity->focaly = limbs[BUGBEAR][1][1]; // 0 + entity->focalz = limbs[BUGBEAR][1][2]; // 0 + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // right leg + entity = newEntity(1419, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[BUGBEAR][2][0]; // 1 + entity->focaly = limbs[BUGBEAR][2][1]; // 0 + entity->focalz = limbs[BUGBEAR][2][2]; // 2 + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // left leg + entity = newEntity(1418, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[BUGBEAR][3][0]; // 1 + entity->focaly = limbs[BUGBEAR][3][1]; // 0 + entity->focalz = limbs[BUGBEAR][3][2]; // 2 + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // right arm + entity = newEntity(1415, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[BUGBEAR][4][0]; // -.25 + entity->focaly = limbs[BUGBEAR][4][1]; // 0 + entity->focalz = limbs[BUGBEAR][4][2]; // 3 + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // left arm + entity = newEntity(1414, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->focalx = limbs[BUGBEAR][4][0]; // -.25 + entity->focaly = limbs[BUGBEAR][4][1]; // 0 + entity->focalz = limbs[BUGBEAR][4][2]; // 3 + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // world weapon + entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->noColorChangeAllyLimb = 1.0; + entity->focalx = limbs[BUGBEAR][6][0]; + entity->focaly = limbs[BUGBEAR][6][1]; + entity->focalz = limbs[BUGBEAR][6][2]; + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + entity->pitch = .25; + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // shield + entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->noColorChangeAllyLimb = 1.0; + entity->focalx = limbs[BUGBEAR][7][0]; + entity->focaly = limbs[BUGBEAR][7][1]; + entity->focalz = limbs[BUGBEAR][7][2]; + entity->behavior = &actBugbearLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); +} + +void actBugbearLimb(Entity* my) +{ + my->actMonsterLimb(); +} + +void bugbearDie(Entity* my) +{ + for ( int c = 0; c < 12; c++ ) + { + Entity* gib = spawnGib(my); + if (c < 6) { + gib->sprite = 1412 + c; + gib->skill[5] = 1; // poof + } + serverSpawnGibForClient(gib); + } + + my->spawnBlood(); + + playSoundEntity(my, 676 + local_rng.rand() % 3, 128); + + my->removeMonsterDeathNodes(); + + list_RemoveNode(my->mynode); + return; +} + +#define BUGBEARWALKSPEED .12 +real_t getWalkSpeed(Entity& my) +{ + real_t val = BUGBEARWALKSPEED; + if ( my.monsterState == MONSTER_STATE_GENERIC_CHARGE ) + { + val /= 2; + } + return val; +} + +void bugbearMoveBodyparts(Entity* my, Stat* myStats, double dist) +{ + node_t* node; + Entity* entity = nullptr; + Entity* rightbody = nullptr; + int bodypart; + + my->focalx = limbs[BUGBEAR][0][0]; + my->focaly = limbs[BUGBEAR][0][1]; + my->focalz = limbs[BUGBEAR][0][2]; + + /*if ( keystatus[SDLK_LSHIFT] ) + { + if ( keystatus[SDLK_h] ) + { + keystatus[SDLK_h] = 0; + myStats->EFFECTS[EFF_STUNNED] = !myStats->EFFECTS[EFF_STUNNED]; + myStats->EFFECTS_TIMERS[EFF_STUNNED] = myStats->EFFECTS[EFF_STUNNED] ? -1 : 0; + } + if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + int pose = MONSTER_POSE_MELEE_WINDUP1; + my->attack(pose, 0, nullptr); + } + if ( keystatus[SDLK_1] ) + { + keystatus[SDLK_1] = 0; + int pose = MONSTER_POSE_MELEE_WINDUP1; + pose = MONSTER_POSE_MELEE_WINDUP2; + my->attack(pose, 0, nullptr); + } + if ( keystatus[SDLK_2] ) + { + keystatus[SDLK_2] = 0; + int pose = MONSTER_POSE_MELEE_WINDUP1; + pose = MONSTER_POSE_MELEE_WINDUP3; + my->attack(pose, 0, nullptr); + } + if ( keystatus[SDLK_3] ) + { + keystatus[SDLK_3] = 0; + my->attack(MONSTER_POSE_SPECIAL_WINDUP1, 0, nullptr); + } + }*/ + bool wearingring = false; + + // set invisibility //TODO: isInvisible()? + if ( multiplayer != CLIENT ) + { + if ( myStats->ring != nullptr ) + if ( myStats->ring->type == RING_INVISIBILITY ) + { + wearingring = true; + } + if ( myStats->cloak != nullptr ) + if ( myStats->cloak->type == CLOAK_INVISIBILITY ) + { + wearingring = true; + } + if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true ) + { + my->flags[INVISIBLE] = true; + my->flags[BLOCKSIGHT] = false; + bodypart = 0; + for ( node = my->children.first; node != nullptr; node = node->next ) + { + if ( bodypart < LIMB_HUMANOID_TORSO ) + { + bodypart++; + continue; + } + if ( bodypart >= 7 ) + { + break; + } + entity = (Entity*)node->element; + if ( !entity->flags[INVISIBLE] ) + { + entity->flags[INVISIBLE] = true; + serverUpdateEntityBodypart(my, bodypart); + } + bodypart++; + } + } + else + { + my->flags[INVISIBLE] = false; + my->flags[BLOCKSIGHT] = true; + bodypart = 0; + for ( node = my->children.first; node != nullptr; node = node->next ) + { + if ( bodypart < LIMB_HUMANOID_TORSO ) + { + bodypart++; + continue; + } + if ( bodypart >= 7 ) + { + break; + } + entity = (Entity*)node->element; + if ( entity->flags[INVISIBLE] ) + { + entity->flags[INVISIBLE] = false; + serverUpdateEntityBodypart(my, bodypart); + serverUpdateEntityFlag(my, INVISIBLE); + } + bodypart++; + } + } + + // sleeping + if ( myStats->EFFECTS[EFF_ASLEEP] ) + { + my->z = limbs[BUGBEAR][11][0]; + } + else + { + my->z = limbs[BUGBEAR][11][2]; + } + } + + Entity* weaponarm = nullptr; + Entity* shieldarm = nullptr; + + //Move bodyparts + for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++) + { + if ( bodypart < 2 ) + { + continue; + } + entity = (Entity*)node->element; + entity->x = my->x; + entity->y = my->y; + entity->z = my->z; + entity->yaw = my->yaw; + if ( bodypart == LIMB_HUMANOID_RIGHTLEG || bodypart == LIMB_HUMANOID_LEFTARM ) + { + if ( bodypart == LIMB_HUMANOID_RIGHTLEG ) + { + rightbody = (Entity*)node->next->element; + } + + node_t* shieldNode = list_Node(&my->children, 8); + Entity* shield = nullptr; + if ( shieldNode ) + { + shield = (Entity*)shieldNode->element; + } + + if ( bodypart == LIMB_HUMANOID_LEFTARM + && (MONSTER_ATTACK == MONSTER_POSE_SPECIAL_WINDUP1 + || MONSTER_ATTACK == MONSTER_POSE_BUGBEAR_SHIELD) ) + { + my->monsterHitTime = 0; + if ( MONSTER_ATTACK == MONSTER_POSE_SPECIAL_WINDUP1 ) + { + if ( MONSTER_ATTACKTIME == 0 ) + { + entity->pitch = 0; + entity->skill[1] = 0; + } + + real_t shieldSetpoint = 2 * PI / 4; + if ( MONSTER_SHIELDYAW < shieldSetpoint ) + { + MONSTER_SHIELDYAW += 0.1; + } + if ( MONSTER_SHIELDYAW >= shieldSetpoint ) + { + MONSTER_SHIELDYAW = shieldSetpoint; + } + + if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.1, PI / 16, true, 0.01) ) + { + if ( MONSTER_ATTACKTIME >= 2 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) ) + { + if ( multiplayer != CLIENT ) + { + my->attack(MONSTER_POSE_BUGBEAR_SHIELD, 0, nullptr); + } + } + } + } + else if ( MONSTER_ATTACK == MONSTER_POSE_BUGBEAR_SHIELD ) + { + if ( MONSTER_ATTACKTIME == 0 ) + { + entity->pitch = PI / 16; + MONSTER_SHIELDYAW = PI / 4; + entity->skill[1] = 0; + } + + if ( MONSTER_SHIELDYAW > -PI / 8 ) + { + MONSTER_SHIELDYAW -= 0.1; + } + if ( MONSTER_SHIELDYAW <= -PI / 8 ) + { + MONSTER_SHIELDYAW = -PI / 8; + } + + if ( entity->skill[1] == 0 ) + { + if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 6 * PI / 4, false, 0.0) ) + { + entity->skill[1] = 1; + } + } + else if ( entity->skill[1] == 1 ) + { + if ( MONSTER_ATTACKTIME >= 0 ) + { + entity->skill[1] = 2; + } + } + else if ( entity->skill[1] == 2 ) + { + if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.1, 0.0, false, 0.0) ) + { + entity->skill[1] = 0; + MONSTER_ATTACK = 0; + } + } + } + } + else if ( dist > 0.1 && (bodypart == LIMB_HUMANOID_RIGHTLEG || (shield && shield->sprite <= 0)) ) + { + // swing right leg, left arm in sync. + if ( !rightbody->skill[0] ) + { + entity->pitch -= dist * getWalkSpeed(*my); + if ( entity->pitch < -PI / 4.0 ) + { + entity->pitch = -PI / 4.0; + if ( bodypart == 3 && entity->skill[0] == 0 ) + { + playSoundEntityLocal(my, 115, 128); + entity->skill[0] = 1; + } + } + } + else + { + entity->pitch += dist * getWalkSpeed(*my); + if ( entity->pitch > PI / 4.0 ) + { + entity->pitch = PI / 4.0; + if ( bodypart == 3 && entity->skill[0] == 1 ) + { + playSoundEntityLocal(my, 115, 128); + entity->skill[0] = 0; + } + } + } + } + else + { + // if not moving, reset position of the leg/arm. + if ( bodypart == LIMB_HUMANOID_LEFTARM ) + { + while ( entity->pitch < -PI ) + { + entity->pitch += 2 * PI; + } + while ( entity->pitch >= PI ) + { + entity->pitch -= 2 * PI; + } + + if ( entity->pitch < 0.0 ) + { + entity->pitch += 1 / fmax(dist * .1, 10.0); + if ( entity->pitch > 0.0 ) + { + entity->pitch = 0.0; + } + } + else if ( entity->pitch > 0.0 ) + { + entity->pitch -= 1 / fmax(dist * .1, 10.0); + if ( entity->pitch < 0.0 ) + { + entity->pitch = 0.0; + } + } + } + else + { + if ( entity->pitch < 0 ) + { + entity->pitch += 1 / fmax(dist * .1, 10.0); + if ( entity->pitch > 0 ) + { + entity->pitch = entity->fskill[0]; + } + } + else if ( entity->pitch > 0 ) + { + entity->pitch -= 1 / fmax(dist * .1, 10.0); + if ( entity->pitch < 0 ) + { + entity->pitch = entity->fskill[0]; + } + } + } + } + } + else if ( bodypart == LIMB_HUMANOID_LEFTLEG || bodypart == LIMB_HUMANOID_RIGHTARM ) + { + if ( bodypart == LIMB_HUMANOID_RIGHTARM ) + { + if ( my->monsterAttack > 0 ) + { + my->handleWeaponArmAttack(entity); + } + } + + if ( bodypart != LIMB_HUMANOID_RIGHTARM || (my->monsterAttack == 0 ) ) + { + // swing right arm/ left leg in sync + if ( dist > 0.1 ) + { + if ( entity->skill[0] ) + { + entity->pitch -= dist * getWalkSpeed(*my); + if ( entity->pitch < -PI / 4.0 ) + { + entity->skill[0] = 0; + entity->pitch = -PI / 4.0; + } + } + else + { + entity->pitch += dist * getWalkSpeed(*my); + if ( entity->pitch > PI / 4.0 ) + { + entity->skill[0] = 1; + entity->pitch = PI / 4.0; + } + } + } + else + { + // if not moving, reset position of the leg/arm. + if ( entity->pitch < 0 ) + { + entity->pitch += 1 / fmax(dist * .1, 10.0); + if ( entity->pitch > 0 ) + { + entity->pitch = 0; + } + } + else if ( entity->pitch > 0 ) + { + entity->pitch -= 1 / fmax(dist * .1, 10.0); + if ( entity->pitch < 0 ) + { + entity->pitch = 0; + } + } + } + } + } + switch ( bodypart ) + { + // torso + case 2: + entity->focalx = limbs[BUGBEAR][1][0]; + entity->focaly = limbs[BUGBEAR][1][1]; + entity->focalz = limbs[BUGBEAR][1][2]; + entity->x -= .5 * cos(my->yaw); + entity->y -= .5 * sin(my->yaw); + entity->z += 2.25; + break; + // right leg + case 3: + entity->focalx = limbs[BUGBEAR][2][0]; + entity->focaly = limbs[BUGBEAR][2][1]; + entity->focalz = limbs[BUGBEAR][2][2]; + entity->x += 2 * cos(my->yaw + PI / 2) - 1.25 * cos(my->yaw); + entity->y += 2 * sin(my->yaw + PI / 2) - 1.25 * sin(my->yaw); + entity->z += 5; + entity->x += limbs[BUGBEAR][9][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][9][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][9][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][9][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][9][2]; + if ( my->z >= (limbs[BUGBEAR][11][0] - 0.1) && my->z <= (limbs[BUGBEAR][11][0] + 0.1) ) + { + entity->yaw += PI / 8; + entity->pitch = -PI / 2; + } + else if ( entity->pitch <= -PI / 3 ) + { + entity->pitch = 0; + } + break; + // left leg + case 4: + entity->focalx = limbs[BUGBEAR][3][0]; + entity->focaly = limbs[BUGBEAR][3][1]; + entity->focalz = limbs[BUGBEAR][3][2]; + entity->x -= 2 * cos(my->yaw + PI / 2) + 1.25 * cos(my->yaw); + entity->y -= 2 * sin(my->yaw + PI / 2) + 1.25 * sin(my->yaw); + entity->z += 5; + entity->x += limbs[BUGBEAR][10][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][10][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][10][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][10][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][10][2]; + if ( my->z >= (limbs[BUGBEAR][11][0] - 0.1) && my->z <= (limbs[BUGBEAR][11][0] + 0.1) ) + { + entity->yaw -= PI / 8; + entity->pitch = -PI / 2; + } + else if ( entity->pitch <= -PI / 3 ) + { + entity->pitch = 0; + } + break; + // right arm + case 5: + { + weaponarm = entity; + entity->x += 3.5 * cos(my->yaw + PI / 2) - 1 * cos(my->yaw); + entity->y += 3.5 * sin(my->yaw + PI / 2) - 1 * sin(my->yaw); + entity->z += .1; + + node_t* weaponNode = list_Node(&my->children, 7); + if ( weaponNode ) + { + Entity* weapon = (Entity*)weaponNode->element; + if ( my->monsterState != MONSTER_STATE_ATTACK && my->monsterAttack == 0 ) + { + if ( weapon ) + { + if ( weapon->sprite != items[HEAVY_CROSSBOW].index ) + { + my->monsterArmbended = 1; + } + else + { + my->monsterArmbended = 0; + } + } + } + if ( MONSTER_ARMBENDED || (weapon->flags[INVISIBLE] && my->monsterState == MONSTER_STATE_WAIT) ) + { + // if weapon invisible and I'm not attacking, relax arm. + entity->focalx = limbs[BUGBEAR][4][0]; + entity->focaly = limbs[BUGBEAR][4][1]; + entity->focalz = limbs[BUGBEAR][4][2]; + entity->sprite = 1415; + + entity->x += limbs[BUGBEAR][16][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][16][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][16][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][16][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][16][2]; + + if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2 || my->monsterAttack == 2 ) + { + entity->focaly += 0.5; + entity->x += -1 * cos(my->yaw + PI / 2); + entity->y += -1 * sin(my->yaw + PI / 2); + } + } + else + { + // else flex arm. + entity->focalx = limbs[BUGBEAR][15][0]; + entity->focaly = limbs[BUGBEAR][15][1]; + entity->focalz = limbs[BUGBEAR][15][2]; + entity->sprite = 1417; + + entity->x += limbs[BUGBEAR][12][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][12][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][12][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][12][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][12][2]; + } + } + + entity->yaw += MONSTER_WEAPONYAW; + if ( my->z >= (limbs[BUGBEAR][11][0] - 0.1) && my->z <= (limbs[BUGBEAR][11][0] + 0.1) ) + { + entity->pitch = 0; + } + break; + } + // left arm + case 6: + { + shieldarm = entity; + entity->x -= 3.5 * cos(my->yaw + PI / 2) + 1 * cos(my->yaw); + entity->y -= 3.5 * sin(my->yaw + PI / 2) + 1 * sin(my->yaw); + entity->z += .1; + node_t* shieldNode = list_Node(&my->children, 8); + if ( shieldNode ) + { + Entity* shield = (Entity*)shieldNode->element; + if ( shield->flags[INVISIBLE] && my->monsterState == MONSTER_STATE_WAIT ) + { + // relax arm + entity->focalx = limbs[BUGBEAR][4][0]; + entity->focaly = limbs[BUGBEAR][4][1]; + entity->focalz = limbs[BUGBEAR][4][2]; + entity->sprite = 1414; + + entity->x += -limbs[BUGBEAR][16][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][16][1] * cos(my->yaw); + entity->y += -limbs[BUGBEAR][16][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][16][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][16][2]; + } + else + { + // else flex arm + entity->focalx = limbs[BUGBEAR][5][0]; + entity->focaly = limbs[BUGBEAR][5][1]; + entity->focalz = limbs[BUGBEAR][5][2]; + entity->sprite = 1416; + entity->x += limbs[BUGBEAR][8][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][8][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][8][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][8][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][8][2]; + } + } + if ( my->z >= (limbs[BUGBEAR][11][0] - 0.1) && my->z <= (limbs[BUGBEAR][11][0] + 0.1) ) + { + entity->pitch = 0; + } + + if ( MONSTER_ATTACK != MONSTER_POSE_BUGBEAR_SHIELD && MONSTER_ATTACK != MONSTER_POSE_SPECIAL_WINDUP1 ) + { + if ( my->monsterDefend && my->monsterAttack == 0 ) + { + if ( MONSTER_SHIELDYAW < 2 * PI / 5 ) + { + MONSTER_SHIELDYAW += 0.3; + MONSTER_SHIELDYAW = std::min(MONSTER_SHIELDYAW, 2 * PI / 5); + } + else if ( MONSTER_SHIELDYAW > 2 * PI / 5 ) + { + MONSTER_SHIELDYAW -= 0.3; + MONSTER_SHIELDYAW = std::max(MONSTER_SHIELDYAW, 2 * PI / 5); + } + } + else + { + if ( MONSTER_SHIELDYAW > 0.0 ) + { + MONSTER_SHIELDYAW -= 0.3; + MONSTER_SHIELDYAW = std::max(MONSTER_SHIELDYAW, 0.0); + } + else if ( MONSTER_SHIELDYAW < 0.0 ) + { + MONSTER_SHIELDYAW += 0.3; + MONSTER_SHIELDYAW = std::min(MONSTER_SHIELDYAW, 0.0); + } + } + } + entity->yaw += MONSTER_SHIELDYAW; + break; + } + case LIMB_HUMANOID_WEAPON: + if ( multiplayer != CLIENT ) + { + /*if ( keystatus[SDLK_LSHIFT] ) + { + if ( keystatus[SDLK_KP_7] ) + { + keystatus[SDLK_KP_7] = 0; + entity->sprite = -1; + if ( myStats->weapon ) + { + list_RemoveNode(myStats->weapon->node); + myStats->weapon = nullptr; + } + } + if ( keystatus[SDLK_KP_8] ) + { + keystatus[SDLK_KP_8] = 0; + entity->sprite = 1422; + if ( !myStats->weapon ) + { + myStats->weapon = newItem(STEEL_SWORD, EXCELLENT, 0, 1, 0, false, nullptr); + } + else + { + myStats->weapon->type = STEEL_SWORD; + } + } + if ( keystatus[SDLK_KP_9] ) + { + keystatus[SDLK_KP_9] = 0; + entity->sprite = 1424; + if ( !myStats->weapon ) + { + myStats->weapon = newItem(STEEL_AXE, EXCELLENT, 0, 1, 0, false, nullptr); + } + else + { + myStats->weapon->type = STEEL_AXE; + } + } + if ( keystatus[SDLK_KP_6] ) + { + keystatus[SDLK_KP_6] = 0; + entity->sprite = 984; + if ( !myStats->weapon ) + { + myStats->weapon = newItem(HEAVY_CROSSBOW, EXCELLENT, 0, 1, 0, false, nullptr); + } + else + { + myStats->weapon->type = HEAVY_CROSSBOW; + } + } + }*/ + + if ( myStats->weapon == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? + { + entity->flags[INVISIBLE] = true; + } + else + { + entity->flags[INVISIBLE] = false; + if ( myStats->weapon->type == STEEL_AXE ) + { + entity->sprite = 1424; + } + else if ( myStats->weapon->type == STEEL_SWORD ) + { + entity->sprite = 1422; + } + else if ( myStats->weapon->type == HEAVY_CROSSBOW ) + { + entity->sprite = itemModel(myStats->weapon); + } + else + { + entity->flags[INVISIBLE] = true; + } + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) + { + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } + } + } + } + else + { + if ( entity->sprite <= 0 ) + { + entity->flags[INVISIBLE] = true; + } + } + if ( weaponarm != nullptr ) + { + my->handleHumanoidWeaponLimb(entity, weaponarm); + } + entity->x += limbs[BUGBEAR][13][0] * cos(my->yaw + PI / 2) + limbs[BUGBEAR][13][1] * cos(my->yaw); + entity->y += limbs[BUGBEAR][13][0] * sin(my->yaw + PI / 2) + limbs[BUGBEAR][13][1] * sin(my->yaw); + entity->z += limbs[BUGBEAR][13][2]; + if ( MONSTER_ARMBENDED ) + { + entity->focalx = limbs[BUGBEAR][17][0]; + entity->focaly = limbs[BUGBEAR][17][1]; + entity->focalz = limbs[BUGBEAR][17][2]; + if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP2 || MONSTER_ATTACK == 2 ) + { + entity->focaly += 0.75; + } + } + else + { + entity->focalx = limbs[BUGBEAR][6][0]; + entity->focaly = limbs[BUGBEAR][6][1]; + entity->focalz = limbs[BUGBEAR][6][2]; + if ( entity->sprite == items[HEAVY_CROSSBOW].index ) + { + entity->focalx += 4.0; + entity->focalz += 2; + } + else if ( entity->sprite == 1424 ) + { + entity->focalz += 1; + } + } + break; + case LIMB_HUMANOID_SHIELD: + //if ( keystatus[SDLK_LSHIFT] ) + //{ + // if ( keystatus[SDLK_KP_1] ) + // { + // keystatus[SDLK_KP_1] = 0; + // entity->sprite = -1; + // if ( myStats->shield ) + // { + // list_RemoveNode(myStats->shield->node); + // myStats->shield = nullptr; + // } + // } + // if ( keystatus[SDLK_KP_3] ) + // { + // keystatus[SDLK_KP_3] = 0; + // entity->sprite = 1420; + // if ( !myStats->shield ) + // { + // myStats->shield = newItem(STEEL_SHIELD, EXCELLENT, 0, 1, 0, false, nullptr); + // } + // else + // { + // myStats->shield->type = STEEL_SHIELD; + // } + // } + //} + ///*if ( keystatus[SDLK_SPACE] ) + //{ + // keystatus[SDLK_SPACE] = 0; + // my->monsterDefend = my->monsterDefend == 0 ? 1 : 0; + //}*/ + if ( multiplayer != CLIENT ) + { + if ( myStats->shield == nullptr ) + { + entity->flags[INVISIBLE] = true; + entity->sprite = 0; + } + else + { + entity->flags[INVISIBLE] = false; + if ( myStats->shield->type == STEEL_SHIELD ) + { + entity->sprite = 1420; + } + else + { + entity->flags[INVISIBLE] = true; + } + } + if ( myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? + { + entity->flags[INVISIBLE] = true; + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) + { + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } + } + } + } + else + { + if ( entity->sprite <= 0 ) + { + entity->flags[INVISIBLE] = true; + } + } + if ( shieldarm != nullptr ) + { + my->handleHumanoidShieldLimb(entity, shieldarm); + } + break; + default: + break; + } + } + // rotate shield a bit + node_t* shieldNode = list_Node(&my->children, 8); + if ( shieldNode ) + { + Entity* shieldEntity = (Entity*)shieldNode->element; + if ( shieldEntity->sprite != items[TOOL_TORCH].index && shieldEntity->sprite != items[TOOL_LANTERN].index && shieldEntity->sprite != items[TOOL_CRYSTALSHARD].index ) + { + shieldEntity->yaw -= 2 * PI / 6; + } + } + if ( MONSTER_ATTACK > 0 && (MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 + || MONSTER_ATTACK == MONSTER_POSE_SPECIAL_WINDUP1 + || MONSTER_ATTACK == MONSTER_POSE_BUGBEAR_SHIELD) ) + { + MONSTER_ATTACKTIME++; + } + else if ( MONSTER_ATTACK == 0 ) + { + MONSTER_ATTACKTIME = 0; + } + else + { + // do nothing, don't reset attacktime or increment it. + } +} + +void Entity::bugbearChooseWeapon(const Entity* target, double dist) +{ + Stat* myStats = getStats(); + if ( !myStats ) + { + return; + } + + if ( monsterSpecialState == BUGBEAR_DEFENSE ) + { + if ( monsterStrafeDirection == 0 && local_rng.rand() % 10 == 0 && ticks % 10 == 0 ) + { + setBugbearStrafeDir(true); + //monsterStrafeDirection = -1 + ((local_rng.rand() % 2 == 0) ? 2 : 0); + } + } + + if ( monsterSpecialState != 0 || monsterSpecialTimer != 0 ) + { + if ( monsterSpecialTimer < MONSTER_SPECIAL_COOLDOWN_BUGBEAR / 2 ) + { + monsterSpecialState = 0; + monsterStrafeDirection = 0; + } + return; + } + + if ( monsterSpecialTimer == 0 + && (ticks % 10 == 0) + && (dist < STRIKERANGE * 2 || hasRangedWeapon()) ) + { + Stat* targetStats = target->getStats(); + if ( !targetStats ) + { + return; + } + + int specialRoll = -1; + int bonusFromHP = 0; + specialRoll = local_rng.rand() % 40; + if ( myStats->HP <= myStats->MAXHP * 0.8 ) + { + bonusFromHP += 2; // +% chance if on low health + } + if ( myStats->HP <= myStats->MAXHP * 0.4 ) + { + bonusFromHP += 3; // +extra % chance if on lower health + } + + int requiredRoll = (2 + bonusFromHP); + + if ( dist < STRIKERANGE ) + { + requiredRoll += 5; + } + + if ( specialRoll < requiredRoll ) + { + Entity* leader = nullptr; + if ( myStats->leader_uid != 0 ) + { + leader = uidToEntity(myStats->leader_uid); + } + else if ( parent != 0 ) + { + leader = uidToEntity(parent); + } + + if ( leader && leader->monsterSpecialState == BUGBEAR_DEFENSE ) + { + if ( local_rng.rand() % 2 != 0 || leader->monsterSpecialTimer >= MONSTER_SPECIAL_COOLDOWN_BUGBEAR - TICKS_PER_SECOND ) + { + return; + } + } + + monsterSpecialState = BUGBEAR_DEFENSE; + monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_BUGBEAR; + + if ( leader ) + { + if ( leader->monsterStrafeDirection != 0 ) + { + if ( local_rng.rand() % 2 == 0 ) + { + monsterStrafeDirection = 0; + return; + } + } + } + + setBugbearStrafeDir(true); + //monsterStrafeDirection = -1 + ((local_rng.rand() % 2 == 0) ? 2 : 0); + } + } +} + +void Entity::setBugbearStrafeDir(bool forceDirection) +{ + Stat* myStats = getStats(); + if ( !myStats ) { return; } + if ( myStats->type != BUGBEAR ) + { + return; + } + if ( monsterSpecialState != BUGBEAR_DEFENSE ) + { + return; + } + Entity* leader = nullptr; + if ( myStats->leader_uid != 0 ) + { + leader = uidToEntity(myStats->leader_uid); + } + else if ( parent != 0 ) + { + leader = uidToEntity(parent); + } + + std::vector dirs = { -1, 1 }; + if ( !forceDirection ) + { + dirs.push_back(0); + } + std::set gooddirs; + real_t ox = x; + real_t oy = y; + + Entity* target = monsterTarget != 0 ? uidToEntity(monsterTarget) : nullptr; + + for ( auto dir : dirs ) + { + x = ox; + y = oy; + real_t tangent = yaw; + tangent += (PI / 2) * dir; + if ( dir != 0 ) + { + x = this->x + 4 * cos(tangent); + y = this->y + 4 * sin(tangent); + + bool bFlag = false; + if ( target ) + { + bFlag = target->flags[PASSABLE]; + target->flags[PASSABLE] = true; + } + Entity* ohitentity = hit.entity; + bool clear = barony_clear(x, y, this); + hit.entity = ohitentity; + if ( target ) + { + target->flags[PASSABLE] = bFlag; + } + + if ( !clear ) + { + // no good, blocked by things + continue; + } + } + + if ( leader ) + { + if ( entityInsideEntity(leader, this) ) + { + // no good, blocked by ally + continue; + } + + if ( Stat* leaderStats = leader->getStats() ) + { + if ( monsterTarget != 0 && leader->monsterTarget == monsterTarget ) + { + // check LOS of leader to their target + if ( target ) + { + real_t tangent2 = atan2(target->y - leader->y, target->x - leader->x); + // trace the tangent see if we would intersect it + Entity* ohitentity = hit.entity; + real_t dist = lineTraceTarget(leader, leader->x, leader->y, tangent2, 128.0, 0, false, this); + bool inTheWay = hit.entity == this; + hit.entity = ohitentity; + if ( inTheWay ) + { + continue; + } + } + } + } + } + gooddirs.insert(dir); + } + + x = ox; + y = oy; + + if ( gooddirs.size() > 0 ) + { + if ( monsterStrafeDirection != 0 ) + { + if ( gooddirs.size() >= 2 && gooddirs.find(monsterStrafeDirection) != gooddirs.end() ) + { + // remove current direction + gooddirs.erase(monsterStrafeDirection); + } + } + auto it = gooddirs.begin(); + int pick = local_rng.rand() % gooddirs.size(); + for ( int i = 0; i < pick; ++i ) + { + ++it; + } + monsterStrafeDirection = *it; + } + else + { + if ( leader && leader->monsterStrafeDirection != 0 ) + { + monsterStrafeDirection = -1 * leader->monsterStrafeDirection; + } + else + { + if ( monsterStrafeDirection != 0 ) + { + monsterStrafeDirection = -1 * monsterStrafeDirection; + } + else + { + monsterStrafeDirection = local_rng.rand() % 2 == 0 ? -1 : 1; + } + } + } + + //messagePlayer(0, MESSAGE_DEBUG, "pickdir: %d", monsterStrafeDirection); +} \ No newline at end of file diff --git a/src/monster_shared.cpp b/src/monster_shared.cpp index d86a2563b..324777c49 100644 --- a/src/monster_shared.cpp +++ b/src/monster_shared.cpp @@ -235,6 +235,10 @@ void Entity::initMonster(int mySprite) monsterFootstepType = MONSTER_FOOTSTEP_NONE; monsterSpellAnimation = MONSTER_SPELLCAST_NONE; break; + case BUGBEAR: + monsterFootstepType = MONSTER_FOOTSTEP_STOMP; + monsterSpellAnimation = MONSTER_SPELLCAST_NONE; + break; default: monsterFootstepType = MONSTER_FOOTSTEP_NONE; monsterSpellAnimation = MONSTER_SPELLCAST_NONE; diff --git a/src/net.cpp b/src/net.cpp index 91e3d6b5d..ba866834f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -328,6 +328,7 @@ bool messagePlayer(int player, Uint32 type, char const * const message, ...) va_end( argptr ); strncpy(str, messageSanitizePercentSign(str, nullptr).c_str(), Player::MessageZone_t::ADD_MESSAGE_BUFFER_LENGTH - 1); + str[Player::MessageZone_t::ADD_MESSAGE_BUFFER_LENGTH - 1] = '\0'; return messagePlayerColor(player, type, 0xFFFFFFFF, str); } diff --git a/src/scores.cpp b/src/scores.cpp index e6a09b585..7a02c6373 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -1072,6 +1072,21 @@ void loadAllScores(const std::string& scoresfilename) } } } + else if ( versionNumber < 422 ) + { + // legacy nummonsters + for ( int c = 0; c < NUMMONSTERS; c++ ) + { + if ( c < 37 ) + { + fp->read(&score->kills[c], sizeof(Sint32), 1); + } + else + { + score->kills[c] = 0; + } + } + } else { for ( int c = 0; c < NUMMONSTERS; c++ ) @@ -6641,6 +6656,10 @@ int loadGame(int player, const SaveGameInfo& info) { // load player data client_classes[statsPlayer] = info.players[player].char_class; stats[statsPlayer]->playerRace = info.players[player].race; + for ( int c = 0; c < NUMMONSTERS; ++c ) + { + kills[c] = 0; + } for (int c = 0; c < NUMMONSTERS && c < info.players[player].kills.size(); ++c) { kills[c] = info.players[player].kills[c]; } diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index c60355ad8..e35917518 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1322,6 +1322,39 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->GOLD = 0; stats->RANDOM_GOLD = 0; break; + case 189: + case (1000 + BUGBEAR): + stats->type = BUGBEAR; + stats->sex = static_cast(local_rng.rand() % 2); + stats->appearance = local_rng.rand(); + stats->inventory.first = NULL; + stats->inventory.last = NULL; + stats->HP = 130; + stats->RANDOM_HP = 20; + stats->MAXHP = stats->HP; + stats->RANDOM_MAXHP = stats->RANDOM_HP; + stats->MP = 30; + stats->MAXMP = 30; + stats->OLDHP = stats->HP; + stats->STR = 12; + stats->DEX = 3; + stats->CON = 5; + stats->INT = -4; + stats->PER = 6; + stats->CHR = -4; + stats->EXP = 0; + stats->LVL = 14; + stats->GOLD = 0; + stats->HUNGER = 900; + + stats->setProficiency(PRO_SWORD, 60); + stats->setProficiency(PRO_AXE, 60); + stats->setProficiency(PRO_RANGED, 0); + stats->setProficiency(PRO_SHIELD, 80); + + stats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 1; + break; case 10: default: break; From 5fb6a975dce60064ffe3e4927226d62ac16c8507 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:48:41 +1000 Subject: [PATCH 147/244] * fix crash to unsanitised player names in lobby chat/callouts/npc dialogue * fix minotaur warning not showing on underworld 7 to swamp 7 --- src/actmonster.cpp | 8 ++++---- src/interface/interface.cpp | 29 +++++++++++++++++++++++------ src/ui/GameUI.cpp | 9 ++++++++- src/ui/GameUI.hpp | 1 + src/ui/MainMenu.cpp | 26 +++++++++++++++++++++----- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index ca6fd3f95..3aa8bfb8d 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1389,7 +1389,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], { //messagePlayer(monsterclicked, MESSAGE_INTERACTION | MESSAGE_WORLD, Language::get(534), namesays); players[monsterclicked]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(), - Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(4262 + (int)myStats->type), stats[monsterclicked]->name); + Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(getMonsterInteractGreeting(*myStats)), stats[monsterclicked]->name); } } else @@ -1405,7 +1405,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], { //messagePlayer(monsterclicked, MESSAGE_INTERACTION | MESSAGE_WORLD, Language::get(534), namesays); players[monsterclicked]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(), - Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(4262 + (int)myStats->type), stats[monsterclicked]->name); + Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(getMonsterInteractGreeting(*myStats)), stats[monsterclicked]->name); } if ( my->checkFriend(players[monsterclicked]->entity) ) @@ -1772,7 +1772,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], { //messagePlayer(monsterclicked, MESSAGE_INTERACTION | MESSAGE_WORLD, Language::get(534), namesays); players[monsterclicked]->worldUI.worldTooltipDialogue.createDialogueTooltip(my->getUID(), - Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(4262 + (int)myStats->type), stats[monsterclicked]->name); + Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC, Language::get(getMonsterInteractGreeting(*myStats)), stats[monsterclicked]->name); } return false; @@ -11797,7 +11797,7 @@ void Entity::handleNPCInteractDialogue(Stat& myStats, AllyNPCChatter event) if ( local_rng.getU8() % 8 == 0 ) { players[monsterAllyIndex]->worldUI.worldTooltipDialogue.createDialogueTooltip(getUID(), - Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD, Language::get(4262 + (int)myStats.type), stats[monsterAllyIndex]->name); + Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD, Language::get(getMonsterInteractGreeting(myStats)), stats[monsterAllyIndex]->name); //messagePlayerMonsterEvent(monsterAllyIndex, 0xFFFFFFFF, // myStats, Language::get(3129), Language::get(3130), MSG_COMBAT); } diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index a5d055784..4cf5aeb11 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -22401,7 +22401,9 @@ std::string CalloutRadialMenu::getCalloutMessage(const IconEntry::IconEntryText_ { char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); - snprintf(buf, sizeof(buf), text_map.worldMsgEmote.c_str(), shortname, object); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); + snprintf(buf, sizeof(buf), text_map.worldMsgEmote.c_str(), nameStr.c_str(), object); } } else @@ -22415,7 +22417,9 @@ std::string CalloutRadialMenu::getCalloutMessage(const IconEntry::IconEntryText_ { char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); - snprintf(buf, sizeof(buf), text_map.worldMsgEmote.c_str(), shortname); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); + snprintf(buf, sizeof(buf), text_map.worldMsgEmote.c_str(), nameStr.c_str()); } } return buf; @@ -22435,6 +22439,7 @@ std::string CalloutRadialMenu::getCalloutMessage(const IconEntry::IconEntryText_ char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); std::string playerSays = shortname; + playerSays = messageSanitizePercentSign(playerSays, nullptr); playerSays += ": "; snprintf(buf, sizeof(buf), text_map.worldMsgSays.c_str(), playerSays.c_str(), object); } @@ -22451,6 +22456,7 @@ std::string CalloutRadialMenu::getCalloutMessage(const IconEntry::IconEntryText_ char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); std::string playerSays = shortname; + playerSays = messageSanitizePercentSign(playerSays, nullptr); playerSays += ": "; snprintf(buf, sizeof(buf), text_map.worldMsgSays.c_str(), playerSays.c_str()); } @@ -22566,6 +22572,7 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName char shortname[32]; stringCopy(shortname, stats[toPlayer]->name, sizeof(shortname), 22); targetPlayerName = shortname; + targetPlayerName = messageSanitizePercentSign(targetPlayerName, nullptr); } auto& textMap = findIcon->second.text_map[key]; @@ -22600,8 +22607,10 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName { char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); char buf[128]; - snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), shortname); + snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), nameStr.c_str()); return buf; } else @@ -22845,6 +22854,7 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName char shortname[32]; stringCopy(shortname, stats[entity->skill[2]]->name, sizeof(shortname), 22); targetPlayerName = shortname; + targetPlayerName = messageSanitizePercentSign(targetPlayerName, nullptr); } key = "player_wave"; @@ -22881,8 +22891,10 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName { char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); char buf[128]; - snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), shortname); + snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), nameStr.c_str()); return buf; } else @@ -22901,6 +22913,7 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName char shortname[32]; stringCopy(shortname, stats[entity->skill[2]]->name, sizeof(shortname), 22); targetPlayerName = shortname; + targetPlayerName = messageSanitizePercentSign(targetPlayerName, nullptr); } if ( cmd == CALLOUT_CMD_THANKS ) @@ -22944,8 +22957,10 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName { char shortname[32]; stringCopy(shortname, stats[getPlayer()]->name, sizeof(shortname), 22); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); char buf[128]; - snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), shortname); + snprintf(buf, sizeof(buf), textMap.worldMsgEmoteToYou.c_str(), nameStr.c_str()); return buf; } else @@ -26312,7 +26327,9 @@ bool CalloutRadialMenu::allowedInteractEntity(Entity& selectedEntity, bool updat { char shortname[32]; stringCopy(shortname, stats[playernum]->name, sizeof(shortname), 22); - strcat(interactText, shortname); + std::string nameStr = shortname; + nameStr = messageSanitizePercentSign(nameStr, nullptr); + strcat(interactText, nameStr.c_str()); } } } diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 615228f33..8276c263b 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -36739,12 +36739,13 @@ void Player::HUD_t::updateMinotaurWarning() ++m.animTicks; } - bool newLevel = m.levelProcessed != currentlevel; + bool newLevel = m.levelProcessed != currentlevel || m.secretlevelProcessed != secretlevel; if ( !minotaurlevel || (newLevel && m.started) ) { m.deinit(); } m.levelProcessed = currentlevel; + m.secretlevelProcessed = secretlevel; if ( !m.started ) { @@ -37946,6 +37947,9 @@ void Player::WorldUI_t::WorldTooltipDialogue_t::createDialogueTooltip(Uint32 uid vsnprintf(buf, sizeof(buf), message, argptr); va_end(argptr); + strncpy(buf, messageSanitizePercentSign(buf, nullptr).c_str(), sizeof(buf) - 1); + buf[1023] = '\0'; + strcpy((char*)net_packet->data, "BUBL"); SDLNet_Write32(uid, &net_packet->data[4]); net_packet->data[8] = Uint8(type); @@ -37990,6 +37994,9 @@ void Player::WorldUI_t::WorldTooltipDialogue_t::createDialogueTooltip(Uint32 uid vsnprintf(buf, sizeof(buf), message, argptr); va_end(argptr); + strncpy(buf, messageSanitizePercentSign(buf, nullptr).c_str(), sizeof(buf) - 1); + buf[1023] = '\0'; + if (player.playernum == clientnum) { messagePlayer(player.playernum, MESSAGE_CHATTER, buf); diff --git a/src/ui/GameUI.hpp b/src/ui/GameUI.hpp index 9d6b7e220..ee6595ab7 100644 --- a/src/ui/GameUI.hpp +++ b/src/ui/GameUI.hpp @@ -360,6 +360,7 @@ struct MinotaurWarning_t bool minotaurSpawned = false; bool minotaurDied = false; int levelProcessed = 0; + bool secretlevelProcessed = false; void setAnimatePosition(const int destx, const int desty, const int destw, const int desth); void setAnimatePosition(int destx, int desty); void init(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index dcf19310d..2c7c96dcd 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -11033,6 +11033,12 @@ namespace MainMenu { return; } + std::string msgStr = msg; + int numCharsAdded = 0; + msgStr = messageSanitizePercentSign(msgStr, &numCharsAdded); + msg = msgStr.c_str(); + len += numCharsAdded; + memcpy((char*)net_packet->data, "CMSG", 4); SDLNet_Write32(color, &net_packet->data[4]); stringCopy((char*)net_packet->data + 8, msg, 256, len); @@ -16854,20 +16860,30 @@ namespace MainMenu { name_field->setWidgetBack("back_button"); name_field->setWidgetRight("randomize_name"); name_field->setWidgetDown("game_settings"); - static auto name_field_fn = [](const char* text, int index) { + static auto name_field_fn = [](Field* field, const char* text, const int index) { + if ( field && !text ) + { + text = field->getText(); + } + //std::string nameStr = text; + //nameStr = messageSanitizePercentSign(nameStr, nullptr); + //text = nameStr.c_str(); + size_t old_len = std::min(sizeof(Stat::name), strlen(stats[index]->name) + 1); size_t new_len = strlen(text) + 1; size_t shortest_len = std::min(old_len, new_len); + if (new_len != old_len || memcmp(stats[index]->name, text, shortest_len)) { + memset(stats[index]->name, 0, sizeof(Stat::name)); memcpy(stats[index]->name, text, new_len); sendPlayerOverNet(); saveLastCharacter(index, multiplayer); } }; - name_field->setCallback([](Field& field){name_field_fn(field.getText(), field.getOwner());}); + name_field->setCallback([](Field& field) {name_field_fn(&field, nullptr, field.getOwner()); }); name_field->setTickCallback([](Widget& widget){ Field* field = static_cast(&widget); - name_field_fn(field->getText(), field->getOwner()); + name_field_fn(field, nullptr, field->getOwner()); // rescue this player's focus if (!main_menu_frame) { @@ -16906,7 +16922,7 @@ namespace MainMenu { } choice = choice % names.size(); auto name = names[choice].c_str(); - name_field_fn(name, index); + name_field_fn(nullptr, name, index); auto card = static_cast(button.getParent()); auto field = card->findField("name"); assert(field); field->setText(name); @@ -16915,7 +16931,7 @@ namespace MainMenu { randomPlayerNamesMale : randomPlayerNamesFemale; auto choice = RNG.uniform(0, (int)names.size() - 1); auto name = names[choice].c_str(); - name_field_fn(name, index); + name_field_fn(nullptr, name, index); auto card = static_cast(button.getParent()); auto field = card->findField("name"); assert(field); field->setText(name); From ab6cc3dba956cf707784b4cbc47515f2fbf2be57 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:49:24 +1000 Subject: [PATCH 148/244] * fix ally rotating crazy when backing up with range weapon --- src/entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index a0fb3c335..9a403d316 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -18503,7 +18503,7 @@ bool Entity::backupWithRangedWeapon(Stat& myStats, int dist, int hasrangedweapon { if ( target->behavior == &actMonster && target->monsterTarget == getUID() ) // my target is attacking me { - if ( entityDist(this, target) < TOUCHRANGE * 2 ) + if ( dist < TOUCHRANGE * 2 ) { return true; } From f7736cecc41c7b45c3fc71e650881ddc2dcd6837 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:50:22 +1000 Subject: [PATCH 149/244] * bats add some minor STR/DEX scaling, fix miss chance being backwards --- src/collision.cpp | 4 ++-- src/entity.cpp | 4 ++-- src/monster_bat.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 15b1a689b..a90644ba4 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -554,11 +554,11 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) } else if ( flanking ) { - miss = local_rng.rand() % 10 < 6 + (accuracyBonus ? 3 : 0); + miss = local_rng.rand() % 10 < (4 + (accuracyBonus ? -2 : 0)); } else { - miss = local_rng.rand() % 10 < 4 + (accuracyBonus ? 3 : 0); + miss = local_rng.rand() % 10 < (6 + (accuracyBonus ? -2 : 0)); } if ( miss ) diff --git a/src/entity.cpp b/src/entity.cpp index 9a403d316..7a5abea3d 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7808,11 +7808,11 @@ void Entity::attack(int pose, int charge, Entity* target) } else if ( flanking ) { - miss = local_rng.rand() % 10 < 6; + miss = local_rng.rand() % 10 < 4; } else { - miss = local_rng.rand() % 10 < 4; + miss = local_rng.rand() % 10 < 6; } if ( myStats->weapon ) diff --git a/src/monster_bat.cpp b/src/monster_bat.cpp index 3be78328f..a85c229d7 100644 --- a/src/monster_bat.cpp +++ b/src/monster_bat.cpp @@ -51,6 +51,12 @@ void initBat(Entity* my, Stat* myStats) myStats->leader_uid = 0; } + if ( isMonsterStatsDefault(*myStats) ) + { + myStats->STR += std::min(5, currentlevel / 5); + myStats->DEX += std::min(3, currentlevel / 5); + } + // apply random stat increases if set in stat_shared.cpp or editor setRandomMonsterStats(myStats, rng); From ab4a1ab5c24425e17b6fa0b6df4f51344f7f3161 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:51:43 +1000 Subject: [PATCH 150/244] * vampires 2x slime water dmg, rearrange a equip degrade func check --- src/magic/actmagic.cpp | 51 +++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 2a2aa4ed9..749f0d4ef 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -1174,6 +1174,27 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } + // Only degrade the equipment if Friendly Fire is ON or if it is (OFF && target is an enemy) + bool bShouldEquipmentDegrade = false; + if ( parent && parent->behavior == &actDeathGhost ) + { + bShouldEquipmentDegrade = false; + } + else if ( (svFlags & SV_FLAG_FRIENDLYFIRE) ) + { + // Friendly Fire is ON, equipment should always degrade, as hit will register + bShouldEquipmentDegrade = true; + } + else + { + // Friendly Fire is OFF, is the target an enemy? + if ( parent != nullptr && (parent->checkFriend(hit.entity)) == false ) + { + // Target is an enemy, equipment should degrade + bShouldEquipmentDegrade = true; + } + } + // Handling reflecting the missile if ( reflection ) { @@ -1250,27 +1271,6 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. ++my->actmagicReflectionCount; } - // Only degrade the equipment if Friendly Fire is ON or if it is (OFF && target is an enemy) - bool bShouldEquipmentDegrade = false; - if ( parent && parent->behavior == &actDeathGhost ) - { - bShouldEquipmentDegrade = false; - } - else if ( (svFlags & SV_FLAG_FRIENDLYFIRE) ) - { - // Friendly Fire is ON, equipment should always degrade, as hit will register - bShouldEquipmentDegrade = true; - } - else - { - // Friendly Fire is OFF, is the target an enemy? - if ( parent != nullptr && (parent->checkFriend(hit.entity)) == false ) - { - // Target is an enemy, equipment should degrade - bShouldEquipmentDegrade = true; - } - } - if ( bShouldEquipmentDegrade ) { // Reflection of 3 does not degrade equipment @@ -3025,6 +3025,10 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. playSoundEntity(hit.entity, 28, volume); int damage = element->damage; damage += (spellbookDamageBonus * damage); + if ( spell->ID == SPELL_SLIME_WATER && hitstats->type == VAMPIRE ) + { + damage *= 2; + } //damage += ((element->mana - element->base_mana) / static_cast(element->overload_multiplier)) * element->damage; int oldHP = hitstats->HP; @@ -3299,6 +3303,11 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { Uint32 color = makeColorRGB(255, 0, 0); messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + + if ( hitstats->type == VAMPIRE ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + } } } } From 2996d2c48b98b067a0c681c7f9b4c486205c2524 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:55:21 +1000 Subject: [PATCH 151/244] * bugbear atk logic * arbalest on npcs has 1.5x atk speed * bat no award leadership on kill --- src/actmonster.cpp | 584 +++++++++++++++++++++++++++++++++------------ src/entity.cpp | 162 +++++++++++-- 2 files changed, 562 insertions(+), 184 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 3aa8bfb8d..efc6ebad6 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -35,85 +35,87 @@ float limbs[NUMMONSTERS][20][3]; // determines which monsters fight which bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING - { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // HUMAN - { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // RAT - { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GOBLIN - { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // SLIME - { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // TROLL - { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // BAT_SMALL - { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SPIDER - { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // GHOUL - { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SKELETON - { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCORPION - { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // IMP - { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // CRAB - { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // GNOME - { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 }, // DEMON - { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // SUCCUBUS - { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // MIMIC - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // LICH - { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 }, // MINOTAUR - { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // DEVIL - { 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER - { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1 }, // KOBOLD - { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1 }, // SCARAB - { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1 }, // CRYSTALGOLEM - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // INCUBUS - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // VAMPIRE - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // SHADOW - { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // COCKATRICE - { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // INSECTOID - { 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GOATMAN - { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // AUTOMATON - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_ICE - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // LICH_FIRE - { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // SENTRYBOT - { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 }, // SPELLBOT - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // DUMMYBOT + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING + { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1 }, // HUMAN + { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // RAT + { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GOBLIN + { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // SLIME + { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // TROLL + { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // BAT_SMALL + { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPIDER + { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // GHOUL + { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SKELETON + { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCORPION + { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // IMP + { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // CRAB + { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GNOME + { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DEMON + { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SUCCUBUS + { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MIMIC + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // LICH + { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MINOTAUR + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // DEVIL + { 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER + { 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // KOBOLD + { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCARAB + { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // CRYSTALGOLEM + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // INCUBUS + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // VAMPIRE + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SHADOW + { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // COCKATRICE + { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // INSECTOID + { 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // GOATMAN + { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // AUTOMATON + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // LICH_ICE + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // LICH_FIRE + { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SENTRYBOT + { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SPELLBOT + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT + { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 } // BUGBEAR }; // determines which monsters come to the aid of other monsters bool monsterally[NUMMONSTERS][NUMMONSTERS] = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // HUMAN - { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT - { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // GOBLIN - { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // SLIME - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL - { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL - { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER - { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 }, // GHOUL - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // SKELETON - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // IMP - { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRAB - { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // DEMON - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // SUCCUBUS - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0 }, // DEVIL - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER - { 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD - { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB - { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // INCUBUS - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // VAMPIRE - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // SHADOW - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE - { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // GOATMAN - { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // AUTOMATON - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH_ICE - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // LICH_FIRE - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // SENTRYBOT - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // SPELLBOT - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 }, // GYROBOT - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 } // DUMMYBOT + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // HUMAN + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // GOBLIN + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // SLIME + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER + { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, // GHOUL + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SKELETON + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCORPION + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // IMP + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRAB + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GNOME + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // DEMON + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SUCCUBUS + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MIMIC + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // MINOTAUR + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // DEVIL + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SHOPKEEPER + { 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // KOBOLD + { 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SCARAB + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // CRYSTALGOLEM + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // INCUBUS + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // VAMPIRE + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // SHADOW + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // COCKATRICE + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // INSECTOID + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // GOATMAN + { 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // AUTOMATON + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH_ICE + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // LICH_FIRE + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // SENTRYBOT + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPELLBOT + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // GYROBOT + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DUMMYBOT + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // BUGBEAR }; // monster sight ranges @@ -155,7 +157,8 @@ double sightranges[NUMMONSTERS] = 256, // SENTRYBOT 192, // SPELLBOT 256, // GYROBOT - 32 // DUMMYBOT + 32, // DUMMYBOT + 128 // BUGBEAR }; int monsterGlobalAnimationMultiplier = 10; @@ -163,7 +166,11 @@ int monsterGlobalAttackTimeMultiplier = 1; std::string getMonsterLocalizedName(Monster creature) { - if ( creature < KOBOLD ) + if ( creature == BUGBEAR ) + { + return Language::get(6256); + } + else if ( creature < KOBOLD ) { if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) { return Language::get(102); @@ -180,6 +187,10 @@ std::string getMonsterLocalizedName(Monster creature) std::string getMonsterLocalizedPlural(Monster creature) { + if ( creature == BUGBEAR ) + { + return Language::get(6257); + } if ( creature < KOBOLD ) { if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter))) { @@ -196,6 +207,10 @@ std::string getMonsterLocalizedPlural(Monster creature) } std::string getMonsterLocalizedInjury(Monster creature) { + if ( creature == BUGBEAR ) + { + return Language::get(6258); + } if ( creature < KOBOLD ) { if (creature == SPIDER && ((!intro && arachnophobia_filter) || (intro && MainMenu::arachnophobia_filter)) ) { @@ -1339,6 +1354,22 @@ bool monsterMoveAside(Entity* my, Entity* entity, bool ignoreMonsterState) return false; } +int getMonsterInteractGreeting(Stat& myStats) +{ + if ( myStats.type == BUGBEAR ) + { + return 6259; + } + else if ( myStats.type < BUGBEAR ) + { + return 4262 + myStats.type; + } + else + { + return 4288; // ... + } +} + /*------------------------------------------------------------------------------- act* @@ -2300,6 +2331,7 @@ void monsterAnimate(Entity* my, Stat* myStats, double dist) case DUMMYBOT: dummyBotAnimate(my, myStats, dist); break; case MIMIC: mimicAnimate(my, myStats, dist); break; case BAT_SMALL: batAnimate(my, myStats, dist); break; + case BUGBEAR: bugbearMoveBodyparts(my, myStats, dist); break; default: break; } @@ -2392,6 +2424,7 @@ void actMonster(Entity* my) case DUMMYBOT: initDummyBot(my, nullptr); break; case MIMIC: initMimic(my, nullptr); break; case BAT_SMALL: initBat(my, nullptr); break; + case BUGBEAR: initBugbear(my, nullptr); break; default: printlog("Unknown monster, can't init!"); break; } } @@ -2486,6 +2519,7 @@ void actMonster(Entity* my) case DUMMYBOT: initDummyBot(my, myStats); break; case MIMIC: initMimic(my, myStats); break; case BAT_SMALL: initBat(my, myStats); break; + case BUGBEAR: initBugbear(my, myStats); break; default: break; //This should never be reached. } } @@ -3608,6 +3642,10 @@ void actMonster(Entity* my) break; case BAT_SMALL: batDie(my); + break; + case BUGBEAR: + bugbearDie(my); + break; default: break; //This should never be reached. } @@ -3843,6 +3881,7 @@ void actMonster(Entity* my) case GYROBOT: case DUMMYBOT: case MIMIC: + case BUGBEAR: handleinvisible = false; break; default: @@ -4322,19 +4361,17 @@ void actMonster(Entity* my) { continue; } - bool entityInside = entityInsideEntity(my, entity); - /*if ( my->getRace() == BAT_SMALL && entity->getRace() == BAT_SMALL ) - { - int x1 = my->sizex; - int y1 = my->sizey; - my->sizex = 2; - my->sizey = 2; - int x2 = entity->sizex; - int y2 = entity->sizey; - entity->sizex = 2; - entity->sizey = 2; - }*/ + int sizex = my->sizex; + int sizey = my->sizey; + if ( entity->getRace() == BUGBEAR && myStats->type == BUGBEAR ) + { + my->sizex = 8; + my->sizey = 8; + } + bool entityInside = entityInsideEntity(my, entity); + my->sizex = sizex; + my->sizey = sizey; if ( entityInside && entity->getRace() != GYROBOT ) { if ( entity->behavior != &actDoorFrame ) @@ -4544,35 +4581,35 @@ void actMonster(Entity* my) messagePlayer(0, "defending!"); }*/ - //if ( myStats->type == DEVIL ) + //if ( myStats->type == BUGBEAR ) //{ - //std::string state_string; - // - //switch(my->monsterState) - //{ - //case MONSTER_STATE_WAIT: - // state_string = "WAIT"; - // break; - //case MONSTER_STATE_ATTACK: - // state_string = "CHARGE"; - // break; - //case MONSTER_STATE_PATH: - // state_string = "PATH"; - // break; - //case MONSTER_STATE_HUNT: - // state_string = "HUNT"; - // break; - //case MONSTER_STATE_TALK: - // state_string = "TALK"; - // break; - //default: - // state_string = std::to_string(my->monsterState); - // //state_string = "Unknown state"; - // break; - //} - // - //messagePlayer(0, MESSAGE_DEBUG, "%s, ATK: %d hittime:%d, atktime:%d, (%d|%d), timer:%d", - // state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message. + // std::string state_string; + // + // switch(my->monsterState) + // { + // case MONSTER_STATE_WAIT: + // state_string = "WAIT"; + // break; + // case MONSTER_STATE_ATTACK: + // state_string = "CHARGE"; + // break; + // case MONSTER_STATE_PATH: + // state_string = "PATH"; + // break; + // case MONSTER_STATE_HUNT: + // state_string = "HUNT"; + // break; + // case MONSTER_STATE_TALK: + // state_string = "TALK"; + // break; + // default: + // state_string = std::to_string(my->monsterState); + // //state_string = "Unknown state"; + // break; + // } + // + // messagePlayer(0, MESSAGE_DEBUG, "%s, ATK: %d hittime:%d, atktime:%d, (%d|%d), timer:%d", + // state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message. //} //Begin state machine @@ -5507,7 +5544,7 @@ void actMonster(Entity* my) } Entity* tempHitEntity = hit.entity; - if ( lineTrace(my, my->x, my->x, tangent2, TOUCHRANGE, 1, false) < TOUCHRANGE ) + if ( lineTrace(my, my->x, my->y, tangent2, TOUCHRANGE, 1, false) < TOUCHRANGE ) { MONSTER_FLIPPEDANGLE = (MONSTER_FLIPPEDANGLE < 5) * 10; goAgain++; @@ -5552,6 +5589,12 @@ void actMonster(Entity* my) } } + int chaseRange = 16; + if ( myStats->type == BUGBEAR ) + { + chaseRange = 20; + } + if ( monsterIsImmobileTurret(my, myStats) || myStats->EFFECTS[EFF_ROOTED] ) { // this is just so that the monster rotates. it doesn't actually move @@ -5559,7 +5602,7 @@ void actMonster(Entity* my) MONSTER_VELY = maxVelY * 0.01; } else if ( !myStats->EFFECTS[EFF_KNOCKBACK] && - ((dist > 16 && !hasrangedweapon && !my->shouldRetreat(*myStats)) + ((dist > chaseRange && !hasrangedweapon && !my->shouldRetreat(*myStats)) || (hasrangedweapon && dist > rangedWeaponDistance)) ) { if ( my->shouldRetreat(*myStats) ) @@ -5570,6 +5613,30 @@ void actMonster(Entity* my) } else { + if ( myStats->type == BUGBEAR && my->monsterStrafeDirection != 0 ) + { + double strafeTangent = tangent2; + if ( dist < 24 ) + { + // move diagonally + strafeTangent -= ((PI / 4) * my->monsterStrafeDirection); + } + else + { + // move sideways (dist between 64 and 100 from backupWithRangedWeapon) + strafeTangent -= ((PI / 2) * my->monsterStrafeDirection); + } + if ( ticks % TICKS_PER_SECOND == 0 && my->monsterStrafeDirection != 0 && local_rng.rand() % 10 == 0 ) + { + my->setBugbearStrafeDir(true); + //my->monsterStrafeDirection *= -1; + } + real_t maxVelX = cos(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + real_t maxVelY = sin(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + MONSTER_VELX = maxVelX; + MONSTER_VELY = maxVelY; + } + dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my); } if ( hit.entity != NULL ) @@ -5774,6 +5841,30 @@ void actMonster(Entity* my) MONSTER_VELX = maxVelX; MONSTER_VELY = maxVelY; + if ( myStats->type == BUGBEAR && my->monsterStrafeDirection != 0 ) + { + double strafeTangent = tangent2; + if ( dist < 24 ) + { + // move diagonally + strafeTangent -= ((PI / 4) * my->monsterStrafeDirection); + } + else + { + // move sideways (dist between 64 and 100 from backupWithRangedWeapon) + strafeTangent -= ((PI / 2) * my->monsterStrafeDirection); + } + if ( ticks % TICKS_PER_SECOND == 0 && my->monsterStrafeDirection != 0 && local_rng.rand() % 10 == 0 ) + { + my->setBugbearStrafeDir(true); + //my->monsterStrafeDirection *= -1; + } + real_t maxVelX = cos(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + real_t maxVelY = sin(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + MONSTER_VELX = maxVelX; + MONSTER_VELY = maxVelY; + } + if ( my->backupWithRangedWeapon(*myStats, dist, hasrangedweapon) && wasInsideEntity ) { @@ -5787,13 +5878,45 @@ void actMonster(Entity* my) } else { - // this is just so that the monster rotates. it doesn't actually move - int myDex = my->monsterGetDexterityForMovement(); - MONSTER_VELX = cos(tangent) * .02 * .045 * (myDex + 10) * weightratio; - MONSTER_VELY = sin(tangent) * .02 * .045 * (myDex + 10) * weightratio; + if ( myStats->type == BUGBEAR && my->monsterStrafeDirection != 0 ) + { + double strafeTangent = tangent2; + if ( dist < 24 ) + { + // move diagonally + strafeTangent -= ((PI / 4) * my->monsterStrafeDirection); + } + else + { + // move sideways (dist between 64 and 100 from backupWithRangedWeapon) + strafeTangent -= ((PI / 2) * my->monsterStrafeDirection); + } + if ( ticks % TICKS_PER_SECOND == 0 && my->monsterStrafeDirection != 0 && local_rng.rand() % 10 == 0 ) + { + my->setBugbearStrafeDir(true); + //my->monsterStrafeDirection *= -1; + } + real_t maxVelX = cos(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + real_t maxVelY = sin(strafeTangent) * .045 * (myDex + 10) * weightratio * -.5; + MONSTER_VELX = maxVelX; + MONSTER_VELY = maxVelY; + + dist2 = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my); + } + else + { + // this is just so that the monster rotates. it doesn't actually move + int myDex = my->monsterGetDexterityForMovement(); + MONSTER_VELX = cos(tangent) * .02 * .045 * (myDex + 10) * weightratio; + MONSTER_VELY = sin(tangent) * .02 * .045 * (myDex + 10) * weightratio; + } } } + int previousStrafeDirection = my->monsterStrafeDirection; + bool previousBackupWithRangedWeapon = my->backupWithRangedWeapon(*myStats, dist, hasrangedweapon); + bool previousShouldRetreat = my->shouldRetreat(*myStats); + my->handleMonsterAttack(myStats, entity, dist); // bust ceilings @@ -5807,7 +5930,14 @@ void actMonster(Entity* my) hasrangedweapon = my->hasRangedWeapon(); // re-update this status check if weapon was consumed/stowed away // rotate monster - if ( my->backupWithRangedWeapon(*myStats, dist, hasrangedweapon) || my->shouldRetreat(*myStats) ) + if ( myStats->type == BUGBEAR && previousStrafeDirection != 0 ) + { + real_t tempVelX = cos(tangent2) * .045 * (myDex + 10) * weightratio; + real_t tempVelY = sin(tangent2) * .045 * (myDex + 10) * weightratio; + // override if we're strafing, keep facing the target + dir = my->yaw - atan2(tempVelY, tempVelX); + } + else if ( previousBackupWithRangedWeapon || previousShouldRetreat ) { int myDex = my->getDEX(); if ( my->monsterAllyGetPlayerLeader() ) @@ -5882,6 +6012,7 @@ void actMonster(Entity* my) else { my->yaw -= dir / 2; + //messagePlayer(0, MESSAGE_DEBUG, "yaw: %.2f dir: %.2f, velx: %.2f vely: %.2f", my->yaw, dir / 2, my->vel_x, my->vel_y); } while ( my->yaw < 0 ) { @@ -8256,6 +8387,109 @@ void actMonster(Entity* my) } } } + else if ( my->monsterState == MONSTER_STATE_GENERIC_DODGE ) + { + dist = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my); + + Entity* target = uidToEntity(my->monsterTarget); + if ( dist != sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY) ) + { + my->monsterSpecialTimer = 0; // hit obstacle + } + + MONSTER_VELX *= 0.95; + MONSTER_VELY *= 0.95; + + if ( my->monsterSpecialTimer == 0 ) + { + my->monsterState = MONSTER_STATE_WAIT; + MONSTER_VELX = 0; + MONSTER_VELY = 0; + if ( target ) + { + my->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH); + my->monsterHitTime = HITRATE * 2; + if ( sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2)) < STRIKERANGE ) + { + if ( local_rng.rand() % 2 == 0 ) + { + my->handleMonsterAttack(myStats, target, 0.f); + } + else + { + my->monsterHitTime = 25; + } + } + } + } + } + else if ( my->monsterState == MONSTER_STATE_GENERIC_CHARGE ) + { + Entity* target = uidToEntity(my->monsterTarget); + + real_t tangent = my->yaw; + if ( target ) + { + real_t tangent2 = atan2(target->y - my->y, target->x - my->x); + real_t dist = lineTraceTarget(my, my->x, my->y, tangent2, sightranges[myStats->type], 0, false, target); + if ( hit.entity == target ) + { + tangent = tangent2; + } + } + + dir = my->yaw - atan2(sin(tangent), cos(tangent)); + while ( dir >= PI ) + { + dir -= PI * 2; + } + while ( dir < -PI ) + { + dir += PI * 2; + } + my->yaw -= dir / 64; + while ( my->yaw < 0 ) + { + my->yaw += 2 * PI; + } + while ( my->yaw >= 2 * PI ) + { + my->yaw -= 2 * PI; + } + + int myDex = my->monsterGetDexterityForMovement() + 30; + real_t maxVelX = cos(my->yaw) * .045 * (myDex + 10) * weightratio; + real_t maxVelY = sin(my->yaw) * .045 * (myDex + 10) * weightratio; + MONSTER_VELX = maxVelX; + MONSTER_VELY = maxVelY; + + dist = clipMove(&my->x, &my->y, MONSTER_VELX, MONSTER_VELY, my); + + bool stopPath = false; + if ( dist != sqrt(MONSTER_VELX * MONSTER_VELX + MONSTER_VELY * MONSTER_VELY) ) + { + my->monsterSpecialTimer = 0; // hit obstacle + } + + if ( my->monsterSpecialTimer == 0 ) + { + my->monsterState = MONSTER_STATE_WAIT; + MONSTER_VELX = 0; + MONSTER_VELY = 0; + if ( target ) + { + my->monsterHitTime = HITRATE * 2; + if ( sqrt(pow(my->x - target->x, 2) + pow(my->y - target->y, 2)) < TOUCHRANGE ) + { + my->monsterAcquireAttackTarget(*target, MONSTER_STATE_ATTACK); + } + else + { + my->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH); + } + } + } + } else if ( my->monsterState == MONSTER_STATE_LICHFIRE_DODGE || my->monsterState == MONSTER_STATE_LICHICE_DODGE ) { @@ -8973,8 +9207,14 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } } + int meleeDist = STRIKERANGE; + if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + { + meleeDist = TOUCHRANGE - 1; + } + // check the range to the target, depending on ranged weapon or melee. - if ( (dist < STRIKERANGE && !hasrangedweapon) || (hasrangedweapon && dist < getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon)) || lichRangeCheckOverride ) + if ( (dist < meleeDist && !hasrangedweapon) || (hasrangedweapon && dist < getMonsterEffectiveDistanceOfRangedWeapon(myStats->weapon)) || lichRangeCheckOverride ) { // increment the hit time, don't attack until this reaches the hitrate of the weapon this->monsterHitTime++; @@ -9010,6 +9250,10 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } } } + else if ( myStats->weapon->type == HEAVY_CROSSBOW ) + { + bow = 1.5; + } } if ( myStats->type == BAT_SMALL ) { @@ -9096,14 +9340,14 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) { lineTrace(this, this->x, this->y, newTangent, tracedist, 0, false); } - if ( hit.entity != nullptr ) + if ( hit.entity != nullptr && hit.entity == target ) { // found the target in range hitstats = hit.entity->getStats(); if ( hit.entity->behavior == &actMonster && !hasrangedweapon ) { // alert the monster! - if ( hit.entity->skill[0] != MONSTER_STATE_ATTACK ) + if ( hit.entity->monsterState != MONSTER_STATE_ATTACK ) { hit.entity->monsterAcquireAttackTarget(*this, MONSTER_STATE_PATH); } @@ -9149,8 +9393,24 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } } } - - if ( monsterDefend == MONSTER_DEFEND_HOLD ) + + if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + { + if ( myStats->shield && myStats->shield->type == STEEL_SHIELD && local_rng.rand() % 3 == 0 && dist <= meleeDist ) + { + // shield bash + this->attack(MONSTER_POSE_SPECIAL_WINDUP1, charge, nullptr); // attacku! D:< + } + else if ( monsterDefend == MONSTER_DEFEND_HOLD ) + { + monsterHitTime = HITRATE / 2; + } + else + { + this->attack(pose, charge, nullptr); // attacku! D:< + } + } + else if ( monsterDefend == MONSTER_DEFEND_HOLD ) { // skip attack, continue defending. offset the hit time to allow for timing variation. monsterHitTime = HITRATE / 4; @@ -9166,9 +9426,25 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } else { + if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + { + if ( monsterHitTime < HITRATE - 5 ) + { + ++monsterHitTime; + } + } if ( ticks % (90 + getUID() % 10) == 0 ) { - if ( !hasrangedweapon && dist > TOUCHRANGE && target && target->hasRangedWeapon() ) + if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + { + int oldDefend = monsterDefend; + monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon); + if ( oldDefend != monsterDefend ) + { + serverUpdateEntitySkill(this, 47); + } + } + else if ( !hasrangedweapon && dist > TOUCHRANGE && target && target->hasRangedWeapon() ) { int oldDefend = monsterDefend; monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon); @@ -9775,34 +10051,8 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di switch ( myStats->type ) { - //case MIMIC: - // if ( monsterSpecialState == MIMIC_ACTIVE && !myStats->EFFECTS[EFF_MIMIC_LOCKED] ) - // { - // if ( local_rng.rand() % 5 == 0 ) - // { - // int tx = this->x / 16; - // int ty = this->y / 16; - // list_t* itemsList = nullptr; - // for ( int i = -1; i <= 1; ++i ) - // { - // for ( int j = -1; j <= 1; ++j ) - // { - // getItemsOnTile(tx + i, ty + j, &itemsList); //Check the tile the monster is on for items. - // } - // } - - // if ( itemsList ) - // { - // createParticleDropRising(this, 593, 1.f); - // serverSpawnMiscParticles(this, PARTICLE_EFFECT_RISING_DROP, 593); - // this->monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_MIMIC_EAT; - // this->monsterSpecialState = MIMIC_MAGIC; - // serverUpdateEntitySkill(this, 33); - // } - // break; - // } - // } - // break; + case BUGBEAR: + break; case KOBOLD: if ( (hasrangedweapon && !(myStats->weapon && itemCategory(myStats->weapon) == SPELLBOOK)) || myStats->weapon == nullptr ) { @@ -12002,7 +12252,7 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat& return MONSTER_DEFEND_NONE; } - if ( monsterSpecialState > 0 ) + if ( monsterSpecialState > 0 && !(myStats.type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE) ) { return MONSTER_DEFEND_NONE; } @@ -12019,7 +12269,7 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat& bool isPlayerAlly = (monsterAllyIndex >= 0 && monsterAllyIndex < MAXPLAYERS); - if ( !(isPlayerAlly || myStats.type == HUMAN) ) + if ( !(isPlayerAlly || myStats.type == HUMAN || myStats.type == BUGBEAR) ) { return MONSTER_DEFEND_NONE; } @@ -12077,6 +12327,22 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat& } } + if ( myStats.type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + { + if ( targetDist > TOUCHRANGE && !hasrangedweapon ) + { + blockChance = 20; + } + else + { + blockChance = 12; + if ( monsterDefend == MONSTER_DEFEND_NONE ) + { + blockChance = 18; + } + } + } + if ( local_rng.rand() % 20 < blockChance ) { if ( isPlayerAlly || myStats.type == HUMAN ) diff --git a/src/entity.cpp b/src/entity.cpp index 7a5abea3d..38fdfbdbe 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -863,6 +863,12 @@ void Entity::killedByMonsterObituary(Entity* victim) case MIMIC: victim->setObituary(Language::get(2166)); break; + case BAT_SMALL: + victim->setObituary(Language::get(6254)); + break; + case BUGBEAR: + victim->setObituary(Language::get(6255)); + break; default: victim->setObituary(Language::get(1500)); break; @@ -6932,6 +6938,10 @@ void Entity::attack(int pose, int charge, Entity* target) return; // don't execute the attack, let the monster animation call the attack() function again. } + else if ( myStats->type == BUGBEAR && pose == MONSTER_POSE_BUGBEAR_SHIELD ) + { + monsterAttack = pose; + } else if ( myStats->type == VAMPIRE && pose == MONSTER_POSE_VAMPIRE_AURA_CAST ) { monsterAttack = 0; @@ -7067,7 +7077,7 @@ void Entity::attack(int pose, int charge, Entity* target) isIllusion = true; } - if ( myStats->weapon != nullptr + if ( myStats->weapon != nullptr && !(myStats->type == BUGBEAR && pose == MONSTER_POSE_BUGBEAR_SHIELD) && (!shapeshifted || (shapeshifted && myStats->type == CREATURE_IMP && itemCategory(myStats->weapon) == MAGICSTAFF)) ) { // if non-shapeshifted, or you're an imp with a staff then process throwing/magic weapons @@ -8636,6 +8646,10 @@ void Entity::attack(int pose, int charge, Entity* target) { weaponskill = PRO_UNARMED; } + if ( pose == MONSTER_POSE_BUGBEAR_SHIELD ) + { + weaponskill = PRO_UNARMED; + } real_t weaponMultipliers = 0.0; if ( weaponskill == PRO_UNARMED ) @@ -8728,7 +8742,11 @@ void Entity::attack(int pose, int charge, Entity* target) damagePreMultiplier = 2; } - const int myAttack = std::max(0, (Entity::getAttack(this, myStats, behavior == &actPlayer) * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats)); + int myAttack = std::max(0, (Entity::getAttack(this, myStats, behavior == &actPlayer) * damagePreMultiplier) + getBonusAttackOnTarget(*hitstats)); + if ( myStats->type == BUGBEAR && pose == MONSTER_POSE_BUGBEAR_SHIELD ) + { + myAttack += 2 + local_rng.rand() % 3; + } int enemyAC = AC(hitstats); if ( weaponskill == PRO_POLEARM && myStats->weapon && myStats->weapon->type == ARTIFACT_SPEAR && !shapeshifted ) { @@ -8765,6 +8783,10 @@ void Entity::attack(int pose, int charge, Entity* target) if ( weaponskill == PRO_AXE ) { damage++; + if ( myStats->type == BUGBEAR ) + { + damage += 1; + } } if ( myStats->type == LICH_FIRE && !hitstats->defending ) { @@ -9835,6 +9857,40 @@ void Entity::attack(int pose, int charge, Entity* target) } } } + else if ( myStats->type == BUGBEAR && pose == MONSTER_POSE_BUGBEAR_SHIELD ) + { + if ( hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) ) + { + real_t pushbackMultiplier = 0.9; + knockbackInflicted = true; + + real_t tangent = atan2(hit.entity->y - this->y, hit.entity->x - this->x); + if ( hit.entity->behavior == &actMonster ) + { + hit.entity->vel_x = cos(tangent) * pushbackMultiplier; + hit.entity->vel_y = sin(tangent) * pushbackMultiplier; + hit.entity->monsterKnockbackVelocity = 0.01; + hit.entity->monsterKnockbackUID = this->getUID(); + hit.entity->monsterKnockbackTangentDir = tangent; + //hit.entity->lookAtEntity(*parent); + } + else if ( hit.entity->behavior == &actPlayer ) + { + if ( !players[hit.entity->skill[2]]->isLocalPlayer() ) + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = tangent; + serverUpdateEntityFSkill(hit.entity, 11); + serverUpdateEntityFSkill(hit.entity, 9); + } + else + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = tangent; + } + } + } + } else if ( myStats->type == BAT_SMALL ) { if ( !hitstats->EFFECTS[EFF_BLEEDING] ) @@ -11806,7 +11862,6 @@ int AC(Stat* stat) armor += stat->getActiveShieldBonus(true, false); } } - if ( stat->type == MIMIC && stat->EFFECTS[EFF_MIMIC_LOCKED] ) { armor *= 2; @@ -13030,7 +13085,10 @@ void Entity::awardXP(Entity* src, bool share, bool root) } else { - leader->increaseSkill(PRO_LEADERSHIP); + if ( srcStats->type != BAT_SMALL ) + { + leader->increaseSkill(PRO_LEADERSHIP); + } } leader->awardXP(src, true, false); @@ -15076,6 +15134,7 @@ int Entity::getAttackPose() const || myStats->type == HUMAN || myStats->type == GOBLIN || myStats->type == SKELETON || myStats->type == GNOME || myStats->type == SUCCUBUS || myStats->type == SHOPKEEPER + || myStats->type == BUGBEAR || myStats->type == SHADOW ) { if ( myStats->weapon->type == CROSSBOW || myStats->weapon->type == HEAVY_CROSSBOW ) @@ -15118,6 +15177,7 @@ int Entity::getAttackPose() const || myStats->type == HUMAN || myStats->type == GOBLIN || myStats->type == SKELETON || myStats->type == GNOME || myStats->type == SUCCUBUS || myStats->type == SHOPKEEPER + || myStats->type == BUGBEAR || myStats->type == SHADOW ) { if ( getWeaponSkill(myStats->weapon) == PRO_AXE || getWeaponSkill(myStats->weapon) == PRO_MACE @@ -15189,6 +15249,10 @@ int Entity::getAttackPose() const { pose = MONSTER_POSE_MELEE_WINDUP1; } + else if ( myStats->type == BUGBEAR ) + { + pose = MONSTER_POSE_MELEE_WINDUP1; + } else { pose = 1; @@ -15697,7 +15761,7 @@ void Entity::humanoidAnimateWalk(Entity* limb, node_t* bodypartNode, int bodypar if ( shieldNode ) { Entity* shield = (Entity*)shieldNode->element; - if ( dist > 0.1 && (bodypart != LIMB_HUMANOID_LEFTARM || shield->sprite == 0) ) + if ( dist > 0.1 && (bodypart != LIMB_HUMANOID_LEFTARM || shield->sprite <= 0) ) { // walking to destination if ( !rightbody->skill[0] ) @@ -15964,13 +16028,27 @@ void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb) if ( weaponArmLimb->skill[1] < 2 && weaponArmLimb->pitch < PI / 2 ) { // cos(weaponArmLimb->pitch)) * cos(weaponArmLimb->yaw) allows forward/back motion dependent on the arm rotation. - weaponLimb->x = weaponArmLimb->x + (3 * cos(weaponArmLimb->pitch)) * cos(weaponArmLimb->yaw); - weaponLimb->y = weaponArmLimb->y + (3 * cos(weaponArmLimb->pitch)) * sin(weaponArmLimb->yaw); + real_t forward = 3.0; + if ( monsterType == BUGBEAR ) + { + forward = limbs[BUGBEAR][18][0]; + } + + weaponLimb->x = weaponArmLimb->x + (forward * cos(weaponArmLimb->pitch)) * cos(weaponArmLimb->yaw); + weaponLimb->y = weaponArmLimb->y + (forward * cos(weaponArmLimb->pitch)) * sin(weaponArmLimb->yaw); if ( weaponArmLimb->pitch < PI / 3 ) { // adjust the z point halfway through swing. - weaponLimb->z = weaponArmLimb->z + 1.5 - 2 * cos(weaponArmLimb->pitch / 2); + if ( monsterType == BUGBEAR ) + { + weaponLimb->z = weaponArmLimb->z; + weaponLimb->z += limbs[BUGBEAR][18][1] * sin(weaponArmLimb->pitch * 2); + } + else + { + weaponLimb->z = weaponArmLimb->z + 1.5 - 2 * cos(weaponArmLimb->pitch / 2); + } if ( monsterType == INCUBUS || monsterType == SUCCUBUS ) { weaponLimb->z += 2; @@ -15978,7 +16056,15 @@ void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb) } else { - weaponLimb->z = weaponArmLimb->z - .5 * (myAttack == 0); + if ( monsterType == BUGBEAR ) + { + weaponLimb->z = weaponArmLimb->z; + weaponLimb->z += limbs[BUGBEAR][18][2] * sin(weaponArmLimb->pitch * 2); + } + else + { + weaponLimb->z = weaponArmLimb->z - .5 * (myAttack == 0); + } if ( weaponLimb->pitch > PI / 2 ) { limbAnimateToLimit(weaponLimb, ANIMATE_PITCH, -0.5, PI * 0.5, false, 0); @@ -15999,6 +16085,10 @@ void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb) weaponLimb->x = weaponArmLimb->x + .5 * cos(weaponArmLimb->yaw) * (myAttack == 0); weaponLimb->y = weaponArmLimb->y + .5 * sin(weaponArmLimb->yaw) * (myAttack == 0); weaponLimb->z = weaponArmLimb->z - .5; + if ( monsterType == BUGBEAR ) + { + weaponLimb->z = weaponArmLimb->z; + } weaponLimb->pitch = weaponArmLimb->pitch + .25 * (myAttack == 0); if ( monsterType == INCUBUS || monsterType == SUCCUBUS ) { @@ -16012,6 +16102,16 @@ void Entity::handleHumanoidWeaponLimb(Entity* weaponLimb, Entity* weaponArmLimb) weaponLimb->y = weaponArmLimb->y + .5 * sin(weaponArmLimb->yaw) * (myAttack == 0); weaponLimb->z = weaponArmLimb->z - .5 * (myAttack == 0); weaponLimb->pitch = weaponArmLimb->pitch + .25 * (myAttack == 0); + if ( monsterType == BUGBEAR ) + { + if ( !isPlayer && this->monsterArmbended ) + { + weaponLimb->x = weaponArmLimb->x; + weaponLimb->y = weaponArmLimb->y; + weaponLimb->z = weaponArmLimb->z; + weaponLimb->pitch = weaponArmLimb->pitch; + } + } } } } @@ -16866,7 +16966,7 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool bool hadOldTarget = (uidToEntity(monsterTarget) != nullptr); Sint32 oldMonsterState = monsterState; - if ( target.getRace() == GYROBOT || target.isInertMimic() || target.isUntargetableBat() ) + if ( target.getRace() == GYROBOT || target.isInertMimic() || target.isUntargetableBat() || !(target.behavior == &actMonster || target.behavior == &actPlayer) ) { return; } @@ -16947,6 +17047,17 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool return; } } + else if ( myStats->type == BUGBEAR ) + { + Stat* targetStats = target.getStats(); + if ( targetStats && targetStats->type == BUGBEAR ) + { + if ( (targetStats && targetStats->leader_uid == getUID()) || target.parent == getUID() ) + { + return; + } + } + } if ( target.getRace() == INCUBUS ) { @@ -19673,11 +19784,7 @@ void messagePlayerMonsterEvent(int player, Uint32 color, Stat& monsterStats, con { messagePlayerColor(player, MESSAGE_COMBAT, color, msgGeneric, monsterStats.name); } - else if ( monsterType < KOBOLD ) //Original monster count - { - messagePlayerColor(player, MESSAGE_COMBAT, color, msgNamed, monsterStats.name); - } - else if ( monsterType >= KOBOLD ) //New monsters + else { messagePlayerColor(player, MESSAGE_COMBAT, color, msgNamed, monsterStats.name); } @@ -19687,11 +19794,7 @@ void messagePlayerMonsterEvent(int player, Uint32 color, Stat& monsterStats, con { messagePlayerColor(player, MESSAGE_COMBAT_BASIC, color, msgGeneric, monsterStats.name); } - else if ( monsterType < KOBOLD ) //Original monster count - { - messagePlayerColor(player, MESSAGE_COMBAT_BASIC, color, msgNamed, monsterStats.name); - } - else if ( monsterType >= KOBOLD ) //New monsters + else { messagePlayerColor(player, MESSAGE_COMBAT_BASIC, color, msgNamed, monsterStats.name); } @@ -19751,11 +19854,7 @@ void messagePlayerMonsterEvent(int player, Uint32 color, Stat& monsterStats, con { messagePlayerColor(player, MESSAGE_COMBAT, color, msgGeneric, monsterStats.name, monsterStats.weapon->getName()); } - else if ( monsterType < KOBOLD ) //Original monster count - { - messagePlayerColor(player, MESSAGE_COMBAT, color, msgNamed, monsterStats.name, monsterStats.weapon->getName()); - } - else if ( monsterType >= KOBOLD ) //New monsters + else { messagePlayerColor(player, MESSAGE_COMBAT, color, msgNamed, monsterStats.name, monsterStats.weapon->getName()); } @@ -21024,6 +21123,14 @@ void Entity::handleHumanoidShieldLimb(Entity* shieldLimb, Entity* shieldArmLimb) switch ( race ) { + case BUGBEAR: + shieldLimb->x -= limbs[race][19][0] * cos(this->yaw + PI / 2) + limbs[race][19][1] * cos(this->yaw); + shieldLimb->y -= limbs[race][19][0] * sin(this->yaw + PI / 2) + limbs[race][19][1] * sin(this->yaw); + shieldLimb->z += limbs[race][19][2]; + shieldLimb->yaw = shieldArmLimb->yaw; + shieldLimb->roll = 0; + shieldLimb->pitch = shieldArmLimb->pitch; + break; case CREATURE_IMP: shieldLimb->focalx = limbs[race][8][0]; shieldLimb->focaly = limbs[race][8][1]; @@ -21931,6 +22038,11 @@ real_t Entity::getDamageTableMultiplier(Entity* my, Stat& myStats, DamageTableTy { bonus = -.2; } + if ( damageType == DamageTableType::DAMAGE_TABLE_MAGIC + && myStats.type == BUGBEAR && myStats.defending && myStats.shield ) + { + damageMultiplier = 0.2; + } int followerResist = my ? my->getFollowerBonusDamageResist() : 0; if ( followerResist != 0 ) { From 27d10bd67f30b26deeb176e78d078b1bf7e75b0b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 01:55:39 +1000 Subject: [PATCH 152/244] * solution update --- VS.2015/Barony/Barony.vcxproj | 2 ++ VS.2015/Barony/Barony.vcxproj.filters | 3 +++ 2 files changed, 5 insertions(+) diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index e703e1818..92d61a85e 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -193,6 +193,7 @@ v143 true MultiByte + false Application @@ -1184,6 +1185,7 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% + diff --git a/VS.2015/Barony/Barony.vcxproj.filters b/VS.2015/Barony/Barony.vcxproj.filters index fb6282010..494cd70a2 100644 --- a/VS.2015/Barony/Barony.vcxproj.filters +++ b/VS.2015/Barony/Barony.vcxproj.filters @@ -498,6 +498,9 @@ Source Files + + Source Files + From f059924fb49b2b7ae1bee107cdecbaf9d6af352b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 25 Sep 2024 02:26:24 +1000 Subject: [PATCH 153/244] * bat bit more scaling HP, bugbear bit more CON, taunt sound on defending --- src/entity.cpp | 24 ++++++++++++++++++++++++ src/monster_bat.cpp | 3 +++ src/stat_shared.cpp | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 38fdfbdbe..ac7a5abfe 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -11458,6 +11458,30 @@ void Entity::attack(int pose, int charge, Entity* target) } } } + + if ( hitstats ) + { + if ( hitstats->type == BUGBEAR + && hitstats->defending ) + { +#ifdef USE_FMOD + + bool playing = false; + if ( hitstats->monster_sound ) + { + hitstats->monster_sound->isPlaying(&playing); + } + if ( !playing ) + { + if ( (hitstats->OLDHP == hitstats->HP && local_rng.rand() % 5 == 0) + || (local_rng.rand() % 10 == 0) ) + { + hitstats->monster_sound = playSoundEntity(hit.entity, 681 + local_rng.rand() % 2, 128); + } + } +#endif + } + } } } } diff --git a/src/monster_bat.cpp b/src/monster_bat.cpp index a85c229d7..c70098b3e 100644 --- a/src/monster_bat.cpp +++ b/src/monster_bat.cpp @@ -55,6 +55,9 @@ void initBat(Entity* my, Stat* myStats) { myStats->STR += std::min(5, currentlevel / 5); myStats->DEX += std::min(3, currentlevel / 5); + myStats->HP += std::min(30, 5 * (currentlevel / 5)); + myStats->MAXHP = myStats->HP; + myStats->OLDHP = myStats->HP; } // apply random stat increases if set in stat_shared.cpp or editor diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index e35917518..44e3e5047 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1338,7 +1338,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->OLDHP = stats->HP; stats->STR = 12; stats->DEX = 3; - stats->CON = 5; + stats->CON = 8; stats->INT = -4; stats->PER = 6; stats->CHR = -4; From 1f647f7023842c8e1a73a0fe3e97adb8b40c915a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:42:20 +1000 Subject: [PATCH 154/244] * bugbear add defend if not in ranged dist * bats can target bugbears/incubus, gnomes angy at vampires, bats aid vamps * gnome add quivers --- src/actmonster.cpp | 79 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index efc6ebad6..6de5281e8 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -35,20 +35,23 @@ float limbs[NUMMONSTERS][20][3]; // determines which monsters fight which bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { +// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B +// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U +// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1 }, // HUMAN { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // RAT { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GOBLIN { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // SLIME { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // TROLL - { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // BAT_SMALL + { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1 }, // BAT_SMALL { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPIDER { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // GHOUL { 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SKELETON { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCORPION { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // IMP { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // CRAB - { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GNOME + { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GNOME { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DEMON { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SUCCUBUS { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MIMIC @@ -60,7 +63,7 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SCARAB { 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // CRYSTALGOLEM { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // INCUBUS - { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // VAMPIRE + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // VAMPIRE { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SHADOW { 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // COCKATRICE { 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // INSECTOID @@ -73,18 +76,24 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 } // BUGBEAR +// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B +// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U +// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G }; // determines which monsters come to the aid of other monsters bool monsterally[NUMMONSTERS][NUMMONSTERS] = { +// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B +// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U +// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // NOTHING { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // HUMAN { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // RAT { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // GOBLIN { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, // SLIME { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // TROLL - { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // BAT_SMALL { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // SPIDER { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, // GHOUL { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SKELETON @@ -116,6 +125,9 @@ bool monsterally[NUMMONSTERS][NUMMONSTERS] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // GYROBOT { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DUMMYBOT { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // BUGBEAR +// N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B +// O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U +// T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G }; // monster sight ranges @@ -9435,10 +9447,17 @@ void Entity::handleMonsterAttack(Stat* myStats, Entity* target, double dist) } if ( ticks % (90 + getUID() % 10) == 0 ) { - if ( myStats->type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + if ( myStats->type == BUGBEAR ) { int oldDefend = monsterDefend; - monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon); + if ( monsterSpecialState == BUGBEAR_DEFENSE ) + { + monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon); + } + else if ( !hasrangedweapon && dist > TOUCHRANGE && target ) + { + monsterDefend = shouldMonsterDefend(*myStats, *target, *target->getStats(), dist, hasrangedweapon); + } if ( oldDefend != monsterDefend ) { serverUpdateEntitySkill(this, 47); @@ -12327,20 +12346,27 @@ int Entity::shouldMonsterDefend(Stat& myStats, const Entity& target, const Stat& } } - if ( myStats.type == BUGBEAR && monsterSpecialState == BUGBEAR_DEFENSE ) + if ( myStats.type == BUGBEAR ) { - if ( targetDist > TOUCHRANGE && !hasrangedweapon ) - { - blockChance = 20; - } - else + if ( monsterSpecialState == BUGBEAR_DEFENSE ) { - blockChance = 12; - if ( monsterDefend == MONSTER_DEFEND_NONE ) + if ( targetDist > TOUCHRANGE && !hasrangedweapon ) + { + blockChance = 20; + } + else { - blockChance = 18; + blockChance = 12; + if ( monsterDefend == MONSTER_DEFEND_NONE ) + { + blockChance = 18; + } } } + else if ( targetDist > TOUCHRANGE && !hasrangedweapon ) + { + blockChance = 16; + } } if ( local_rng.rand() % 20 < blockChance ) @@ -12856,6 +12882,29 @@ void Entity::monsterGenerateQuiverItem(Stat* myStats, bool lesserMonster) } switch ( myStats->type ) { + case GNOME: + switch ( rng.rand() % 5 ) + { + case 0: + case 1: + case 2: + myStats->shield = newItem(QUIVER_LIGHTWEIGHT, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr); + break; + case 3: + case 4: + if ( myStats->weapon && myStats->weapon->type == SHORTBOW ) + { + myStats->shield = newItem(QUIVER_KNOCKBACK, SERVICABLE, 0, ammo, ITEM_GENERATED_QUIVER_APPEARANCE, false, nullptr); + } + else + { + // nothing + } + break; + default: + break; + } + break; case HUMAN: switch ( rng.rand() % 5 ) { From c17cc16b0fdd27338abd9f971740571a1f5760b6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:42:46 +1000 Subject: [PATCH 155/244] * fix npc interact cant target items --- src/entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index ac7a5abfe..d62c158ea 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -16990,7 +16990,7 @@ void Entity::monsterAcquireAttackTarget(const Entity& target, Sint32 state, bool bool hadOldTarget = (uidToEntity(monsterTarget) != nullptr); Sint32 oldMonsterState = monsterState; - if ( target.getRace() == GYROBOT || target.isInertMimic() || target.isUntargetableBat() || !(target.behavior == &actMonster || target.behavior == &actPlayer) ) + if ( target.getRace() == GYROBOT || target.isInertMimic() || target.isUntargetableBat() ) { return; } From 989c57bacc1821cf8fe7e9d0c12866584554f803 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:46:59 +1000 Subject: [PATCH 156/244] * gnome update limbs --- src/entity.cpp | 190 ++++++++++++++++++++++++++++++++++++++++++ src/entity_shared.cpp | 2 +- src/items.cpp | 11 ++- src/items.hpp | 3 +- src/mod_tools.cpp | 5 ++ src/mod_tools.hpp | 1 + src/monster.hpp | 2 +- src/stat_shared.cpp | 8 ++ 8 files changed, 215 insertions(+), 7 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index d62c158ea..3505e4834 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -20730,6 +20730,108 @@ void Entity::setHumanoidLimbOffset(Entity* limb, Monster race, int limbType) } switch ( race ) { + case GNOME: + if ( limbType == LIMB_HUMANOID_TORSO ) + { + limb->x -= .25 * cos(this->yaw); + limb->y -= .25 * sin(this->yaw); + limb->z += 1.25; + + if ( limb->sprite == 1431 ) + { + limb->focalx += 0.25; + } + else if ( limb->sprite == 296 ) + { + limb->focalx += 0.5; + } + + if ( limb->sprite != 1427 && limb->sprite != 1431 && limb->sprite != 296 ) + { + limb->focalz -= 0.25; + } + + limb->scalex = limbs[GNOME][11][0]; + limb->scaley = limbs[GNOME][11][1]; + limb->scalez = limbs[GNOME][11][2]; + } + else if ( limbType == LIMB_HUMANOID_RIGHTLEG ) + { + limb->x += 1.25 * cos(this->yaw + PI / 2); + limb->y += 1.25 * sin(this->yaw + PI / 2); + limb->z += 2.75; + if ( this->z >= 3.9 && this->z <= 4.1 ) + { + limb->yaw += PI / 8; + limb->pitch = -PI / 2; + } + + if ( limb->sprite == 1428 || limb->sprite == 1429 + || limb->sprite == 1432 || limb->sprite == 1433 ) + { + limb->focalz -= 0.25; + } + } + else if ( limbType == LIMB_HUMANOID_LEFTLEG ) + { + limb->x -= 1.25 * cos(this->yaw + PI / 2); + limb->y -= 1.25 * sin(this->yaw + PI / 2); + limb->z += 2.75; + if ( this->z >= 3.9 && this->z <= 4.1 ) + { + limb->yaw -= PI / 8; + limb->pitch = -PI / 2; + } + + if ( limb->sprite == 1428 || limb->sprite == 1429 + || limb->sprite == 1432 || limb->sprite == 1433 ) + { + limb->focalz -= 0.25; + } + } + else if ( limbType == LIMB_HUMANOID_RIGHTARM ) + { + limb->x += 2.5 * cos(this->yaw + PI / 2) - .75 * cos(this->yaw); + limb->y += 2.5 * sin(this->yaw + PI / 2) - .75 * sin(this->yaw); + limb->z -= .25; + if ( this->z >= 3.9 && this->z <= 4.1 ) + { + limb->pitch = 0; + } + + if ( limb->sprite != 1434 + && limb->sprite != 299 ) + { + limb->x -= 0.25 * cos(this->yaw + PI / 2); + limb->y -= 0.25 * sin(this->yaw + PI / 2); + } + if ( limb->sprite == 1434 ) + { + limb->focalz -= 0.25; + } + } + else if ( limbType == LIMB_HUMANOID_LEFTARM ) + { + limb->x -= 2.5 * cos(this->yaw + PI / 2) + .75 * cos(this->yaw); + limb->y -= 2.5 * sin(this->yaw + PI / 2) + .75 * sin(this->yaw); + limb->z -= .25; + if ( this->z >= 3.9 && this->z <= 4.1 ) + { + limb->pitch = 0; + } + + if ( limb->sprite != 1436 + && limb->sprite != 301 ) + { + limb->x += 0.25 * cos(this->yaw + PI / 2); + limb->y += 0.25 * sin(this->yaw + PI / 2); + } + if ( limb->sprite == 1436 ) + { + limb->focalz -= 0.25; + } + } + break; case CREATURE_IMP: if ( limbType == LIMB_HUMANOID_TORSO ) { @@ -21182,6 +21284,90 @@ void Entity::handleHumanoidShieldLimb(Entity* shieldLimb, Entity* shieldArmLimb) shieldLimb->scalez = 0.8; } break; + case GNOME: + shieldLimb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw); + shieldLimb->y -= 2.5 * sin(this->yaw + PI / 2) + .20 * sin(this->yaw); + shieldLimb->z += 1; + shieldLimb->yaw = shieldArmLimb->yaw; + shieldLimb->roll = 0; + shieldLimb->pitch = 0; + + if ( shieldLimb->sprite == items[TOOL_LANTERN].index ) + { + shieldLimb->z += 2; + } + if ( flickerLights || ticks % TICKS_PER_SECOND == 1 ) + { + if ( shieldLimb->sprite == items[TOOL_TORCH].index ) + { + if ( flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME) ) + { + flameEntity->x += 2 * cos(shieldArmLimb->yaw); + flameEntity->y += 2 * sin(shieldArmLimb->yaw); + flameEntity->z -= 2; + } + } + else if ( shieldLimb->sprite == items[TOOL_CRYSTALSHARD].index ) + { + /*flameEntity = spawnFlame(shieldLimb, SPRITE_CRYSTALFLAME); + flameEntity->x += 2 * cos(shieldArmLimb->yaw); + flameEntity->y += 2 * sin(shieldArmLimb->yaw); + flameEntity->z -= 2;*/ + } + else if ( shieldLimb->sprite == items[TOOL_LANTERN].index ) + { + if ( flameEntity = spawnFlame(shieldLimb, SPRITE_FLAME) ) + { + flameEntity->x += 2 * cos(shieldArmLimb->yaw); + flameEntity->y += 2 * sin(shieldArmLimb->yaw); + flameEntity->z += 1; + } + } + } + if ( itemSpriteIsQuiverThirdPersonModel(shieldLimb->sprite) ) + { + shieldLimb->focalz += 3; + shieldLimb->scalex = 1.05; + shieldLimb->x -= -0.25 * cos(this->yaw + PI / 2) + 1.25 * cos(this->yaw); + shieldLimb->y -= -0.25 * sin(this->yaw + PI / 2) + 1.25 * sin(this->yaw); + shieldLimb->z += -1.28; + } + else if ( shieldLimb->sprite >= items[SPELLBOOK_LIGHT].index + && shieldLimb->sprite < (items[SPELLBOOK_LIGHT].index + items[SPELLBOOK_LIGHT].variations) ) + { + shieldLimb->pitch = shieldArmLimb->pitch - .25 + 3 * PI / 2; + shieldLimb->yaw += PI / 6; + shieldLimb->focalx -= 4; + shieldLimb->focalz += .5; + shieldLimb->x += 0.5 * cos(this->yaw + PI / 2) + .5 * cos(this->yaw); + shieldLimb->y += 0.5 * sin(this->yaw + PI / 2) + .5 * sin(this->yaw); + shieldLimb->z -= 1; + shieldLimb->scalex = 0.8; + shieldLimb->scaley = 0.8; + shieldLimb->scalez = 0.8; + } + if ( this->fskill[8] > PI / 32 ) //MONSTER_SHIELDYAW and PLAYER_SHIELDYAW defending animation + { + if ( shieldLimb->sprite != items[TOOL_TORCH].index + && shieldLimb->sprite != items[TOOL_LANTERN].index + && shieldLimb->sprite != items[TOOL_CRYSTALSHARD].index ) + { + // shield, so rotate a little. + shieldLimb->roll += PI / 64; + } + else + { + shieldLimb->x += 0.25 * cos(this->yaw); + shieldLimb->y += 0.25 * sin(this->yaw); + shieldLimb->pitch += PI / 16; + if ( flameEntity ) + { + flameEntity->x += 0.75 * cos(shieldArmLimb->yaw); + flameEntity->y += 0.75 * sin(shieldArmLimb->yaw); + } + } + } + break; case HUMAN: case VAMPIRE: shieldLimb->x -= 2.5 * cos(this->yaw + PI / 2) + .20 * cos(this->yaw); @@ -22014,6 +22200,10 @@ void Entity::handleQuiverThirdPersonModel(Stat& myStats) case AUTOMATON: sprite += 2; // short strap break; + case KOBOLD: + case GNOME: + // no strap. + break; default: sprite += 3; // shoulderpad-less. break; diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index bed9861e9..eaade434c 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -1361,7 +1361,6 @@ int canWearEquip(Entity* entity, int category) //monsters with cloak/weapon/shield/boots/mask/gloves (no helm) case BUGBEAR: - case GNOME: case INCUBUS: case SUCCUBUS: case LICH_FIRE: @@ -1370,6 +1369,7 @@ int canWearEquip(Entity* entity, int category) break; //monsters with cloak/weapon/shield/boots/helm/armor/mask/gloves + case GNOME: case GOBLIN: case HUMAN: case VAMPIRE: diff --git a/src/items.cpp b/src/items.cpp index 770f76058..2e358fdd7 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -931,12 +931,15 @@ char* Item::getName() const -------------------------------------------------------------------------------*/ -Sint32 itemModel(const Item* const item) +Sint32 itemModel(const Item* const item, bool shortModel) { if ( !item || item->type < 0 || item->type >= NUMITEMS ) { return 0; } + + int index = shortModel ? items[item->type].indexShort : items[item->type].index; + if ( item->type == TOOL_PLAYER_LOOT_BAG ) { if ( colorblind_lobby ) @@ -960,14 +963,14 @@ Sint32 itemModel(const Item* const item) default: break; } - return items[item->type].index + index; + return index + index; } else { - return items[item->type].index + item->getLootBagPlayer(); + return index + item->getLootBagPlayer(); } } - return items[item->type].index + item->appearance % items[item->type].variations; + return index + item->appearance % items[item->type].variations; } /*------------------------------------------------------------------------------- diff --git a/src/items.hpp b/src/items.hpp index 07d8a5dbf..991633c2e 100644 --- a/src/items.hpp +++ b/src/items.hpp @@ -559,6 +559,7 @@ class ItemGeneric std::string item_name_unidentified; // unidentified item name public: int index; // world model + int indexShort; // short mob world model int fpindex; // first person model int variations; // number of model variations int weight; // weight per item @@ -650,7 +651,7 @@ Entity* dropItemMonster(Item* item, Entity* monster, Stat* monsterStats, Sint16 Item** itemSlot(Stat* myStats, Item* item); enum Category itemCategory(const Item* item); -Sint32 itemModel(const Item* item); +Sint32 itemModel(const Item* item, bool shortModel = false); Sint32 itemModelFirstperson(const Item* item); SDL_Surface* itemSprite(Item* item); void consumeItem(Item*& item, int player); //NOTE: Items have to be unequipped before calling this function on them. NOTE: THIS CAN FREE THE ITEM POINTER. Sets item to nullptr if it does. diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index caf198c32..ad224bd35 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -882,6 +882,10 @@ void ItemTooltips_t::readItemsFromFile() t.itemId = item_itr->value["item_id"].GetInt(); t.fpIndex = item_itr->value["first_person_model_index"].GetInt(); t.tpIndex = item_itr->value["third_person_model_index"].GetInt(); + if ( item_itr->value.HasMember("third_person_model_index_short") ) + { + t.tpShortIndex = item_itr->value["third_person_model_index_short"].GetInt(); + } t.gold = item_itr->value["gold_value"].GetInt(); t.weight = item_itr->value["weight_value"].GetInt(); t.itemLevel = item_itr->value["item_level"].GetInt(); @@ -935,6 +939,7 @@ void ItemTooltips_t::readItemsFromFile() items[i].weight = tmpItems[i].weight; items[i].fpindex = tmpItems[i].fpIndex; items[i].index = tmpItems[i].tpIndex; + items[i].indexShort = tmpItems[i].tpShortIndex; items[i].tooltip = tmpItems[i].tooltip; items[i].attributes.clear(); items[i].attributes = tmpItems[i].attributes; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 98a164a28..24f4f4cb7 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2741,6 +2741,7 @@ class ItemTooltips_t Sint32 itemId = -1; Sint32 fpIndex = -1; Sint32 tpIndex = -1; + Sint32 tpShortIndex = -1; Sint32 gold = 0; Sint32 weight = 0; Sint32 itemLevel = -1; diff --git a/src/monster.hpp b/src/monster.hpp index ae68dce2b..7faa941b4 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -160,7 +160,7 @@ static std::vector monsterSprites[NUMMONSTERS] = { // GNOME { - 295, + 295, 1426, 1430 }, // DEMON diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index 44e3e5047..dd7e5271e 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -193,6 +193,11 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 1; stats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 1; stats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_HELM] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_ARMOR] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_GLOVES] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_MASK] = 1; stats->EDITOR_ITEMS[ITEM_SLOT_INV_1] = 1; stats->EDITOR_ITEMS[ITEM_SLOT_INV_1 + ITEM_CHANCE] = 33; //Fish @@ -1354,6 +1359,9 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 1; stats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 1; + + stats->EDITOR_ITEMS[ITEM_SLOT_INV_1] = 1; + stats->EDITOR_ITEMS[ITEM_SLOT_INV_1 + ITEM_CHANCE] = 33; //Random Items break; case 10: default: From a2d5164c2ba0e769afcc771b11800603809176f5 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:48:56 +1000 Subject: [PATCH 157/244] * remove deprecated bit of steel helm code for goblins --- src/monster_goblin.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/monster_goblin.cpp b/src/monster_goblin.cpp index acc96fd80..5c3147a2d 100644 --- a/src/monster_goblin.cpp +++ b/src/monster_goblin.cpp @@ -1095,17 +1095,7 @@ void goblinMoveBodyparts(Entity* my, Stat* myStats, double dist) entity->roll = PI / 2; if ( multiplayer != CLIENT ) { - bool hasSteelHelm = false; - /*if ( myStats->helmet ) - { - if ( myStats->helmet->type == STEEL_HELM - || myStats->helmet->type == CRYSTAL_HELM - || myStats->helmet->type == ARTIFACT_HELM ) - { - hasSteelHelm = true; - } - }*/ - if ( myStats->mask == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring || hasSteelHelm ) //TODO: isInvisible()? + if ( myStats->mask == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? { entity->flags[INVISIBLE] = true; } From fb8bf9373a0fddecdc8cb4eec191ae8fb8eca3b9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:50:16 +1000 Subject: [PATCH 158/244] * ruins maps misc hashes --- src/files.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/files.cpp b/src/files.cpp index 6d1fbe6ff..d227ef0d1 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -884,7 +884,7 @@ std::unordered_map mapHashes = { { "ruins11.lmp", 55530 }, { "ruins11a.lmp", 11046 }, { "ruins11b.lmp", 8791 }, - { "ruins11c.lmp", 39211 }, + { "ruins11c.lmp", 39502 }, { "ruins11d.lmp", 25593 }, { "ruins11e.lmp", 13047 }, { "ruins12.lmp", 24121 }, @@ -943,7 +943,7 @@ std::unordered_map mapHashes = { { "ruins20e.lmp", 369845 }, { "ruins21.lmp", 71662 }, { "ruins21a.lmp", 4165 }, - { "ruins21b.lmp", 9257 }, + { "ruins21b.lmp", 9311 }, { "ruins21c.lmp", 4264 }, { "ruins21d.lmp", 27792 }, { "ruins21e.lmp", 18263 }, @@ -987,7 +987,7 @@ std::unordered_map mapHashes = { { "ruins28a.lmp", 483 }, { "ruins28b.lmp", 697 }, { "ruins28c.lmp", 10324 }, - { "ruins28d.lmp", 565 }, + { "ruins28d.lmp", 439 }, { "ruins28e.lmp", 250 }, { "ruins28f.lmp", 443 }, { "ruins28g.lmp", 441 }, From 60f81a126cf4b023d899d36d024ee44dffa73fda Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:50:44 +1000 Subject: [PATCH 159/244] * bugbear more likely shield from 50% to 66% --- src/monster_bugbear.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monster_bugbear.cpp b/src/monster_bugbear.cpp index 2a70388c2..e218ae305 100644 --- a/src/monster_bugbear.cpp +++ b/src/monster_bugbear.cpp @@ -157,7 +157,7 @@ void initBugbear(Entity* my, Stat* myStats) } else { - if ( (hasAlly && rng.rand() % 4 == 0) || (!hasAlly && rng.rand() % 2 == 0) ) + if ( (hasAlly && rng.rand() % 4 == 0) || (!hasAlly && rng.rand() % 3 > 0) ) { myStats->shield = newItem(STEEL_SHIELD, static_cast(rng.rand() % 2 + SERVICABLE), -1 + rng.rand() % 3, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, nullptr); } From 9762af51bba7ff619af5c0961778e6cf55fea5c8 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 30 Sep 2024 00:52:30 +1000 Subject: [PATCH 160/244] * gnome part1 --- src/monster_gnome.cpp | 1078 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 970 insertions(+), 108 deletions(-) diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index b2e86d8d1..2dbb15d14 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -21,6 +21,14 @@ #include "collision.hpp" #include "player.hpp" #include "prng.hpp" +#include "mod_tools.hpp" + +enum GnomeVariant +{ + GNOME_DEFAULT, + GNOME_THIEF_MELEE, + GNOME_THIEF_RANGED +}; void initGnome(Entity* my, Stat* myStats) { @@ -28,7 +36,22 @@ void initGnome(Entity* my, Stat* myStats) node_t* node; my->flags[BURNABLE] = true; - my->initMonster(295); //Sprite 295 = Gnome head model + std::string gnome_type = myStats ? myStats->getAttribute("gnome_type") : ""; + if ( gnome_type.find("gnome2") != std::string::npos ) + { + if ( gnome_type.find("gnome2F") != std::string::npos ) + { + my->initMonster(1430); + } + else + { + my->initMonster(1426); + } + } + else + { + my->initMonster(295); //Sprite 295 = Gnome head model + } my->z = 2.25; if ( multiplayer != CLIENT ) @@ -49,6 +72,92 @@ void initGnome(Entity* my, Stat* myStats) myStats->leader_uid = 0; } + if ( currentlevel >= 16 && rng.rand() % 2 == 0 ) + { + // gnome thieves + if ( myStats->getAttribute("gnome_type") == "" ) + { + gnome_type = ""; + if ( rng.rand() % 2 == 0 ) + { + gnome_type = "gnome2"; + } + else + { + gnome_type = "gnome2F"; + } + + if ( myStats->leader_uid == 0 && !my->flags[USERFLAG2] ) + { + gnome_type += "_ranged"; + + int numAllies = 2 + rng.rand() % 4; + int i = 0; + while ( i < numAllies ) + { + Entity* entity = summonMonster(GNOME, my->x, my->y); + if ( entity ) + { + entity->parent = my->getUID(); + if ( Stat* followerStats = entity->getStats() ) + { + followerStats->leader_uid = entity->parent; + std::string followerType = rng.rand() % 2 ? "gnome2" : "gnome2F"; + if ( i % 2 == 0 || i % 3 == 0 ) + { + followerType += "_melee"; + } + else + { + followerType += "_ranged"; + } + followerStats->setAttribute("gnome_type", followerType); + } + entity->seedEntityRNG(rng.getU32()); + } + ++i; + } + } + else + { + gnome_type += rng.rand() % 2 ? "_melee" : "_ranged"; + } + + if ( gnome_type.find("gnome2F") != std::string::npos ) + { + my->sprite = 1430; + } + else + { + my->sprite = 1426; + } + myStats->setAttribute("gnome_type", gnome_type); + + myStats->HP += 30; + myStats->MAXHP = myStats->HP; + myStats->OLDHP = myStats->HP; + } + } + + if ( myStats->getAttribute("gnome_type").find("gnome2F") != std::string::npos ) + { + myStats->sex = sex_t::FEMALE; + } + else if ( myStats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) + { + myStats->sex = sex_t::MALE; + } + + GnomeVariant gnomeVariant = GNOME_DEFAULT; + if ( myStats->getAttribute("gnome_type").find("_ranged") != std::string::npos ) + { + gnomeVariant = GNOME_THIEF_RANGED; + } + else if ( myStats->getAttribute("gnome_type").find("_melee") != std::string::npos ) + { + gnomeVariant = GNOME_THIEF_MELEE; + } + // apply random stat increases if set in stat_shared.cpp or editor setRandomMonsterStats(myStats, rng); @@ -122,11 +231,61 @@ void initGnome(Entity* my, Stat* myStats) break; } + //give weapon + if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 ) + { + if ( gnomeVariant == GNOME_DEFAULT ) + { + switch ( rng.rand() % 10 ) + { + case 0: + case 1: + case 2: + case 3: + case 4: + myStats->weapon = newItem(TOOL_PICKAXE, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + case 5: + case 6: + case 7: + case 8: + case 9: + myStats->GOLD += 100; + myStats->weapon = newItem(MAGICSTAFF_LIGHTNING, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + } + } + else if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + if ( rng.rand() % 2 == 0 ) + { + myStats->weapon = newItem(SHORTBOW, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + else + { + myStats->weapon = newItem(CROSSBOW, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + else if ( gnomeVariant == GNOME_THIEF_MELEE ) + { + if ( rng.rand() % 2 == 0 ) + { + myStats->weapon = newItem(STEEL_SWORD, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + else + { + myStats->weapon = newItem(STEEL_MACE, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + } + //give shield if ( myStats->shield == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] == 1 ) { - switch ( rng.rand() % 10 ) + if ( gnomeVariant == GNOME_DEFAULT ) { + switch ( rng.rand() % 10 ) + { case 0: case 1: myStats->shield = newItem(TOOL_LANTERN, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); @@ -142,11 +301,40 @@ void initGnome(Entity* my, Stat* myStats) case 9: myStats->shield = newItem(WOODEN_SHIELD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); break; + } } + else if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + if ( myStats->weapon && isRangedWeapon(*myStats->weapon) ) + { + my->monsterGenerateQuiverItem(myStats); + } + else + { + /*if ( rng.rand() % 10 <= 4 ) + { + myStats->shield = newItem(WOODEN_SHIELD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + }*/ + } + } + /*else if ( gnomeVariant == GNOME_THIEF_MELEE ) + { + if ( rng.rand() % 10 <= 3 ) + { + if ( rng.rand() % 2 == 0 ) + { + myStats->shield = newItem(IRON_SHIELD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + else + { + myStats->shield = newItem(WOODEN_SHIELD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + }*/ } - //give weapon - if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 ) + // give cloak + if ( myStats->cloak == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 ) { switch ( rng.rand() % 10 ) { @@ -155,37 +343,117 @@ void initGnome(Entity* my, Stat* myStats) case 2: case 3: case 4: - myStats->weapon = newItem(TOOL_PICKAXE, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); - break; case 5: + break; case 6: case 7: case 8: case 9: - myStats->GOLD += 100; - myStats->weapon = newItem(MAGICSTAFF_LIGHTNING, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->cloak = newItem(CLOAK, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); break; } } - // give cloak - if ( myStats->cloak == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] == 1 ) + if ( myStats->shoes == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] == 1 ) { - switch ( rng.rand() % 10 ) + if ( gnomeVariant == GNOME_THIEF_MELEE ) { + myStats->shoes = newItem(SUEDE_BOOTS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + + if ( myStats->gloves == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_GLOVES] == 1 ) + { + if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + myStats->gloves = newItem(SUEDE_GLOVES, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + + if ( myStats->helmet == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_HELM] == 1 ) + { + if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + if ( rng.rand() % 2 == 0 ) + { + myStats->helmet = newItem(HAT_HOOD_WHISPERS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); + } + else + { + myStats->helmet = newItem(HAT_BYCOCKET, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); + } + } + else if ( gnomeVariant == GNOME_THIEF_MELEE ) + { + if ( rng.rand() % 4 == 0 ) + { + myStats->helmet = newItem(HAT_BANDANA, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + else + { + if ( rng.rand() % 2 == 0 ) + { + myStats->helmet = newItem(HAT_HOOD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 2, false, nullptr); + } + else + { + myStats->helmet = newItem(HAT_HOOD_ASSASSIN, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + } + } + } + } + + if ( myStats->mask == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_MASK] == 1 ) + { + if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + switch ( rng.rand() % 10 ) + { + case 0: + myStats->mask = newItem(MASK_PIPE, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + case 1: + case 2: + myStats->mask = newItem(TOOL_GLASSES, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + case 3: + case 4: + case 5: + myStats->mask = newItem(MASK_EYEPATCH, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + case 6: + case 7: + case 8: + case 9: + myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; + default: + break; + } + } + else if ( gnomeVariant == GNOME_THIEF_MELEE ) + { + switch ( rng.rand() % 10 ) + { case 0: + myStats->mask = newItem(MASK_PIPE, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; case 1: case 2: case 3: + myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + break; case 4: case 5: - break; case 6: case 7: case 8: case 9: - myStats->cloak = newItem(CLOAK, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->mask = newItem(MASK_MOUTHKNIFE, SERVICABLE, 0, 1, rng.rand(), false, nullptr); + break; + default: break; + } } } } @@ -353,6 +621,49 @@ void initGnome(Entity* my, Stat* myStats) node->size = sizeof(Entity*); my->bodyparts.push_back(entity); + // helmet + entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->scalex = 1.01; + entity->scaley = 1.01; + entity->scalez = 1.01; + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->noColorChangeAllyLimb = 1.0; + entity->focalx = limbs[GNOME][9][0]; // 0 + entity->focaly = limbs[GNOME][9][1]; // 0 + entity->focalz = limbs[GNOME][9][2]; // -2 + entity->behavior = &actGnomeLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + + // mask + entity = newEntity(-1, 1, map.entities, nullptr); //Limb entity. + entity->sizex = 4; + entity->sizey = 4; + entity->skill[2] = my->getUID(); + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[USERFLAG2] = my->flags[USERFLAG2]; + entity->noColorChangeAllyLimb = 1.0; + entity->focalx = limbs[GNOME][10][0]; // 0 + entity->focaly = limbs[GNOME][10][1]; // 0 + entity->focalz = limbs[GNOME][10][2]; // .25 + entity->behavior = &actGnomeLimb; + entity->parent = my->getUID(); + node = list_AddNodeLast(&my->children); + node->element = entity; + node->deconstructor = &emptyDeconstructor; + node->size = sizeof(Entity*); + my->bodyparts.push_back(entity); + if ( multiplayer == CLIENT || MONSTER_INIT ) { return; @@ -400,6 +711,284 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) int bodypart; bool wearingring = false; + GnomeVariant gnomeVariant = GNOME_DEFAULT; + if ( myStats ) + { + if ( myStats->getAttribute("gnome_type").find("_ranged") != std::string::npos ) + { + gnomeVariant = GNOME_THIEF_RANGED; + } + else if ( myStats->getAttribute("gnome_type").find("_melee") != std::string::npos ) + { + gnomeVariant = GNOME_THIEF_MELEE; + } + + //static bool forceWalk = false; + //if ( keystatus[SDLK_KP_5] ) + //{ + // keystatus[SDLK_KP_5] = 0; + // forceWalk = !forceWalk; + //} + //if ( keystatus[SDLK_KP_6] ) + //{ + // myStats->EFFECTS[EFF_STUNNED] = !myStats->EFFECTS[EFF_STUNNED]; + //} + //if ( forceWalk ) + //{ + // dist = 0.15; + //} + + //if ( keystatus[SDLK_9] ) + //{ + // keystatus[SDLK_9] = 0; + // if ( myStats->helmet ) + // { + // myStats->helmet->appearance++; + // } + //} + //if ( keystatus[SDLK_0] ) + //{ + // keystatus[SDLK_0] = 0; + // if ( myStats->mask ) + // { + // myStats->mask->appearance++; + // } + //} + //if ( keystatus[SDLK_6] ) + //{ + // keystatus[SDLK_6] = 0; + // if ( my->sprite == 295 ) + // { + // my->sprite = 1426; + // } + // else if ( my->sprite == 1426 ) + // { + // my->sprite = 1430; + // } + // else + // { + // my->sprite = 295; + // } + //} + //if ( keystatus[SDLK_7] ) + //{ + // keystatus[SDLK_7] = 0; + // if ( myStats->shoes ) + // { + // while ( true ) + // { + // int type = myStats->shoes->type; + // type++; + // if ( type >= NUMITEMS ) + // { + // if ( myStats->shoes->node ) + // { + // list_RemoveNode(myStats->shoes->node); + // } + // else + // { + // free(myStats->shoes); + // } + // myStats->shoes = nullptr; + // break; + // } + // myStats->shoes->type = (ItemType)type; + // if ( items[myStats->shoes->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BOOTS ) + // { + // break; + // } + // } + // } + // else + // { + // myStats->shoes = newItem(LEATHER_BOOTS, EXCELLENT, 0, 1, 0, true, nullptr); + // } + //} + //if ( keystatus[SDLK_8] ) + //{ + // keystatus[SDLK_8] = 0; + // if ( myStats->gloves ) + // { + // while ( true ) + // { + // int type = myStats->gloves->type; + // type++; + // if ( type >= NUMITEMS ) + // { + // if ( myStats->gloves->node ) + // { + // list_RemoveNode(myStats->gloves->node); + // } + // else + // { + // free(myStats->gloves); + // } + // myStats->gloves = nullptr; + // break; + // } + // myStats->gloves->type = (ItemType)type; + // if ( items[myStats->gloves->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_GLOVES ) + // { + // break; + // } + // } + // } + // else + // { + // myStats->gloves = newItem(GLOVES, EXCELLENT, 0, 1, 0, true, nullptr); + // } + //} + //if ( keystatus[SDLK_g] ) + //{ + // keystatus[SDLK_g] = 0; + // if ( myStats->helmet ) + // { + // if ( keystatus[SDLK_LSHIFT] ) + // { + // while ( true ) + // { + // int type = myStats->helmet->type; + // type--; + // if ( type < 0 ) + // { + // type = NUMITEMS - 1; + // } + // myStats->helmet->type = (ItemType)type; + // if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) + // { + // break; + // } + // } + // } + // else + // { + // while ( true ) + // { + // int type = myStats->helmet->type; + // type++; + // if ( type >= NUMITEMS ) + // { + // type = 0; + // } + // myStats->helmet->type = (ItemType)type; + // if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) + // { + // break; + // } + // } + // } + // } + //} + //if ( keystatus[SDLK_j] ) + //{ + // keystatus[SDLK_j] = 0; + // if ( myStats->breastplate ) + // { + // if ( keystatus[SDLK_LSHIFT] ) + // { + // while ( true ) + // { + // int type = myStats->breastplate->type; + // type--; + // if ( type < 0 ) + // { + // type = NUMITEMS - 1; + // } + // myStats->breastplate->type = (ItemType)type; + // if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) + // { + // break; + // } + // } + // } + // else + // { + // while ( true ) + // { + // int type = myStats->breastplate->type; + // type++; + // if ( type >= NUMITEMS ) + // { + // if ( myStats->breastplate->node ) + // { + // list_RemoveNode(myStats->breastplate->node); + // } + // else + // { + // free(myStats->breastplate); + // } + // myStats->breastplate = nullptr; + // break; + // } + // myStats->breastplate->type = (ItemType)type; + // if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) + // { + // break; + // } + // } + // } + // } + // else + // { + // myStats->breastplate = newItem(LEATHER_BREASTPIECE, EXCELLENT, 0, 1, 0, true, nullptr); + // } + //} + //if ( keystatus[SDLK_h] ) + //{ + // keystatus[SDLK_h] = 0; + // if ( myStats->mask ) + // { + // if ( keystatus[SDLK_LSHIFT] ) + // { + // while ( true ) + // { + // int type = myStats->mask->type; + // type--; + // if ( type < 0 ) + // { + // type = NUMITEMS - 1; + // } + // myStats->mask->type = (ItemType)type; + // if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) + // { + // break; + // } + // } + // } + // else + // { + // while ( true ) + // { + // int type = myStats->mask->type; + // type++; + // if ( type >= NUMITEMS ) + // { + // type = 0; + // } + // myStats->mask->type = (ItemType)type; + // if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) + // { + // break; + // } + // } + // } + // } + //} + } + + my->focalx = limbs[GNOME][0][0]; + my->focaly = limbs[GNOME][0][1]; + my->focalz = limbs[GNOME][0][2]; + if ( my->sprite == 1430 ) + { + my->focalx -= 0.26; + } + else if ( my->sprite == 295 ) + { + my->focalx -= 0.25; + my->focalz -= 0.25; + } + // set invisibility //TODO: isInvisible()? if ( multiplayer != CLIENT ) { @@ -479,6 +1068,9 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) } Entity* shieldarm = nullptr; + Entity* helmet = nullptr; + + std::string gnome_type = my->sprite == 1426 ? "gnome2" : my->sprite == 1430 ? "gnome2F" : ""; //Move bodyparts for (bodypart = 0, node = my->children.first; node != nullptr; node = node->next, bodypart++) @@ -532,17 +1124,71 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) { // torso case LIMB_HUMANOID_TORSO: - entity->x -= .25 * cos(my->yaw); - entity->y -= .25 * sin(my->yaw); - entity->z += 1.25; + entity->focalx = limbs[GNOME][1][0]; + entity->focaly = limbs[GNOME][1][1]; + entity->focalz = limbs[GNOME][1][2]; + if ( multiplayer != CLIENT ) + { + if ( myStats->breastplate == nullptr ) + { + if ( gnomeVariant == GNOME_THIEF_MELEE ) + { + entity->sprite = 1444; + } + else if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + entity->sprite = 1442; + } + else + { + entity->sprite = gnome_type == "gnome2" ? 1427 : gnome_type == "gnome2F" ? 1431 : 296; + } + } + else + { + entity->sprite = itemModel(myStats->breastplate, true); + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) + { + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } + } + } + } + my->setHumanoidLimbOffset(entity, GNOME, LIMB_HUMANOID_TORSO); break; // right leg case LIMB_HUMANOID_RIGHTLEG: + entity->focalx = limbs[GNOME][2][0]; + entity->focaly = limbs[GNOME][2][1]; + entity->focalz = limbs[GNOME][2][2]; if ( multiplayer != CLIENT ) { if ( myStats->shoes == nullptr ) { - entity->sprite = 297; + if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + entity->sprite = 1439; + } + else + { + entity->sprite = gnome_type == "gnome2" ? 1428 : gnome_type == "gnome2F" ? 1432 : 297; + } } else { @@ -570,22 +1216,25 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) } } } - entity->x += 1.25 * cos(my->yaw + PI / 2); - entity->y += 1.25 * sin(my->yaw + PI / 2); - entity->z += 2.75; - if ( my->z >= 3.9 && my->z <= 4.1 ) - { - entity->yaw += PI / 8; - entity->pitch = -PI / 2; - } + my->setHumanoidLimbOffset(entity, GNOME, LIMB_HUMANOID_RIGHTLEG); break; // left leg case LIMB_HUMANOID_LEFTLEG: + entity->focalx = limbs[GNOME][3][0]; + entity->focaly = limbs[GNOME][3][1]; + entity->focalz = limbs[GNOME][3][2]; if ( multiplayer != CLIENT ) { if ( myStats->shoes == nullptr ) { - entity->sprite = 298; + if ( gnomeVariant == GNOME_THIEF_RANGED ) + { + entity->sprite = 1438; + } + else + { + entity->sprite = gnome_type == "gnome2" ? 1429 : gnome_type == "gnome2F" ? 1433 : 298; + } } else { @@ -613,20 +1262,75 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) } } } - entity->x -= 1.25 * cos(my->yaw + PI / 2); - entity->y -= 1.25 * sin(my->yaw + PI / 2); - entity->z += 2.75; - if ( my->z >= 3.9 && my->z <= 4.1 ) - { - entity->yaw -= PI / 8; - entity->pitch = -PI / 2; - } + my->setHumanoidLimbOffset(entity, GNOME, LIMB_HUMANOID_LEFTLEG); break; // right arm case LIMB_HUMANOID_RIGHTARM: { - ; + if ( multiplayer != CLIENT ) + { + if ( myStats->gloves == nullptr ) + { + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1434 : 299; + } + else + { + if ( setGloveSprite(myStats, entity, SPRITE_GLOVE_RIGHT_OFFSET) != 0 ) + { + // successfully set sprite for the human model + } + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) + { + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } + } + } + } + + if ( multiplayer == CLIENT ) + { + if ( entity->skill[7] == 0 ) + { + if ( entity->sprite == 299 || entity->sprite == 1434 ) + { + // these are the default arms. + // chances are they may be wrong if sent by the server, + } + else + { + // otherwise we're being sent gloves armor etc so it's probably right. + entity->skill[7] = entity->sprite; + } + } + if ( entity->skill[7] == 0 ) + { + // we set this ourselves until proper initialisation. + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1434 : 299; + } + else + { + entity->sprite = entity->skill[7]; + } + } + node_t* weaponNode = list_Node(&my->children, 7); + bool bentArm = false; if ( weaponNode ) { Entity* weapon = (Entity*)weaponNode->element; @@ -635,29 +1339,91 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) entity->focalx = limbs[GNOME][4][0]; // 0 entity->focaly = limbs[GNOME][4][1]; // 0 entity->focalz = limbs[GNOME][4][2]; // 2 - entity->sprite = 299; } else { entity->focalx = limbs[GNOME][4][0] + 1; // 1 - entity->focaly = limbs[GNOME][4][1]; // 0 - entity->focalz = limbs[GNOME][4][2] - 1; // 1 - entity->sprite = 300; + entity->focaly = limbs[GNOME][4][1] + 0.25; // 0 + entity->focalz = limbs[GNOME][4][2] - 0.75; // 1 + if ( entity->sprite == 299 || entity->sprite == 1434 ) + { + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1435 : 300; + } + else + { + entity->sprite += 2; + } } } - entity->x += 2.5 * cos(my->yaw + PI / 2) - .75 * cos(my->yaw); - entity->y += 2.5 * sin(my->yaw + PI / 2) - .75 * sin(my->yaw); - entity->z -= .25; + my->setHumanoidLimbOffset(entity, GNOME, LIMB_HUMANOID_RIGHTARM); entity->yaw += MONSTER_WEAPONYAW; - if ( my->z >= 3.9 && my->z <= 4.1 ) - { - entity->pitch = 0; - } break; // left arm } case LIMB_HUMANOID_LEFTARM: { + if ( multiplayer != CLIENT ) + { + if ( myStats->gloves == nullptr ) + { + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1436 : 301; + } + else + { + if ( setGloveSprite(myStats, entity, SPRITE_GLOVE_LEFT_OFFSET) != 0 ) + { + // successfully set sprite for the human model + } + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) + { + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } + } + } + } + + if ( multiplayer == CLIENT ) + { + if ( entity->skill[7] == 0 ) + { + if ( entity->sprite == 301 || entity->sprite == 1436 ) + { + // these are the default arms. + // chances are they may be wrong if sent by the server, + } + else + { + // otherwise we're being sent gloves armor etc so it's probably right. + entity->skill[7] = entity->sprite; + } + } + if ( entity->skill[7] == 0 ) + { + // we set this ourselves until proper initialisation. + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1436 : 301; + } + else + { + entity->sprite = entity->skill[7]; + } + } + shieldarm = entity; node_t* shieldNode = list_Node(&my->children, 8); if ( shieldNode ) @@ -668,23 +1434,23 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) entity->focalx = limbs[GNOME][5][0]; // 0 entity->focaly = limbs[GNOME][5][1]; // 0 entity->focalz = limbs[GNOME][5][2]; // 2 - entity->sprite = 301; } else { entity->focalx = limbs[GNOME][5][0] + 1; // 1 - entity->focaly = limbs[GNOME][5][1]; // 0 - entity->focalz = limbs[GNOME][5][2] - 1; // 1 - entity->sprite = 302; + entity->focaly = limbs[GNOME][5][1] - 0.25; // 0 + entity->focalz = limbs[GNOME][5][2] - 0.75; // 1 + if ( entity->sprite == 301 || entity->sprite == 1436 ) + { + entity->sprite = (gnome_type == "gnome2" || gnome_type == "gnome2F") ? 1437 : 302; + } + else + { + entity->sprite += 2; + } } } - entity->x -= 2.5 * cos(my->yaw + PI / 2) + .75 * cos(my->yaw); - entity->y -= 2.5 * sin(my->yaw + PI / 2) + .75 * sin(my->yaw); - entity->z -= .25; - if ( my->z >= 3.9 && my->z <= 4.1 ) - { - entity->pitch = 0; - } + my->setHumanoidLimbOffset(entity, GNOME, LIMB_HUMANOID_LEFTARM); if ( my->monsterDefend && my->monsterAttack == 0 ) { MONSTER_SHIELDYAW = PI / 5; @@ -811,76 +1577,155 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) entity->flags[INVISIBLE] = true; } } - entity->x -= 2.5 * cos(my->yaw + PI / 2) + .20 * cos(my->yaw); - entity->y -= 2.5 * sin(my->yaw + PI / 2) + .20 * sin(my->yaw); - entity->z += 1; - entity->yaw = shieldarm->yaw; - entity->roll = 0; - entity->pitch = 0; - if ( entity->sprite == items[TOOL_LANTERN].index ) + my->handleHumanoidShieldLimb(entity, shieldarm); + break; + // cloak + case LIMB_HUMANOID_CLOAK: + entity->focalx = limbs[GNOME][8][0]; + entity->focaly = limbs[GNOME][8][1]; + entity->focalz = limbs[GNOME][8][2]; + if ( multiplayer != CLIENT ) { - entity->z += 2; - } - if ( flickerLights || my->ticks % TICKS_PER_SECOND == 1 ) - { - if ( entity->sprite == items[TOOL_TORCH].index ) - { - if ( entity2 = spawnFlame(entity, SPRITE_FLAME) ) - { - entity2->x += 2 * cos(entity->yaw); - entity2->y += 2 * sin(entity->yaw); - entity2->z -= 2; - } - } - else if ( entity->sprite == items[TOOL_CRYSTALSHARD].index ) - { - /*entity2 = spawnFlame(entity, SPRITE_CRYSTALFLAME); - entity2->x += 2 * cos(entity->yaw); - entity2->y += 2 * sin(entity->yaw); - entity2->z -= 2;*/ - } - else if ( entity->sprite == items[TOOL_LANTERN].index ) - { - if ( entity2 = spawnFlame(entity, SPRITE_FLAME) ) + if ( myStats->cloak == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? + { + entity->flags[INVISIBLE] = true; + } + else + { + entity->flags[INVISIBLE] = false; + entity->sprite = itemModel(myStats->cloak); + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity2->x += 2 * cos(entity->yaw); - entity2->y += 2 * sin(entity->yaw); - entity2->z += 1; + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } - } + } } - if ( MONSTER_SHIELDYAW > PI / 32 ) + else + { + if ( entity->sprite <= 0 ) + { + entity->flags[INVISIBLE] = true; + } + } + entity->x -= cos(my->yaw) * 1.5; + entity->y -= sin(my->yaw) * 1.5; + entity->yaw += PI / 2; + + if ( entity->sprite != 1427 && entity->sprite != 1431 && entity->sprite != 296 ) + { + // push back for larger armors + entity->x -= cos(my->yaw) * 1.0; + entity->y -= sin(my->yaw) * 1.0; + } + break; + // helm + case LIMB_HUMANOID_HELMET: + helmet = entity; + entity->focalx = limbs[GNOME][9][0]; // 0 + entity->focaly = limbs[GNOME][9][1]; // 0 + entity->focalz = limbs[GNOME][9][2]; // -2 + entity->pitch = my->pitch; + entity->roll = 0; + if ( multiplayer != CLIENT ) { - if ( entity->sprite != items[TOOL_TORCH].index && entity->sprite != items[TOOL_LANTERN].index && entity->sprite != items[TOOL_CRYSTALSHARD].index ) + entity->sprite = itemModel(myStats->helmet); + if ( myStats->helmet == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? { - // shield, so rotate a little. - entity->roll += PI / 64; + entity->flags[INVISIBLE] = true; } else { - entity->x += 0.25 * cos(my->yaw); - entity->y += 0.25 * sin(my->yaw); - entity->pitch += PI / 16; - if ( entity2 ) + entity->flags[INVISIBLE] = false; + } + if ( multiplayer == SERVER ) + { + // update sprites for clients + if ( entity->ticks >= *cvar_entity_bodypart_sync_tick ) { - entity2->x += 0.75 * cos(shieldarm->yaw); - entity2->y += 0.75 * sin(shieldarm->yaw); + bool updateBodypart = false; + if ( entity->skill[10] != entity->sprite ) + { + entity->skill[10] = entity->sprite; + updateBodypart = true; + } + if ( entity->skill[11] != entity->flags[INVISIBLE] ) + { + entity->skill[11] = entity->flags[INVISIBLE]; + updateBodypart = true; + } + if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) ) + { + updateBodypart = true; + } + if ( updateBodypart ) + { + serverUpdateEntityBodypart(my, bodypart); + } } } } + else + { + if ( entity->sprite <= 0 ) + { + entity->flags[INVISIBLE] = true; + } + } + my->setHelmetLimbOffset(entity); break; - // cloak - case LIMB_HUMANOID_CLOAK: + // mask + case LIMB_HUMANOID_MASK: + entity->focalx = limbs[GNOME][10][0]; // 0 + entity->focaly = limbs[GNOME][10][1]; // 0 + entity->focalz = limbs[GNOME][10][2]; // .25 + entity->pitch = my->pitch; + entity->roll = PI / 2; if ( multiplayer != CLIENT ) { - if ( myStats->cloak == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? + if ( myStats->mask == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()? { entity->flags[INVISIBLE] = true; } else { entity->flags[INVISIBLE] = false; - entity->sprite = itemModel(myStats->cloak); + } + if ( myStats->mask != nullptr ) + { + if ( myStats->mask->type == TOOL_GLASSES ) + { + entity->sprite = 165; // GlassesWorn.vox + } + else if ( myStats->mask->type == MONOCLE ) + { + entity->sprite = 1196; // monocleWorn.vox + } + else + { + entity->sprite = itemModel(myStats->mask); + } } if ( multiplayer == SERVER ) { @@ -916,9 +1761,26 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) entity->flags[INVISIBLE] = true; } } - entity->x -= cos(my->yaw) * 1.5; - entity->y -= sin(my->yaw) * 1.5; - entity->yaw += PI / 2; + + if ( entity->sprite == items[MASK_SHAMAN].index ) + { + entity->roll = 0; + my->setHelmetLimbOffset(entity); + my->setHelmetLimbOffsetWithMask(helmet, entity); + } + else if ( EquipmentModelOffsets.modelOffsetExists(GNOME, entity->sprite) ) + { + my->setHelmetLimbOffset(entity); + my->setHelmetLimbOffsetWithMask(helmet, entity); + } + else + { + entity->focalx = limbs[GNOME][10][0] + .35; // .35 + entity->focaly = limbs[GNOME][10][1] - 2; // -2 + entity->focalz = limbs[GNOME][10][2]; // .25 + } + break; + default: break; } } From 758179dac5a2e773959c9c6b962d6ed11613e63f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 10 Oct 2024 16:32:35 +1100 Subject: [PATCH 161/244] * gnome adjust limbs/height, adjust some droptables --- src/monster_gnome.cpp | 733 ++++++++++++++++++++++++++---------------- 1 file changed, 449 insertions(+), 284 deletions(-) diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index 2dbb15d14..c9298a882 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -58,7 +58,14 @@ void initGnome(Entity* my, Stat* myStats) { MONSTER_SPOTSND = 220; MONSTER_SPOTVAR = 5; - MONSTER_IDLESND = 217; + if ( gnome_type.find("gnome2") != std::string::npos ) + { + MONSTER_IDLESND = 683; + } + else + { + MONSTER_IDLESND = 217; + } MONSTER_IDLEVAR = 3; } if ( multiplayer != CLIENT && !MONSTER_INIT ) @@ -72,7 +79,7 @@ void initGnome(Entity* my, Stat* myStats) myStats->leader_uid = 0; } - if ( currentlevel >= 16 && rng.rand() % 2 == 0 ) + if ( currentlevel >= 16 && rng.rand() % 5 >= 1 ) { // gnome thieves if ( myStats->getAttribute("gnome_type") == "" ) @@ -132,20 +139,18 @@ void initGnome(Entity* my, Stat* myStats) my->sprite = 1426; } myStats->setAttribute("gnome_type", gnome_type); - - myStats->HP += 30; - myStats->MAXHP = myStats->HP; - myStats->OLDHP = myStats->HP; } } if ( myStats->getAttribute("gnome_type").find("gnome2F") != std::string::npos ) { myStats->sex = sex_t::FEMALE; + MONSTER_IDLESND = 683; } else if ( myStats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) { myStats->sex = sex_t::MALE; + MONSTER_IDLESND = 683; } GnomeVariant gnomeVariant = GNOME_DEFAULT; @@ -158,6 +163,28 @@ void initGnome(Entity* my, Stat* myStats) gnomeVariant = GNOME_THIEF_MELEE; } + if ( gnomeVariant != GNOME_DEFAULT ) + { + myStats->HP = 80; + myStats->MAXHP = myStats->HP; + myStats->OLDHP = myStats->HP; + + myStats->CON = 8; + myStats->PER = 5; + myStats->STR = 7; + myStats->DEX = 5; + if ( rng.rand() % 2 ) + { + myStats->GOLD = 15; + myStats->RANDOM_GOLD = 15; + } + else + { + myStats->GOLD = 0; + myStats->RANDOM_GOLD = 0; + } + } + // apply random stat increases if set in stat_shared.cpp or editor setRandomMonsterStats(myStats, rng); @@ -188,21 +215,84 @@ void initGnome(Entity* my, Stat* myStats) my->setHardcoreStats(*myStats); // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots - switch ( defaultItems ) + if ( gnomeVariant == GNOME_DEFAULT ) { + switch ( defaultItems ) + { + case 6: + case 5: + case 4: + case 3: + if ( rng.rand() % 50 == 0 ) + { + if ( rng.rand() % 2 == 0 ) + { + newItem(ENCHANTED_FEATHER, WORN, 0, 1, (2 * (ENCHANTED_FEATHER_MAX_DURABILITY - 1)) / 4, false, &myStats->inventory); + } + else + { + newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook("Winny's Report"), false, &myStats->inventory); + } + } + case 2: + if ( rng.rand() % 10 == 0 ) + { + if ( rng.rand() % 2 == 0 ) + { + newItem(MASK_PIPE, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, &myStats->inventory); + } + else + { + int i = 1 + rng.rand() % 4; + for ( c = 0; c < i; c++ ) + { + newItem(static_cast(GEM_GARNET + rng.rand() % 15), static_cast(1 + rng.rand() % 4), 0, 1, rng.rand(), false, &myStats->inventory); + } + } + } + case 1: + if ( rng.rand() % 3 == 0 ) + { + newItem(FOOD_FISH, EXCELLENT, 0, 1, rng.rand(), false, &myStats->inventory); + } + break; + default: + break; + } + } + else + { + switch ( defaultItems ) + { case 6: case 5: case 4: case 3: - if ( rng.rand() % 50 == 0 ) + if ( rng.rand() % 100 == 0 ) { - if ( rng.rand() % 2 == 0 ) - { - newItem(ENCHANTED_FEATHER, WORN, 0, 1, (2 * (ENCHANTED_FEATHER_MAX_DURABILITY - 1)) / 4, false, &myStats->inventory); - } - else + newItem(ENCHANTED_FEATHER, WORN, 0, 1, (2 * (ENCHANTED_FEATHER_MAX_DURABILITY - 1)) / 4, false, &myStats->inventory); + } + else + { + if ( (rng.rand() % 4 > 0 && gnomeVariant == GNOME_THIEF_RANGED) + || (rng.rand() % 2 == 0 && gnomeVariant == GNOME_THIEF_MELEE) ) { - newItem(READABLE_BOOK, EXCELLENT, 0, 1, getBook("Winny's Report"), false, &myStats->inventory); + if ( rng.rand() % 2 == 0 ) + { + auto item = newItem(TOOL_BEARTRAP, DECREPIT, -1, 1, rng.rand(), false, &myStats->inventory); + if ( item ) + { + item->isDroppable = false; + } + } + else + { + auto item = newItem(static_cast(TOOL_BOMB + rng.rand() % 3), EXCELLENT, -1, 1, rng.rand(), false, &myStats->inventory); + if ( item ) + { + item->isDroppable = false; + } + } } } case 2: @@ -210,25 +300,29 @@ void initGnome(Entity* my, Stat* myStats) { if ( rng.rand() % 2 == 0 ) { - newItem(MASK_PIPE, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, &myStats->inventory); + newItem(static_cast(GEM_GLASS), static_cast(1 + rng.rand() % 4), 0, 1, rng.rand(), false, &myStats->inventory); } else { - int i = 1 + rng.rand() % 4; - for ( c = 0; c < i; c++ ) - { - newItem(static_cast(GEM_GARNET + rng.rand() % 15), static_cast(1 + rng.rand() % 4), 0, 1, rng.rand(), false, &myStats->inventory); - } + newItem(static_cast(GEM_GARNET + rng.rand() % 15), static_cast(1 + rng.rand() % 4), 0, 1, rng.rand(), false, &myStats->inventory); } } case 1: if ( rng.rand() % 3 == 0 ) { - newItem(FOOD_FISH, EXCELLENT, 0, 1, rng.rand(), false, &myStats->inventory); + if ( rng.rand() % 4 == 0 ) + { + newItem(FOOD_CREAMPIE, static_cast(3 + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, &myStats->inventory); + } + else + { + newItem(TOOL_LOCKPICK, EXCELLENT, 0, 1, rng.rand(), false, &myStats->inventory); + } } break; default: break; + } } //give weapon @@ -359,6 +453,7 @@ void initGnome(Entity* my, Stat* myStats) if ( gnomeVariant == GNOME_THIEF_MELEE ) { myStats->shoes = newItem(SUEDE_BOOTS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->shoes->isDroppable = (rng.rand() % 4 == 0) ? true : false; } } @@ -367,6 +462,7 @@ void initGnome(Entity* my, Stat* myStats) if ( gnomeVariant == GNOME_THIEF_RANGED ) { myStats->gloves = newItem(SUEDE_GLOVES, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->gloves->isDroppable = (rng.rand() % 4 == 0) ? true : false; } } @@ -374,12 +470,13 @@ void initGnome(Entity* my, Stat* myStats) { if ( gnomeVariant == GNOME_THIEF_RANGED ) { - if ( rng.rand() % 2 == 0 ) + if ( myStats->leader_uid != 0 ) { myStats->helmet = newItem(HAT_HOOD_WHISPERS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); } else { + // leader has the bycocket myStats->helmet = newItem(HAT_BYCOCKET, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); } } @@ -394,6 +491,7 @@ void initGnome(Entity* my, Stat* myStats) if ( rng.rand() % 2 == 0 ) { myStats->helmet = newItem(HAT_HOOD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 2, false, nullptr); + myStats->helmet->isDroppable = (rng.rand() % 4 == 0) ? true : false; } else { @@ -426,6 +524,7 @@ void initGnome(Entity* my, Stat* myStats) case 8: case 9: myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->mask->isDroppable = (rng.rand() % 4 == 0) ? true : false; break; default: break; @@ -441,7 +540,7 @@ void initGnome(Entity* my, Stat* myStats) case 1: case 2: case 3: - myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->mask = newItem(MASK_MOUTHKNIFE, SERVICABLE, 0, 1, rng.rand(), false, nullptr); break; case 4: case 5: @@ -449,7 +548,8 @@ void initGnome(Entity* my, Stat* myStats) case 7: case 8: case 9: - myStats->mask = newItem(MASK_MOUTHKNIFE, SERVICABLE, 0, 1, rng.rand(), false, nullptr); + myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->mask->isDroppable = (rng.rand() % 4 == 0) ? true : false; break; default: break; @@ -479,7 +579,7 @@ void initGnome(Entity* my, Stat* myStats) my->bodyparts.push_back(entity); // right leg - entity = newEntity(297, 1, map.entities, nullptr); //Limb entity. + entity = newEntity(1469, 1, map.entities, nullptr); //Limb entity. entity->sizex = 4; entity->sizey = 4; entity->skill[2] = my->getUID(); @@ -498,7 +598,7 @@ void initGnome(Entity* my, Stat* myStats) my->bodyparts.push_back(entity); // left leg - entity = newEntity(298, 1, map.entities, nullptr); //Limb entity. + entity = newEntity(1470, 1, map.entities, nullptr); //Limb entity. entity->sizex = 4; entity->sizey = 4; entity->skill[2] = my->getUID(); @@ -677,6 +777,7 @@ void actGnomeLimb(Entity* my) void gnomeDie(Entity* my) { + if ( !my ) { return; } for ( int c = 0; c < 10; c++ ) { Entity* entity = spawnGib(my); @@ -690,6 +791,65 @@ void gnomeDie(Entity* my) } } + if ( my->getStats() && my->getStats()->getAttribute("gnome_type").find("gnome2") != std::string::npos ) + { + // underlings flee on leader death + if ( my->getStats() && my->getStats()->killer_uid != 0 ) + { + Entity* killer = uidToEntity(my->getStats()->killer_uid); + if ( killer && (killer->behavior == &actPlayer || killer->behavior == &actMonster) ) + { + bool affected = false; + for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( entity && entity->getStats() && entity->getStats()->leader_uid == my->getUID() ) + { + if ( entity->isMobile() ) + { + Entity* ohit = hit.entity; + real_t tangent = atan2(entity->y - my->y, entity->x - my->x); + lineTraceTarget(my, my->x, my->y, tangent, 128.0, 0, false, entity); // trace to leader + if ( hit.entity != entity ) + { + tangent = atan2(entity->y - killer->y, entity->x - killer->x); + lineTraceTarget(killer, killer->x, killer->y, tangent, 128.0, 0, false, entity); // trace to killer + } + if ( hit.entity == entity ) + { + if ( entity->setEffect(EFF_FEAR, true, TICKS_PER_SECOND * 5, true) ) + { + entity->monsterAcquireAttackTarget(*killer, MONSTER_STATE_PATH); + entity->monsterFearfulOfUid = killer->getUID(); + playSoundEntity(entity, 687, 128); // fear.ogg + affected = true; + } + } + entity->getStats()->leader_uid = 0; + hit.entity = ohit; + } + } + } + if ( affected ) + { + int player = -1; + if ( killer->behavior == &actPlayer ) + { + player = killer->skill[2]; + } + else if ( Entity* leader = killer->monsterAllyGetPlayerLeader() ) + { + player = leader->skill[2]; + } + if ( player >= 0 ) + { + messagePlayerColor(player, MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6265)); + } + } + } + } + } + my->spawnBlood(); my->removeMonsterDeathNodes(); @@ -722,259 +882,264 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) { gnomeVariant = GNOME_THIEF_MELEE; } - - //static bool forceWalk = false; - //if ( keystatus[SDLK_KP_5] ) - //{ - // keystatus[SDLK_KP_5] = 0; - // forceWalk = !forceWalk; - //} - //if ( keystatus[SDLK_KP_6] ) - //{ - // myStats->EFFECTS[EFF_STUNNED] = !myStats->EFFECTS[EFF_STUNNED]; - //} - //if ( forceWalk ) - //{ - // dist = 0.15; - //} - - //if ( keystatus[SDLK_9] ) - //{ - // keystatus[SDLK_9] = 0; - // if ( myStats->helmet ) - // { - // myStats->helmet->appearance++; - // } - //} - //if ( keystatus[SDLK_0] ) - //{ - // keystatus[SDLK_0] = 0; - // if ( myStats->mask ) - // { - // myStats->mask->appearance++; - // } - //} - //if ( keystatus[SDLK_6] ) - //{ - // keystatus[SDLK_6] = 0; - // if ( my->sprite == 295 ) - // { - // my->sprite = 1426; - // } - // else if ( my->sprite == 1426 ) - // { - // my->sprite = 1430; - // } - // else - // { - // my->sprite = 295; - // } - //} - //if ( keystatus[SDLK_7] ) - //{ - // keystatus[SDLK_7] = 0; - // if ( myStats->shoes ) - // { - // while ( true ) - // { - // int type = myStats->shoes->type; - // type++; - // if ( type >= NUMITEMS ) - // { - // if ( myStats->shoes->node ) - // { - // list_RemoveNode(myStats->shoes->node); - // } - // else - // { - // free(myStats->shoes); - // } - // myStats->shoes = nullptr; - // break; - // } - // myStats->shoes->type = (ItemType)type; - // if ( items[myStats->shoes->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BOOTS ) - // { - // break; - // } - // } - // } - // else - // { - // myStats->shoes = newItem(LEATHER_BOOTS, EXCELLENT, 0, 1, 0, true, nullptr); - // } - //} - //if ( keystatus[SDLK_8] ) - //{ - // keystatus[SDLK_8] = 0; - // if ( myStats->gloves ) - // { - // while ( true ) - // { - // int type = myStats->gloves->type; - // type++; - // if ( type >= NUMITEMS ) - // { - // if ( myStats->gloves->node ) - // { - // list_RemoveNode(myStats->gloves->node); - // } - // else - // { - // free(myStats->gloves); - // } - // myStats->gloves = nullptr; - // break; - // } - // myStats->gloves->type = (ItemType)type; - // if ( items[myStats->gloves->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_GLOVES ) - // { - // break; - // } - // } - // } - // else - // { - // myStats->gloves = newItem(GLOVES, EXCELLENT, 0, 1, 0, true, nullptr); - // } - //} - //if ( keystatus[SDLK_g] ) - //{ - // keystatus[SDLK_g] = 0; - // if ( myStats->helmet ) - // { - // if ( keystatus[SDLK_LSHIFT] ) - // { - // while ( true ) - // { - // int type = myStats->helmet->type; - // type--; - // if ( type < 0 ) - // { - // type = NUMITEMS - 1; - // } - // myStats->helmet->type = (ItemType)type; - // if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) - // { - // break; - // } - // } - // } - // else - // { - // while ( true ) - // { - // int type = myStats->helmet->type; - // type++; - // if ( type >= NUMITEMS ) - // { - // type = 0; - // } - // myStats->helmet->type = (ItemType)type; - // if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) - // { - // break; - // } - // } - // } - // } - //} - //if ( keystatus[SDLK_j] ) - //{ - // keystatus[SDLK_j] = 0; - // if ( myStats->breastplate ) - // { - // if ( keystatus[SDLK_LSHIFT] ) - // { - // while ( true ) - // { - // int type = myStats->breastplate->type; - // type--; - // if ( type < 0 ) - // { - // type = NUMITEMS - 1; - // } - // myStats->breastplate->type = (ItemType)type; - // if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) - // { - // break; - // } - // } - // } - // else - // { - // while ( true ) - // { - // int type = myStats->breastplate->type; - // type++; - // if ( type >= NUMITEMS ) - // { - // if ( myStats->breastplate->node ) - // { - // list_RemoveNode(myStats->breastplate->node); - // } - // else - // { - // free(myStats->breastplate); - // } - // myStats->breastplate = nullptr; - // break; - // } - // myStats->breastplate->type = (ItemType)type; - // if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) - // { - // break; - // } - // } - // } - // } - // else - // { - // myStats->breastplate = newItem(LEATHER_BREASTPIECE, EXCELLENT, 0, 1, 0, true, nullptr); - // } - //} - //if ( keystatus[SDLK_h] ) - //{ - // keystatus[SDLK_h] = 0; - // if ( myStats->mask ) - // { - // if ( keystatus[SDLK_LSHIFT] ) - // { - // while ( true ) - // { - // int type = myStats->mask->type; - // type--; - // if ( type < 0 ) - // { - // type = NUMITEMS - 1; - // } - // myStats->mask->type = (ItemType)type; - // if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) - // { - // break; - // } - // } - // } - // else - // { - // while ( true ) - // { - // int type = myStats->mask->type; - // type++; - // if ( type >= NUMITEMS ) - // { - // type = 0; - // } - // myStats->mask->type = (ItemType)type; - // if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) - // { - // break; - // } - // } - // } - // } - //} - } +//#ifndef NDEBUG +// static bool forceWalk = false; +// if ( keystatus[SDLK_KP_5] ) +// { +// keystatus[SDLK_KP_5] = 0; +// forceWalk = !forceWalk; +// } +// if ( keystatus[SDLK_KP_6] ) +// { +// myStats->EFFECTS[EFF_STUNNED] = !myStats->EFFECTS[EFF_STUNNED]; +// } +// if ( forceWalk ) +// { +// dist = 0.15; +// } +// +// if ( keystatus[SDLK_9] ) +// { +// keystatus[SDLK_9] = 0; +// if ( myStats->helmet ) +// { +// myStats->helmet->appearance++; +// } +// } +// if ( keystatus[SDLK_0] ) +// { +// keystatus[SDLK_0] = 0; +// if ( myStats->mask ) +// { +// myStats->mask->appearance++; +// } +// } +// if ( keystatus[SDLK_6] ) +// { +// keystatus[SDLK_6] = 0; +// if ( my->sprite == 295 ) +// { +// my->sprite = 1426; +// } +// else if ( my->sprite == 1426 ) +// { +// my->sprite = 1430; +// } +// else +// { +// my->sprite = 295; +// } +// } +// if ( keystatus[SDLK_7] ) +// { +// keystatus[SDLK_7] = 0; +// if ( myStats->shoes ) +// { +// while ( true ) +// { +// int type = myStats->shoes->type; +// type++; +// if ( type >= NUMITEMS ) +// { +// if ( myStats->shoes->node ) +// { +// list_RemoveNode(myStats->shoes->node); +// } +// else +// { +// free(myStats->shoes); +// } +// myStats->shoes = nullptr; +// break; +// } +// myStats->shoes->type = (ItemType)type; +// if ( items[myStats->shoes->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BOOTS ) +// { +// break; +// } +// } +// } +// else +// { +// myStats->shoes = newItem(LEATHER_BOOTS, EXCELLENT, 0, 1, 0, true, nullptr); +// } +// } +// if ( keystatus[SDLK_8] ) +// { +// keystatus[SDLK_8] = 0; +// if ( myStats->gloves ) +// { +// while ( true ) +// { +// int type = myStats->gloves->type; +// type++; +// if ( type >= NUMITEMS ) +// { +// if ( myStats->gloves->node ) +// { +// list_RemoveNode(myStats->gloves->node); +// } +// else +// { +// free(myStats->gloves); +// } +// myStats->gloves = nullptr; +// break; +// } +// myStats->gloves->type = (ItemType)type; +// if ( items[myStats->gloves->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_GLOVES ) +// { +// break; +// } +// } +// } +// else +// { +// myStats->gloves = newItem(GLOVES, EXCELLENT, 0, 1, 0, true, nullptr); +// } +// } +// if ( keystatus[SDLK_g] ) +// { +// keystatus[SDLK_g] = 0; +// if ( myStats->helmet ) +// { +// if ( keystatus[SDLK_LSHIFT] ) +// { +// while ( true ) +// { +// int type = myStats->helmet->type; +// type--; +// if ( type < 0 ) +// { +// type = NUMITEMS - 1; +// } +// myStats->helmet->type = (ItemType)type; +// if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) +// { +// break; +// } +// } +// } +// else +// { +// while ( true ) +// { +// int type = myStats->helmet->type; +// type++; +// if ( type >= NUMITEMS ) +// { +// type = 0; +// } +// myStats->helmet->type = (ItemType)type; +// if ( items[myStats->helmet->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_HELM ) +// { +// break; +// } +// } +// } +// } +// else +// { +// myStats->helmet = newItem(LEATHER_HELM, EXCELLENT, 0, 1, 0, true, nullptr); +// } +// } +// if ( keystatus[SDLK_j] ) +// { +// keystatus[SDLK_j] = 0; +// if ( myStats->breastplate ) +// { +// if ( keystatus[SDLK_LSHIFT] ) +// { +// while ( true ) +// { +// int type = myStats->breastplate->type; +// type--; +// if ( type < 0 ) +// { +// type = NUMITEMS - 1; +// } +// myStats->breastplate->type = (ItemType)type; +// if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) +// { +// break; +// } +// } +// } +// else +// { +// while ( true ) +// { +// int type = myStats->breastplate->type; +// type++; +// if ( type >= NUMITEMS ) +// { +// if ( myStats->breastplate->node ) +// { +// list_RemoveNode(myStats->breastplate->node); +// } +// else +// { +// free(myStats->breastplate); +// } +// myStats->breastplate = nullptr; +// break; +// } +// myStats->breastplate->type = (ItemType)type; +// if ( items[myStats->breastplate->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_BREASTPLATE ) +// { +// break; +// } +// } +// } +// } +// else +// { +// myStats->breastplate = newItem(LEATHER_BREASTPIECE, EXCELLENT, 0, 1, 0, true, nullptr); +// } +// } +// if ( keystatus[SDLK_h] ) +// { +// keystatus[SDLK_h] = 0; +// if ( myStats->mask ) +// { +// if ( keystatus[SDLK_LSHIFT] ) +// { +// while ( true ) +// { +// int type = myStats->mask->type; +// type--; +// if ( type < 0 ) +// { +// type = NUMITEMS - 1; +// } +// myStats->mask->type = (ItemType)type; +// if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) +// { +// break; +// } +// } +// } +// else +// { +// while ( true ) +// { +// int type = myStats->mask->type; +// type++; +// if ( type >= NUMITEMS ) +// { +// type = 0; +// } +// myStats->mask->type = (ItemType)type; +// if ( items[myStats->mask->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_MASK ) +// { +// break; +// } +// } +// } +// } +// } +//#endif +} my->focalx = limbs[GNOME][0][0]; my->focaly = limbs[GNOME][0][1]; @@ -1062,7 +1227,7 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) } else { - my->z = 2.25; + my->z = 2.75; my->pitch = 0; } } @@ -1183,11 +1348,11 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( gnomeVariant == GNOME_THIEF_RANGED ) { - entity->sprite = 1439; + entity->sprite = 1469; } else { - entity->sprite = gnome_type == "gnome2" ? 1428 : gnome_type == "gnome2F" ? 1432 : 297; + entity->sprite = gnome_type == "gnome2" ? 1428 : gnome_type == "gnome2F" ? 1432 : 1469; } } else @@ -1229,11 +1394,11 @@ void gnomeMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( gnomeVariant == GNOME_THIEF_RANGED ) { - entity->sprite = 1438; + entity->sprite = 1470; } else { - entity->sprite = gnome_type == "gnome2" ? 1429 : gnome_type == "gnome2F" ? 1433 : 298; + entity->sprite = gnome_type == "gnome2" ? 1429 : gnome_type == "gnome2F" ? 1433 : 1470; } } else From 197fbed4a74b35c7d2c8e3660d4bb2dc632b8c22 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 10 Oct 2024 17:03:19 +1100 Subject: [PATCH 162/244] * map hash update --- src/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files.cpp b/src/files.cpp index d227ef0d1..e821cd395 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -943,7 +943,7 @@ std::unordered_map mapHashes = { { "ruins20e.lmp", 369845 }, { "ruins21.lmp", 71662 }, { "ruins21a.lmp", 4165 }, - { "ruins21b.lmp", 9311 }, + { "ruins21b.lmp", 8883 }, { "ruins21c.lmp", 4264 }, { "ruins21d.lmp", 27792 }, { "ruins21e.lmp", 18263 }, From 9a95bc5414229d569945ab73ecd7faead8763b3e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 10 Oct 2024 17:50:04 +1100 Subject: [PATCH 163/244] * misc bugfixes * fear/tinker trap unique sfx * demons/bugbear/slimes fight * fix gyrobot blocking boulder * bat no rest on stalags * thorns msg works for monsters with knife * bugbear more magic resist when blocking * cursed tinker trap always set to trigger all * struct for traps to exlude entities on placement (for monster postmortem kills) --- src/actbeartrap.cpp | 26 ++++++- src/actboulder.cpp | 4 + src/actmonster.cpp | 56 ++++++++++++-- src/actplayer.cpp | 1 + src/collision.cpp | 90 ++++++++++++++++------- src/collision.hpp | 7 ++ src/entity.cpp | 113 ++++++++++++++++++++++------- src/entity.hpp | 8 +- src/entity_editor.cpp | 1 + src/entity_shared.cpp | 3 +- src/game.cpp | 2 + src/game.hpp | 1 + src/interface/clickdescription.cpp | 4 + src/interface/consolecommand.cpp | 27 +++++++ src/interface/interface.cpp | 11 +++ src/interface/interface.hpp | 5 ++ src/item_tool.cpp | 34 +++++++++ src/item_usage_funcs.cpp | 29 +++++--- src/items.hpp | 2 +- src/magic/actmagic.cpp | 14 +++- src/magic/magic.cpp | 2 +- src/menu.cpp | 4 + src/monster_minotaur.cpp | 7 ++ src/net.cpp | 5 ++ src/paths.cpp | 7 +- src/player.cpp | 8 ++ src/stat.cpp | 1 + src/stat.hpp | 1 + src/stat_shared.cpp | 1 + 29 files changed, 397 insertions(+), 77 deletions(-) diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index f7baa400d..4cb59a2a2 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -42,6 +42,8 @@ #define BEARTRAP_IDENTIFIED my->skill[15] #define BEARTRAP_OWNER my->skill[17] +std::map monsterTrapIgnoreEntities; + void actBeartrap(Entity* my) { int i; @@ -101,8 +103,15 @@ void actBeartrap(Entity* my) return; } + MonsterTrapIgnoreEntities_t* trapProps = nullptr; + if ( my->parent != 0 && monsterTrapIgnoreEntities.find(my->getUID()) != monsterTrapIgnoreEntities.end() ) + { + trapProps = &monsterTrapIgnoreEntities[my->getUID()]; + } + // launch beartrap node_t* node; + Entity* parent = uidToEntity(my->parent); for ( node = map.creatures->first; node != nullptr; node = node->next ) { Entity* entity = (Entity*)node->element; @@ -115,11 +124,15 @@ void actBeartrap(Entity* my) Stat* stat = entity->getStats(); if ( stat ) { - Entity* parent = uidToEntity(my->parent); if ( (parent && parent->checkFriend(entity)) ) { continue; } + if ( trapProps && trapProps->parent == my->parent + && trapProps->ignoreEntities.find(entity->getUID()) != trapProps->ignoreEntities.end() ) + { + continue; + } if ( stat->type == GYROBOT || entity->isUntargetableBat() ) { continue; @@ -938,6 +951,12 @@ void actBomb(Entity* my) } } + MonsterTrapIgnoreEntities_t* trapProps = nullptr; + if ( my->parent != 0 && monsterTrapIgnoreEntities.find(my->getUID()) != monsterTrapIgnoreEntities.end() ) + { + trapProps = &monsterTrapIgnoreEntities[my->getUID()]; + } + for ( auto it = entitiesWithinRadius.begin(); it != entitiesWithinRadius.end() && !triggered; ++it ) { Entity* entity = *it; @@ -959,6 +978,11 @@ void actBomb(Entity* my) { continue; } + if ( trapProps && trapProps->parent == my->parent && trapProps->ignoreEntities.find(entity->getUID()) != trapProps->ignoreEntities.end() + && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) ) + { + continue; + } if ( stat->type == GYROBOT || entity->isUntargetableBat() ) { continue; diff --git a/src/actboulder.cpp b/src/actboulder.cpp index e09ae5af1..0bd346416 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -181,6 +181,10 @@ bool doesEntityStopBoulder(Entity* entity) { return true; } + else if ( entity->behavior == &::actDaedalusShrine ) + { + return true; + } return false; } diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 6de5281e8..174041155 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -42,7 +42,7 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1 }, // HUMAN { 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // RAT { 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GOBLIN - { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // SLIME + { 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1 }, // SLIME { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // TROLL { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1 }, // BAT_SMALL { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // SPIDER @@ -52,7 +52,7 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // IMP { 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // CRAB { 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0 }, // GNOME - { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // DEMON + { 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1 }, // DEMON { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0 }, // SUCCUBUS { 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 }, // MIMIC { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0 }, // LICH @@ -75,7 +75,7 @@ bool swornenemies[NUMMONSTERS][NUMMONSTERS] = { 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 }, // SPELLBOT { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // GYROBOT { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // DUMMYBOT - { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 } // BUGBEAR + { 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0 } // BUGBEAR // N H R G S T B S G S S I C G D S M L M D S K S G I V S C I G A L L S S G D B // O U A O L R A P H K C M R N E U I I I E H O C O N A H O N O U I I N P Y U U // T M T B I O T I O E O P A O M C M C N V P B A L C M A C S A T F I T L R M G @@ -3378,6 +3378,12 @@ void actMonster(Entity* my) myStats->mask = NULL; node_t* nextnode = NULL; + int mapIndex = 0; + if ( my->x >= 0 && my->y >= 0 && my->x < map.width << 4 && my->y < map.height << 4 ) + { + mapIndex = (int)(my->y / 16)* MAPLAYERS + (int)(my->x / 16) * MAPLAYERS * map.height; + } + for ( node = myStats->inventory.first; node != NULL; node = nextnode ) { nextnode = node->next; @@ -3393,6 +3399,37 @@ void actMonster(Entity* my) item->isDroppable = false; // sometimes don't drop inventory } } + if ( myStats->type == GNOME ) + { + if ( item->type == TOOL_BEARTRAP || (item->type >= TOOL_BOMB && item->type <= TOOL_TELEPORT_BOMB) ) + { + if ( myStats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) + { + if ( !(swimmingtiles[map.tiles[mapIndex]] || lavatiles[map.tiles[mapIndex]]) ) + { + item->isDroppable = false; + if ( item->type >= TOOL_BOMB && item->type <= TOOL_TELEPORT_BOMB ) + { + item->applyBomb(my, item->type, Item::ItemBombPlacement::BOMB_FLOOR, Item::ItemBombFacingDirection::BOMB_UP, my, nullptr); + } + else if ( item->type == TOOL_BEARTRAP ) + { + Entity* trap = item_ToolBeartrap(item, my); + if ( trap ) + { + trap->x = my->x; + trap->y = my->y; + } + if ( !item ) + { + // consumed + continue; + } + } + } + } + } + } bool wasQuiver = itemTypeIsQuiver(item->type); entity = dropItemMonster(item, my, myStats); // returns nullptr on "undroppables" if ( entity ) @@ -5766,7 +5803,7 @@ void actMonster(Entity* my) playSoundEntity(hit.entity, sound, 64); } } - else if ( hit.entity->behavior == &actBoulder && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) + else if ( (hit.entity->behavior == &actBoulder || hit.entity->behavior == &::actDaedalusShrine) && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) { // asplode the rock magicDig(nullptr, nullptr, 0, 1); @@ -6983,7 +7020,7 @@ void actMonster(Entity* my) hit.entity->colliderCurrentHP = 0; hit.entity->colliderKillerUid = 0; } - else if ( hit.entity->behavior == &actBoulder && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) + else if ( (hit.entity->behavior == &actBoulder || hit.entity->behavior == &::actDaedalusShrine) && !hit.entity->flags[PASSABLE] && myStats->type == MINOTAUR ) { // asplode the rock magicDig(nullptr, nullptr, 0, 1); @@ -10442,6 +10479,8 @@ bool Entity::handleMonsterSpecialAttack(Stat* myStats, Entity* target, double di } deinitSuccess = true; break; + case GNOME: + break; case SUCCUBUS: if ( monsterSpecialState == SUCCUBUS_CHARM || forceDeinit ) { @@ -13201,14 +13240,17 @@ void batResetIdle(Entity* my) for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && canRest; ++it ) { list_t* currentList = *it; - for ( node_t* node = currentList->first; node != nullptr; node = node->next ) //Can't convert to map.creatures because of doorframes. + for ( node_t* node = currentList->first; node != nullptr; node = node->next ) { Entity* entity = (Entity*)node->element; if ( entity == my ) { continue; } - if ( entity->behavior == &actCeilingTile || entity->behavior == &actColliderDecoration || entity->behavior == &actFurniture ) + if ( entity->behavior == &actCeilingTile + || entity->behavior == &actColliderDecoration + || entity->behavior == &actFurniture + || entity->behavior == &actStalagCeiling ) { int x2 = entity->x / 16; int y2 = entity->y / 16; diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 5ec91f336..1a9553b60 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -327,6 +327,7 @@ bool Player::Ghost_t::allowedInteractEntity(Entity& entity) || entity.behavior == &actPowerCrystal || entity.behavior == &actPowerCrystalBase || entity.behavior == &actTeleportShrine + || entity.behavior == &::actDaedalusShrine || entity.behavior == &actTeleporter ) { return true; diff --git a/src/collision.cpp b/src/collision.cpp index a90644ba4..4a7478a53 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -678,39 +678,69 @@ int barony_clear(real_t tx, real_t ty, Entity* my) long xmin = floor((tx - my->sizex)/16), xmax = floor((tx + my->sizex)/16); const real_t tymin = ty - my->sizey, tymax = ty + my->sizey; const real_t txmin = tx - my->sizex, txmax = tx + my->sizex; - for ( y = ymin; y <= ymax; y++ ) + if ( my && my->flags[NOCLIP_WALLS] ) { - for ( x = xmin; x <= xmax; x++ ) + for ( y = ymin; y <= ymax; y++ ) { - if ( x >= 0 && y >= 0 && x < map.width && y < map.height ) + for ( x = xmin; x <= xmax; x++ ) { - if (map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height]) + if ( x >= 0 && y >= 0 && x < map.width && y < map.height ) { - // hit a wall - hit.x = x * 16 + 8; - hit.y = y * 16 + 8; - hit.mapx = x; - hit.mapy = y; - hit.entity = NULL; - return 0; + if ( x == 1 || (x == map.width - 1) || y == 1 || (y == map.height - 1) ) + { + // collides with map edges only + hit.x = x * 16 + 8; + hit.y = y * 16 + 8; + hit.mapx = x; + hit.mapy = y; + hit.entity = NULL; + return 0; + } } - - if ( !levitating && (!map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] - || (((swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !waterWalking) - || (lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !lavaWalking)) - && isMonster)) ) + } + } + if ( my && my->behavior == &actMagiclightMoving ) + { + return 1; // no other collision + } + } + else + { + for ( y = ymin; y <= ymax; y++ ) + { + for ( x = xmin; x <= xmax; x++ ) + { + if ( x >= 0 && y >= 0 && x < map.width && y < map.height ) { - // no floor - hit.x = x * 16 + 8; - hit.y = y * 16 + 8; - hit.mapx = x; - hit.mapy = y; - hit.entity = NULL; - return 0; + if (map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height]) + { + // hit a wall + hit.x = x * 16 + 8; + hit.y = y * 16 + 8; + hit.mapx = x; + hit.mapy = y; + hit.entity = NULL; + return 0; + } + + if ( !levitating && (!map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] + || (((swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !waterWalking) + || (lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] && !lavaWalking)) + && isMonster)) ) + { + // no floor + hit.x = x * 16 + 8; + hit.y = y * 16 + 8; + hit.mapx = x; + hit.mapy = y; + hit.entity = NULL; + return 0; + } } } } } + std::vector entLists; if ( multiplayer == CLIENT ) { @@ -738,7 +768,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) } if ( entity->flags[PASSABLE] ) { - if ( my->behavior == &actBoulder && (entity->sprite == 886) ) + if ( my->behavior == &actBoulder && (entity->behavior == &actMonster && entity->sprite == 886) ) { // 886 is gyrobot, as they are passable, force collision here. } @@ -752,7 +782,8 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { continue; } - if ( entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO) + if ( ((entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO)) + || entity->behavior == &::actDaedalusShrine) && my->behavior == &actMonster && type == MINOTAUR ) { continue; @@ -1257,7 +1288,8 @@ Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int en } if ( ignoreFurniture && (entity->behavior == &actFurniture - || entity->isDamageableCollider()) ) + || entity->isDamageableCollider() + || (entity->behavior == &::actDaedalusShrine && myStats->type == MINOTAUR)) ) { continue; // see through furniture cause we'll bust it down } @@ -1992,8 +2024,10 @@ int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntity { continue; } - if ( isMonster && my->getMonsterTypeFromSprite() == MINOTAUR && entity->isDamageableCollider() - && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO) ) + if ( isMonster && my->getMonsterTypeFromSprite() == MINOTAUR + && ((entity->isDamageableCollider() + && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO)) + || entity->behavior == &::actDaedalusShrine) ) { continue; } diff --git a/src/collision.hpp b/src/collision.hpp index 594d3976b..34b2db7a8 100644 --- a/src/collision.hpp +++ b/src/collision.hpp @@ -34,3 +34,10 @@ Entity* findEntityInLine(Entity* my, real_t x1, real_t y1, real_t angle, int ent real_t lineTrace(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground); real_t lineTraceTarget(Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target); //If the linetrace function encounters the linetrace entity, it returns even if it's invisible or passable. int checkObstacle(long x, long y, Entity* my, Entity* target, bool useTileEntityList = true, bool checkWalls = true, bool checkFloor = true); + +struct MonsterTrapIgnoreEntities_t +{ + std::set ignoreEntities; + Uint32 parent = 0; +}; +extern std::map monsterTrapIgnoreEntities; diff --git a/src/entity.cpp b/src/entity.cpp index 3505e4834..61071b895 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -262,6 +262,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli shrineZ(skill[8]), shrineDestXOffset(skill[9]), shrineDestYOffset(skill[10]), + shrineDaedalusState(skill[11]), ceilingTileModel(skill[0]), ceilingTileDir(skill[1]), ceilingTileAllowTrap(skill[3]), @@ -690,6 +691,8 @@ void Entity::killedByMonsterObituary(Entity* victim) return; } + hitstats->killer_uid = this->getUID(); + if ( behavior == &actMagicTrap ) { hitstats->killer = KilledBy::TRAP_MAGIC; @@ -7486,6 +7489,10 @@ void Entity::attack(int pose, int charge, Entity* target) { entity->z -= 1; } + if ( myStats->type == GNOME ) + { + entity->z -= 2; + } entity->yaw = yaw; entity->sizex = 1; entity->sizey = 1; @@ -10908,7 +10915,7 @@ void Entity::attack(int pose, int charge, Entity* target) if ( thornsEffect != 0 && damage > 0 ) { this->modHP(-abs(thornsEffect)); - if ( myStats->HP <= 0 ) + if ( myStats->HP <= 0 && myStats->OLDHP > myStats->HP ) { hit.entity->awardXP(this, true, true); } @@ -10928,20 +10935,36 @@ void Entity::attack(int pose, int charge, Entity* target) cameravars[player].shakey += 10; } - if ( hit.entity->behavior == &actPlayer ) + if ( player >= 0 ) { - // update enemy bar for attacker - if ( !strcmp(myStats->name, "") ) + Uint32 color = makeColorRGB(255, 0, 0); + const char* thornsMsg = Language::get(6264); // named + if ( !strcmp(hitstats->name, "") || monsterNameIsGeneric(*hitstats) ) { - updateEnemyBar(hit.entity, this, getMonsterLocalizedName(myStats->type).c_str(), myStats->HP, myStats->MAXHP, false, - DamageGib::DMG_DEFAULT); + thornsMsg = Language::get(6263); + } + + if ( !strcmp(hitstats->name, "") ) + { + messagePlayerColor(player, MESSAGE_COMBAT, color, thornsMsg, getMonsterLocalizedName(hitstats->type).c_str()); } else { - updateEnemyBar(hit.entity, this, myStats->name, myStats->HP, myStats->MAXHP, false, - DamageGib::DMG_DEFAULT); + messagePlayerColor(player, MESSAGE_COMBAT, color, thornsMsg, hitstats->name); } } + + // update enemy bar for attacker + if ( !strcmp(myStats->name, "") ) + { + updateEnemyBar(hit.entity, this, getMonsterLocalizedName(myStats->type).c_str(), myStats->HP, myStats->MAXHP, false, + DamageGib::DMG_DEFAULT); + } + else + { + updateEnemyBar(hit.entity, this, myStats->name, myStats->HP, myStats->MAXHP, false, + DamageGib::DMG_DEFAULT); + } } if ( hitstats->type == INCUBUS && !strncmp(hitstats->name, "inner demon", strlen("inner demon")) ) @@ -11017,7 +11040,7 @@ void Entity::attack(int pose, int charge, Entity* target) || (whip && ( (flanking && local_rng.rand() % 5 == 0) || (backstab && local_rng.rand() % 2 == 0) || disarmed) ) || (local_rng.rand() % 4 == 0 && pose == MONSTER_POSE_GOLEM_SMASH) || (local_rng.rand() % 4 == 0 && pose == PLAYER_POSE_GOLEM_SMASH) - || (thornsEffect < 0) + || (thornsEffect < 0 && behavior == &actPlayer) || (local_rng.rand() % 10 == 0 && myStats->type == VAMPIRE && myStats->weapon == nullptr) || (local_rng.rand() % 8 == 0 && myStats->EFFECTS[EFF_VAMPIRICAURA] && (myStats->weapon == nullptr || myStats->type == LICH_FIRE)) ) @@ -14631,7 +14654,6 @@ bool Entity::setBootSprite(Entity* leg, int spriteOffset) case KOBOLD: case GOBLIN: case SKELETON: - case GNOME: case SHADOW: case INCUBUS: case VAMPIRE: @@ -14666,6 +14688,36 @@ bool Entity::setBootSprite(Entity* leg, int spriteOffset) return false; } break; + case GNOME: + if ( myStats->shoes->type == LEATHER_BOOTS || myStats->shoes->type == LEATHER_BOOTS_SPEED ) + { + leg->sprite = 1463 + (spriteOffset > 0 ? 1 : 0); + } + else if ( myStats->shoes->type == IRON_BOOTS || myStats->shoes->type == IRON_BOOTS_WATERWALKING ) + { + leg->sprite = 1467 + (spriteOffset > 0 ? 1 : 0); + } + else if ( myStats->shoes->type >= STEEL_BOOTS && myStats->shoes->type <= STEEL_BOOTS_FEATHER ) + { + leg->sprite = 1471 + (spriteOffset > 0 ? 1 : 0); + } + else if ( myStats->shoes->type == CRYSTAL_BOOTS ) + { + leg->sprite = 1465 + (spriteOffset > 0 ? 1 : 0); + } + else if ( myStats->shoes->type == ARTIFACT_BOOTS ) + { + leg->sprite = 1461 + (spriteOffset > 0 ? 1 : 0); + } + else if ( myStats->shoes->type == SUEDE_BOOTS ) + { + leg->sprite = 1473 + (spriteOffset > 0 ? 1 : 0); + } + else + { + return false; + } + break; default: break; } @@ -18512,6 +18564,16 @@ bool Entity::shouldRetreat(Stat& myStats) { return false; } + else if ( myStats.type == GNOME ) + { + if ( myStats.getAttribute("gnome_type").find("_melee") != std::string::npos ) + { + if ( myStats.leader_uid != 0 ) + { + return false; + } + } + } else if ( myStats.type == LICH_FIRE ) { if ( monsterLichFireMeleeSeq == LICH_ATK_BASICSPELL_SINGLE ) @@ -20750,6 +20812,11 @@ void Entity::setHumanoidLimbOffset(Entity* limb, Monster race, int limbType) { limb->focalz -= 0.25; } + if (limb->sprite == items[MACHINIST_APRON].indexShort) + { + limb->focalx -= 0.25; + limb->focalz += 0.5; + } limb->scalex = limbs[GNOME][11][0]; limb->scaley = limbs[GNOME][11][1]; @@ -20759,35 +20826,31 @@ void Entity::setHumanoidLimbOffset(Entity* limb, Monster race, int limbType) { limb->x += 1.25 * cos(this->yaw + PI / 2); limb->y += 1.25 * sin(this->yaw + PI / 2); - limb->z += 2.75; + limb->z += 2.25; + if ( limb->sprite == 1469 || limb->sprite == 1470 ) + { + limb->focalx += 0.5; + } if ( this->z >= 3.9 && this->z <= 4.1 ) { limb->yaw += PI / 8; limb->pitch = -PI / 2; } - - if ( limb->sprite == 1428 || limb->sprite == 1429 - || limb->sprite == 1432 || limb->sprite == 1433 ) - { - limb->focalz -= 0.25; - } } else if ( limbType == LIMB_HUMANOID_LEFTLEG ) { limb->x -= 1.25 * cos(this->yaw + PI / 2); limb->y -= 1.25 * sin(this->yaw + PI / 2); - limb->z += 2.75; + limb->z += 2.25; + if ( limb->sprite == 1469 || limb->sprite == 1470 ) + { + limb->focalx += 0.5; + } if ( this->z >= 3.9 && this->z <= 4.1 ) { limb->yaw -= PI / 8; limb->pitch = -PI / 2; } - - if ( limb->sprite == 1428 || limb->sprite == 1429 - || limb->sprite == 1432 || limb->sprite == 1433 ) - { - limb->focalz -= 0.25; - } } else if ( limbType == LIMB_HUMANOID_RIGHTARM ) { @@ -22255,7 +22318,7 @@ real_t Entity::getDamageTableMultiplier(Entity* my, Stat& myStats, DamageTableTy if ( damageType == DamageTableType::DAMAGE_TABLE_MAGIC && myStats.type == BUGBEAR && myStats.defending && myStats.shield ) { - damageMultiplier = 0.2; + damageMultiplier = 0.1; } int followerResist = my ? my->getFollowerBonusDamageResist() : 0; if ( followerResist != 0 ) diff --git a/src/entity.hpp b/src/entity.hpp index b63fd33ca..ad992d410 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -34,6 +34,7 @@ #define USERFLAG1 14 #define USERFLAG2 15 #define INVISIBLE_DITHER 16 +#define NOCLIP_WALLS 17 // number of entity skills and fskills static const int NUMENTITYSKILLS = 60; @@ -469,6 +470,7 @@ class Entity Sint32& shrineZ; //skill[8] Sint32& shrineDestXOffset; //skill[9] Sint32& shrineDestYOffset; //skill[10] + Sint32& shrineDaedalusState; // skill[11] //--PUBLIC FURNITURE SKILLS-- Sint32& furnitureType; //skill[0] @@ -784,6 +786,7 @@ class Entity void actTeleporter(); void actMagicTrapCeiling(); void actTeleportShrine(); + void actDaedalusShrine(); void actSpellShrine(); bool magicFallingCollision(); bool magicOrbitingCollision(); @@ -1214,6 +1217,7 @@ void actColliderDecoration(Entity* my); //---Magic entity functions--- void actMagiclightBall(Entity* my); +void actMagiclightMoving(Entity* my); //---Misc act functions--- void actAmbientParticleEffectIdle(Entity* my); @@ -1224,7 +1228,7 @@ void actTextSource(Entity* my); static const int NUM_ITEM_STRINGS = 333; static const int NUM_ITEM_STRINGS_BY_TYPE = 129; -static const int NUM_EDITOR_SPRITES = 190; +static const int NUM_EDITOR_SPRITES = 191; static const int NUM_EDITOR_TILES = 350; // furniture types. @@ -1291,6 +1295,8 @@ bool monsterNameIsGeneric(Stat& monsterStats); // returns true if a monster's na bool playerRequiresBloodToSustain(int player); // vampire type or accursed class void spawnBloodVialOnMonsterDeath(Entity* entity, Stat* hitstats, Entity* killer); +void shrineDaedalusRevealMap(Entity& my); + enum EntityHungerIntervals : int { HUNGER_INTERVAL_OVERSATIATED, diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index a9b690298..f33395882 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -231,6 +231,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli shrineZ(skill[8]), shrineDestXOffset(skill[9]), shrineDestYOffset(skill[10]), + shrineDaedalusState(skill[11]), ceilingTileModel(skill[0]), ceilingTileDir(skill[1]), ceilingTileAllowTrap(skill[3]), diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index eaade434c..3761d992a 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -1015,7 +1015,8 @@ char spriteEditorNameStrings[NUM_EDITOR_SPRITES][64] = "AND GATE", "AND GATE", "BAT", - "BUGBEAR" + "BUGBEAR", + "DAEDALUS SHRINE" }; char monsterEditorNameStrings[NUMMONSTERS][16] = diff --git a/src/game.cpp b/src/game.cpp index cdcef96df..69124d670 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2177,6 +2177,8 @@ void gameLogic(void) EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); particleTimerEmitterHitEntities.clear(); + monsterTrapIgnoreEntities.clear(); + minimapHighlights.clear(); achievementObserver.updateData(); diff --git a/src/game.hpp b/src/game.hpp index ade57a37f..f164460e4 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -294,6 +294,7 @@ void actCustomPortal(Entity* my); void actTeleporter(Entity* my); void actMagicTrapCeiling(Entity* my); void actTeleportShrine(Entity* my); +void actDaedalusShrine(Entity* my); void actSpellShrine(Entity* my); void actExpansionEndGamePortal(Entity* my); void actSoundSource(Entity* my); diff --git a/src/interface/clickdescription.cpp b/src/interface/clickdescription.cpp index 425096b96..9ae461f4e 100644 --- a/src/interface/clickdescription.cpp +++ b/src/interface/clickdescription.cpp @@ -183,6 +183,10 @@ void clickDescription(int player, Entity* entity) { messagePlayer(player, MESSAGE_INSPECTION, Language::get(4307)); } + else if ( entity->behavior == &::actDaedalusShrine ) + { + messagePlayer(player, MESSAGE_INSPECTION, Language::get(6260)); + } else if ( entity->behavior == &actStatue ) { messagePlayer(player, MESSAGE_INSPECTION, Language::get(4308)); diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 526dc9ad7..156e97ede 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -2400,6 +2400,25 @@ namespace ConsoleCommands { mapLevel2(clientnum); }); + static ConsoleCommand ccmd_maplevel3("/maplevel3", "magic mapping for the level (cheat)", []CCMD{ + if ( !(svFlags & SV_FLAG_CHEATS) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); + return; + } + if ( multiplayer != SINGLE ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(299)); + return; + } + + if ( Player::getPlayerInteractEntity(clientnum) ) + { + messagePlayer(clientnum, MESSAGE_MISC, Language::get(412)); + shrineDaedalusRevealMap(*Player::getPlayerInteractEntity(clientnum)); + } + }); + static ConsoleCommand ccmd_drunky("/drunky", "make me drunk (cheat)", []CCMD{ if (!(svFlags & SV_FLAG_CHEATS)) { @@ -5005,6 +5024,14 @@ namespace ConsoleCommands { { if ( Entity* entity = (Entity*)node->element ) { + /*if ( entity->sprite == 119 || entity->sprite == 179 || entity->sprite == 127 ) + { + if ( map.tiles && map.tiles[1 + ((int)entity->y / 16) * MAPLAYERS + ((int)entity->x / 16) * MAPLAYERS * map.height] ) + { + printlog("Map [%s] ceiling collider: %d sprite", f.c_str(), entity->sprite); + } + }*/ + Monster monsterType = NOTHING; switch ( entity->sprite ) { case 27: monsterType = HUMAN; break; diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index 4cf5aeb11..acaee31d3 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -23960,6 +23960,10 @@ CalloutRadialMenu::CalloutType CalloutRadialMenu::getCalloutTypeForEntity(const { type = CALLOUT_TYPE_SHRINE; } + else if ( parent->behavior == &::actDaedalusShrine ) + { + type = CALLOUT_TYPE_SHRINE; + } else if ( parent->behavior == &actBomb || parent->behavior == &actBeartrap ) { type = CALLOUT_TYPE_BOMB_TRAP; @@ -26102,6 +26106,13 @@ bool CalloutRadialMenu::allowedInteractEntity(Entity& selectedEntity, bool updat strcat(interactText, Language::get(4309)); // "shrine" } } + else if ( (selectedEntity.behavior == &::actDaedalusShrine) ) + { + if ( updateInteractText ) + { + strcat(interactText, Language::get(6261)); // "shrine" + } + } else if ( (selectedEntity.behavior == &actTeleporter) && interactWorld ) { if ( updateInteractText ) diff --git a/src/interface/interface.hpp b/src/interface/interface.hpp index 97c91534a..df90a226c 100644 --- a/src/interface/interface.hpp +++ b/src/interface/interface.hpp @@ -225,6 +225,11 @@ void freeInterfaceResources(); void clickDescription(const int player, Entity* entity); void consoleCommand(char const * const command); void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap); +struct MinimapHighlight_t +{ + Uint32 ticks = 0; +}; +extern std::map minimapHighlights; void handleDamageIndicatorTicks(); void drawStatus(const int player); void drawStatusNew(const int player); diff --git a/src/item_tool.cpp b/src/item_tool.cpp index 6ad113cae..d769827c6 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -22,6 +22,7 @@ #include "shops.hpp" #include "prng.hpp" #include "mod_tools.hpp" +#include "collision.hpp" void Item::applySkeletonKey(int player, Entity& entity) { @@ -1098,6 +1099,29 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, entity->skill[20] = dir; entity->skill[21] = type; + if ( parent && parent->behavior == &actMonster ) + { + auto& trapProps = monsterTrapIgnoreEntities[entity->getUID()]; + trapProps.parent = entity->parent; + for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + { + Entity* creature = (Entity*)node->element; + if ( creature && parent->checkFriend(creature) ) + { + trapProps.ignoreEntities.insert(creature->getUID()); + } + } + } + else + { + if ( this->beatitude < 0 ) + { + entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + } + } + + playSoundEntity(entity, 686, 64); + if ( parent && parent->behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdate(parent->skill[2], Compendium_t::CPDM_GADGET_DEPLOYED, type, 1); @@ -1225,6 +1249,11 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, entity->skill[16] = placement; entity->skill[20] = dir; entity->skill[21] = type; + if ( this->beatitude < 0 ) + { + entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + } + playSoundEntity(entity, 686, 64); if ( parent && parent->behavior == &actPlayer ) { @@ -1421,6 +1450,11 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, } entity->skill[20] = dir; entity->skill[21] = type; + if ( this->beatitude < 0 ) + { + entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + } + playSoundEntity(entity, 686, 64); if ( parent && parent->behavior == &actPlayer ) { diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index d0aa3f3f7..5d990d201 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -4221,11 +4221,11 @@ void item_ToolMirror(Item*& item, int player) } } -void item_ToolBeartrap(Item*& item, Entity* usedBy) +Entity* item_ToolBeartrap(Item*& item, Entity* usedBy) { if ( !usedBy ) { - return; + return nullptr; } int player = -1; @@ -4240,7 +4240,7 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) { if ( entityInsideTile(usedBy, u, v, 0) ) // no floor { - return; + return nullptr; } } } @@ -4263,7 +4263,18 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) entity->skill[15] = item->identified; entity->skill[17] = -1; consumeItem(item, player); - return; + + auto& trapProps = monsterTrapIgnoreEntities[entity->getUID()]; + trapProps.parent = entity->parent; + for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + { + Entity* creature = (Entity*)node->element; + if ( creature && usedBy->checkFriend(creature) ) + { + trapProps.ignoreEntities.insert(creature->getUID()); + } + } + return entity; } else if ( usedBy->behavior == &actPlayer ) { @@ -4272,13 +4283,13 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) if ( player < 0 || player >= MAXPLAYERS ) { - return; + return nullptr; } if ( multiplayer == CLIENT ) { consumeItem(item, player); - return; + return nullptr; } bool failed = false; switch ( item->status ) @@ -4315,7 +4326,7 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) playSoundEntity(players[player]->entity, 76, 64); } consumeItem(item, player); - return; + return nullptr; } if ( multiplayer != CLIENT && players[player] ) { @@ -4325,7 +4336,7 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) if ( players[player] == nullptr || players[player]->entity == nullptr ) { consumeItem(item, player); - return; + return nullptr; } Entity* entity = newEntity(668, 1, map.entities, nullptr); //Beartrap entity. @@ -4369,7 +4380,7 @@ void item_ToolBeartrap(Item*& item, Entity* usedBy) { Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BEARTRAP_DEPLOYED, TOOL_BEARTRAP, 1); } - return; + return entity; } void item_Food(Item*& item, int player) diff --git a/src/items.hpp b/src/items.hpp index 991633c2e..fb0a09902 100644 --- a/src/items.hpp +++ b/src/items.hpp @@ -633,7 +633,7 @@ void item_AmuletSexChange(Item* item, int player); void item_ToolTowel(Item*& item, int player); void item_ToolTinOpener(Item* item, int player); void item_ToolMirror(Item*& item, int player); -void item_ToolBeartrap(Item*& item, Entity* usedBy); +Entity* item_ToolBeartrap(Item*& item, Entity* usedBy); void item_Food(Item*& item, int player); void item_FoodTin(Item*& item, int player); void item_FoodAutomaton(Item*& item, int player); diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 749f0d4ef..36120f6cb 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -7863,13 +7863,23 @@ bool magicDig(Entity* parent, Entity* projectile, int numRocks, int randRocks) list_RemoveNode(hit.entity->mynode); return true; } + else if ( hit.entity->behavior == &::actDaedalusShrine ) + { + createParticleRock(hit.entity); + if ( multiplayer == SERVER ) + { + serverSpawnMiscParticles(hit.entity, PARTICLE_EFFECT_ABILITY_ROCK, 78); + } + + playSoundEntity(hit.entity, 67, 128); + list_RemoveNode(hit.entity->mynode); + } else if ( hit.entity->behavior == &actBoulder ) { int i = numRocks + local_rng.rand() % 4; // spawn several rock items //TODO: This should really be its own function. - int c; - for ( c = 0; c < i; c++ ) + for ( int c = 0; c < i; c++ ) { Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock entity. entity->flags[INVISIBLE] = true; diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 0d66b7e35..cd4bbbdaf 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -590,7 +590,7 @@ bool spellEffectFear(Entity* my, spellElement_t& element, Entity* forceParent, E duration /= (1 + resistance); if ( target->setEffect(EFF_FEAR, true, duration, true) ) { - playSoundEntity(target, 168, 128); // Healing.ogg + playSoundEntity(target, 687, 128); // fear.ogg Uint32 color = 0; if ( parent ) { diff --git a/src/menu.cpp b/src/menu.cpp index 163641b88..36aff1d3d 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -8544,6 +8544,8 @@ void doNewGame(bool makeHighscore) { monsterAllyFormations.reset(); PingNetworkStatus_t::reset(); particleTimerEmitterHitEntities.clear(); + monsterTrapIgnoreEntities.clear(); + minimapHighlights.clear(); bool bOldSecretLevel = secretlevel; int oldCurrentLevel = currentlevel; @@ -9985,6 +9987,8 @@ void doEndgame(bool saveHighscore, bool onServerDisconnect) { EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); particleTimerEmitterHitEntities.clear(); + monsterTrapIgnoreEntities.clear(); + minimapHighlights.clear(); PingNetworkStatus_t::reset(); gameModeManager.currentSession.restoreSavedServerFlags(); client_classes[0] = 0; diff --git a/src/monster_minotaur.cpp b/src/monster_minotaur.cpp index 5846bc1aa..5aaf75586 100644 --- a/src/monster_minotaur.cpp +++ b/src/monster_minotaur.cpp @@ -1025,6 +1025,13 @@ void actMinotaurCeilingBuster(Entity* my) } } } + else if ( entity->behavior == &::actDaedalusShrine ) + { + Entity* ohitentity = hit.entity; + hit.entity = entity; + magicDig(my, nullptr, 0, 1); + hit.entity = ohitentity; + } else if ( entity->behavior == &actStalagCeiling || entity->behavior == &actStalagFloor || entity->behavior == &actStalagColumn diff --git a/src/net.cpp b/src/net.cpp index ba866834f..46a981a36 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1919,6 +1919,9 @@ void clientActions(Entity* entity) case 1379: entity->behavior = &actGoldBag; break; + case 1369: + entity->behavior = &actDaedalusShrine; + break; case Player::Ghost_t::GHOST_MODEL_P1: case Player::Ghost_t::GHOST_MODEL_P2: case Player::Ghost_t::GHOST_MODEL_P3: @@ -2190,6 +2193,8 @@ static void changeLevel() { EnemyHPDamageBarHandler::dumpCache(); monsterAllyFormations.reset(); particleTimerEmitterHitEntities.clear(); + monsterTrapIgnoreEntities.clear(); + minimapHighlights.clear(); // clear follower menu entities. FollowerMenu[clientnum].closeFollowerMenuGUI(true); diff --git a/src/paths.cpp b/src/paths.cpp index 998bec76c..19bd6197f 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -372,6 +372,7 @@ int pathCheckObstacle(int x, int y, Entity* my, Entity* target) || entity->sprite == 169 // statue || entity->sprite == 177 // shrine || entity->sprite == 178 // spell shrine + || entity->sprite == 1369 // daedalus shrine ) { if ( (int)floor(entity->x / 16) == u && (int)floor(entity->y / 16) == v ) @@ -654,7 +655,7 @@ list_t* generatePath(int x1, int y1, int x2, int y2, Entity* my, Entity* target, { continue; } - if (stats && stats->type == MINOTAUR && (entity->behavior == &actBoulder || (entity->isDamageableCollider() + if (stats && stats->type == MINOTAUR && (entity->behavior == &actBoulder || entity->behavior == &::actDaedalusShrine || (entity->isDamageableCollider() && (entity->colliderHasCollision & EditorEntityData_t::COLLIDER_COLLISION_FLAG_MINO)))) { // minotaurs bust through boulders, not an obstacle @@ -1247,6 +1248,10 @@ bool isPathObstacle(Entity* entity) { return true; } + else if ( entity->behavior == &::actDaedalusShrine ) + { + return true; + } else if ( entity->behavior == &actStalagColumn || entity->behavior == &actStalagFloor || entity->behavior == &actColumn ) diff --git a/src/player.cpp b/src/player.cpp index 221ee6771..b38b4bc90 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -4076,6 +4076,10 @@ void Player::WorldUI_t::setTooltipActive(Entity& tooltip) { interactText += Language::get(4299); // "Touch shrine"; } + else if ( parent->behavior == &::actDaedalusShrine ) + { + interactText += Language::get(6262); // "Touch shrine"; + } else if ( parent->behavior == &actBomb && parent->skill[21] != 0 ) //skill[21] item type { interactText = Language::get(4039); // "Disarm "; @@ -4226,6 +4230,10 @@ bool entityBlocksTooltipInteraction(const int player, Entity& entity) { return false; } + else if ( entity.behavior == &::actDaedalusShrine ) + { + return false; + } else if ( entity.behavior == &actDoor || entity.behavior == &actFountain || entity.behavior == &actSink || entity.behavior == &actHeadstone || entity.behavior == &actChest || entity.behavior == &actChestLid || entity.behavior == &actBoulder || entity.behavior == &actPlayer || entity.behavior == &actPedestalOrb || entity.behavior == &actPowerCrystalBase diff --git a/src/stat.cpp b/src/stat.cpp index 494d1786e..ae4659782 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -330,6 +330,7 @@ void Stat::clearStats() strcpy(this->obituary, Language::get(1500)); this->killer = KilledBy::UNKNOWN; + this->killer_uid = 0; this->killer_monster = NOTHING; this->killer_item = WOODEN_SHIELD; this->killer_name = ""; diff --git a/src/stat.hpp b/src/stat.hpp index 64c4485ff..e041af578 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -224,6 +224,7 @@ class Stat // Obituary stuff char obituary[128]; KilledBy killer = KilledBy::UNKNOWN; + Uint32 killer_uid = 0; Monster killer_monster; ItemType killer_item; std::string killer_name = ""; diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index dd7e5271e..cbd356626 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -49,6 +49,7 @@ Stat::Stat(Sint32 sprite) : this->bleedInflictedBy = 0; this->killer = KilledBy::UNKNOWN; this->killer_monster = NOTHING; + this->killer_uid = 0; this->killer_item = WOODEN_SHIELD; this->killer_name = ""; this->sex = static_cast(local_rng.rand() % 2); From 2d6e4f5b5b06646bd5f3e0e639f3a654c5f75c4e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 10 Oct 2024 18:40:22 +1100 Subject: [PATCH 164/244] * slight dim compendium not researched entries --- src/ui/MainMenu.cpp | 84 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 2c7c96dcd..97ed7b815 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32757,6 +32757,7 @@ namespace MainMenu { static const int compendiumPageRightInnerHeight = 412 - 114 + 22; static const int compendiumPageRightInnerHeightExpanded = 412; constexpr auto compendiumContentsDefaultColor = makeColor(159, 145, 127, 255); + constexpr auto compendiumContentsDefaultColorNoResearch = makeColor(122, 111, 97, 255); constexpr auto compendiumContentsSelectedColor = makeColor(221, 210, 84, 255); constexpr auto compendiumContentsDivColor = makeColorRGB(42, 22, 18); constexpr auto compendiumLoreCostAvailable = makeColorRGB(255, 255, 255); @@ -36543,14 +36544,41 @@ namespace MainMenu { page_right_number_flourish->disabled = true; } + auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks + : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks + : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks + : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks + : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::unlocks + : nullptr))))); + auto contentsFrame = compendiumFrame->findFrame("contents"); if ( contentsFrame ) { for ( auto& e : contentsFrame->getEntries() ) { - if ( e->color != compendiumContentsDivColor ) + if ( e->color == compendiumContentsSelectedColor ) { - e->color = compendiumContentsDefaultColor; + if ( compendium_current == "achievements" ) + { + e->color = compendiumContentsDefaultColor; + } + else + { + e->color = compendiumContentsDefaultColorNoResearch; + if ( unlockStatus ) + { + auto findUnlock = unlockStatus->find(e->name); + if ( findUnlock != unlockStatus->end() ) + { + if ( findUnlock->second == Compendium_t::UNLOCKED_UNVISITED + || findUnlock->second == Compendium_t::UNLOCKED_VISITED ) + { + e->color = compendiumContentsDefaultColor; + } + } + } + } } } } @@ -36627,14 +36655,6 @@ namespace MainMenu { : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::contents[sorting] : nullptr))))); - auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks - : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks - : (compendium_current == "codex" ? &Compendium_t::CompendiumCodex_t::unlocks - : (compendium_current == "items" ? &Compendium_t::CompendiumItems_t::unlocks - : (compendium_current == "magic" ? &Compendium_t::CompendiumItems_t::unlocks - : (compendium_current == "achievements" ? &Compendium_t::AchievementData_t::unlocks - : nullptr))))); - std::string content = ""; std::string previousEntry = compendium_contents_current[compendium_current]; @@ -37167,7 +37187,7 @@ namespace MainMenu { entry->clickable = false; entry->navigable = false; entry->text = data.second; - entry->color = makeColorRGB(42, 22, 18); + entry->color = compendiumContentsDivColor; contents->addImage(SDL_Rect{ 0, (int)(contents->getEntries().size() - 1) * contents->getEntrySize(), contents->getSize().w, 20 }, 0xFFFFFFFF, @@ -37176,11 +37196,13 @@ namespace MainMenu { } else { + Compendium_t::CompendiumUnlockStatus unlockCurrentStatus = Compendium_t::LOCKED_UNKNOWN; if ( unlockStatus ) { auto findUnlock = unlockStatus->find(data.first); if ( findUnlock != unlockStatus->end() ) { + unlockCurrentStatus = findUnlock->second; unlocked = findUnlock->second > Compendium_t::LOCKED_UNKNOWN; drawNotification = unlocked && (findUnlock->second == Compendium_t::UNLOCKED_UNVISITED @@ -37200,7 +37222,7 @@ namespace MainMenu { if ( !unlocked ) { - entry->color = compendiumContentsDefaultColor; + entry->color = compendiumContentsDefaultColorNoResearch; entry->text = "???"; entry->click = contents_activate_unknown_fn; entry->ctrlClick = contents_activate_unknown_fn; @@ -37209,7 +37231,16 @@ namespace MainMenu { } else { - entry->color = compendiumContentsDefaultColor; + if ( unlockCurrentStatus == Compendium_t::UNLOCKED_UNVISITED + || unlockCurrentStatus == Compendium_t::UNLOCKED_VISITED + || achievementsTab ) + { + entry->color = compendiumContentsDefaultColor; + } + else + { + entry->color = compendiumContentsDefaultColorNoResearch; + } entry->text = data.second; int textWidth = 0; @@ -41226,7 +41257,7 @@ namespace MainMenu { page_right_gradient_bottom->ontop = true; auto page_right_unlock = window->addFrame("page_right_unlock"); - page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 8, 376, 128 }); + page_right_unlock->setSize(SDL_Rect{ page_right->getSize().x + 6, page_right->getSize().y + 8, 376, 256 }); page_right_unlock->setHollow(true); page_right_unlock->setClickable(false); @@ -41303,6 +41334,31 @@ namespace MainMenu { } }); + /*auto page_right_point_hint = page_right_unlock->addField("page_right_point_hint", 128); + page_right_point_hint->setHJustify(Field::justify_t::CENTER); + page_right_point_hint->setVJustify(Field::justify_t::TOP); + page_right_point_hint->setText(Language::get(6266)); + page_right_point_hint->setDisabled(false); + page_right_point_hint->setSize(SDL_Rect{ 0, 128 + 32, page_right_unlock->getSize().w, page_right_unlock->getSize().h - 128 + 32 }); + page_right_point_hint->setFont(smallfont_outline); + page_right_point_hint->setOntop(true); + page_right_point_hint->setColor(makeColor(224, 224, 224, 255)); + page_right_point_hint->setInvisible(true); + page_right_point_hint->setTickCallback([](Widget& widget) { + widget.setInvisible(true); + auto parent = static_cast(widget.getParent()); + if ( parent ) + { + if ( auto btn = parent->findButton("unlock_lore_cost") ) + { + if ( btn->getTextColor() != compendiumLoreCostAvailable ) + { + widget.setInvisible(btn->isInvisible()); + } + } + } + });*/ + /*auto reveal_unlock_badge = page_right_unlock->addImage(SDL_Rect{ 376 - 74, 28, 56, 62 }, 0xFFFFFFFF, "*images/ui/Main Menus/AdventureArchives/A_Icon_BaronyShield_Large_00.png", From 02a5ca08299bc6112689f80adbbefa8c10dc7c6f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 10 Oct 2024 22:11:41 +1100 Subject: [PATCH 165/244] * lang update --- lang/en.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index b4f741d1a..81f236491 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6486,5 +6486,14 @@ Magic Required: %d (%s)# 6257 bugbears# 6258 strikes# 6259 *grunt*# +6260 You see a shrine of Daedalus.# +6261 Daedalus shrine# +6262 Touch Daedalus shrine# +6263 You are hurt attacking the %s!# +6264 You are hurt attacking %s!# +6265 The gnomes' allies cower in fear!# +6266 Not enough Lore Points available. +Unlock Achievements to earn more!# +6267 The exit is revealed!# 6300 END# From dd6d818033d9c4dfb22725a198f1c810dcbee0f6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 03:42:18 +1100 Subject: [PATCH 166/244] * caves map hash --- src/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files.cpp b/src/files.cpp index e821cd395..25fbe0120 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -84,7 +84,7 @@ bool isCurrentHoliday(bool force) { std::unordered_map mapHashes = { { "boss.lmp", 2376307 }, { "bramscastle.lmp", 2944609 }, - { "caves.lmp", 1065461 }, + { "caves.lmp", 777612 }, { "caves00.lmp", 70935 }, { "caves01.lmp", 13350 }, { "caves01a.lmp", 5 }, From 693965a614ccb478443bef3bad408fc59be71f24 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 03:43:11 +1100 Subject: [PATCH 167/244] * stop projectile double hit then miss evasion targets --- src/collision.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/collision.cpp b/src/collision.cpp index 4a7478a53..7aa3da26a 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -503,6 +503,7 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { if ( multiplayer == CLIENT ) { return false; } if ( !projectile ) { return false; } + if ( hit.entity ) { return false; } // we hit something in clipMove already if ( (Sint32)getUID() < 0 ) { return false; From 2c5780af710c2cbace9d3d04a6ea35157f57544e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 03:45:43 +1100 Subject: [PATCH 168/244] * individual slimes to editor --- src/maps.cpp | 33 +++++++++++++++++++++++++++++++++ src/stat_shared.cpp | 5 +++++ 2 files changed, 38 insertions(+) diff --git a/src/maps.cpp b/src/maps.cpp index 82b0ea348..86cfc4a14 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -5750,6 +5750,11 @@ void assignActions(map_t* map) case 77: case 78: case 79: + case 193: + case 194: + case 195: + case 196: + case 197: case 80: case 81: case 82: @@ -5808,6 +5813,11 @@ void assignActions(map_t* map) case 77: monsterType = MINOTAUR; break; case 78: monsterType = SCORPION; break; case 79: monsterType = SLIME; break; + case 193: monsterType = SLIME; break; + case 194: monsterType = SLIME; break; + case 195: monsterType = SLIME; break; + case 196: monsterType = SLIME; break; + case 197: monsterType = SLIME; break; case 80: monsterType = SUCCUBUS; break; case 81: monsterType = RAT; break; case 82: monsterType = GHOUL; break; @@ -5930,6 +5940,29 @@ void assignActions(map_t* map) if ( multiplayer != CLIENT ) { myStats->type = monsterType; + if ( myStats->type == SLIME ) + { + switch ( entity->sprite ) + { + case 193: + myStats->setAttribute("slime_type", "slime green"); + break; + case 194: + myStats->setAttribute("slime_type", "slime blue"); + break; + case 195: + myStats->setAttribute("slime_type", "slime red"); + break; + case 196: + myStats->setAttribute("slime_type", "slime tar"); + break; + case 197: + myStats->setAttribute("slime_type", "slime metal"); + break; + default: + break; + } + } if ( myStats->type == DEVIL ) { auto childEntity = newEntity(72, 1, map->entities, nullptr); diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index cbd356626..a88ecfcf7 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -1115,6 +1115,11 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->HUNGER = 900; break; case 79: + case 193: + case 194: + case 195: + case 196: + case 197: case (1000 + SLIME): stats->type = SLIME; stats->sex = static_cast(local_rng.rand() % 2); From 9dd9e04930c7350d3c6a481aa74c0405e8223251 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 03:46:10 +1100 Subject: [PATCH 169/244] * var cleanup --- src/maps.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index 86cfc4a14..b0676efa4 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -8467,10 +8467,9 @@ void mapLevel(int player) void mapLevel2(int player) { - int x, y; - for ( y = 0; y < map.height; ++y ) + for ( int y = 0; y < map.height; ++y ) { - for ( x = 0; x < map.width; ++x ) + for ( int x = 0; x < map.width; ++x ) { if ( map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] ) { From 26b299bc12a1adcb42cf1386cf27e314f337420e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 03:50:16 +1100 Subject: [PATCH 170/244] * bell/daedalus map spawns --- src/maps.cpp | 502 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 453 insertions(+), 49 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index b0676efa4..a234712c8 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -3383,6 +3383,8 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple std::vector itemsGeneratedList; static ConsoleVariable cvar_underworldshrinetest("/underworldshrinetest", false); + int exit_x = -1; + int exit_y = -1; //printlog("j: %d\n",j); //printlog("numpossiblelocations: %d\n",numpossiblelocations); for ( c = 0; c < std::min(j, numpossiblelocations); ++c ) @@ -3422,77 +3424,306 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( (c == 0 || (minotaurlevel && c < 2)) && (!secretlevel || currentlevel != 7) && (!secretlevel || currentlevel != 20) && std::get(mapParameters) == 0 ) { - if ( strcmp(map.name, "Hell") ) + // daedalus shrine + if ( c == 1 && minotaurlevel && !(secretlevel && (currentlevel == 7 || currentlevel == 20)) ) { - entity = newEntity(11, 1, map.entities, nullptr); // ladder - entity->behavior = &actLadder; - } - else - { - entity = newEntity(45, 1, map.entities, nullptr); // hell uses portals instead - entity->behavior = &actPortal; - entity->skill[3] = 1; // not secret portals though - } + int numShrines = 1; + /*if ( !strncmp(map.name, "The Labyrinth", 13) ) + { + numShrines = 3; + }*/ + std::map> goodspots; - // determine if the ladder generated in a viable location - if ( strncmp(map.name, "Underworld", 10) ) - { - bool nopath = false; - bool hellLadderFix = !strncmp(map.name, "Hell", 4); - std::vector tempPassableEntities; - if ( hellLadderFix ) + // generate in different quadrant than exit + int exitquadrant = 0; + if ( exit_x >= map.width / 2 ) { - for ( node = map.entities->first; node != NULL; node = node->next ) + if ( exit_y >= map.height / 2 ) + { + exitquadrant = 3; // northwest is opposite + } + else + { + exitquadrant = 2; // southwest is opposite + } + } + else + { + if ( exit_y >= map.height / 2 ) + { + exitquadrant = 0; // northeast is opposite + } + else + { + exitquadrant = 1; // southeast is opposite + } + } + + std::vector quadrantOrder; + switch ( exitquadrant ) + { + case 0: + quadrantOrder = { 0, 1, 3, 2 }; + break; + case 1: + quadrantOrder = { 1, 2, 0, 3 }; + break; + case 2: + quadrantOrder = { 2, 3, 1, 0 }; + break; + case 3: + quadrantOrder = { 3, 0, 2, 1 }; + break; + default: + break; + } + + for ( int y = 0; y < map.height; ++y ) + { + for ( int x = 0; x < map.width; ++x ) { - if ( (entity2 = (Entity*)node->element) ) + if ( possiblelocations[y + x * map.height] == true ) { - if ( entity2->sprite == 19 || entity2->sprite == 20 - || entity2->sprite == 113 || entity2->sprite == 114 ) + int quadrant = 0; + if ( x >= map.width / 2 ) { - int entx = entity2->x / 16; - int enty = entity2->y / 16; - if ( !entity2->flags[PASSABLE] ) + if ( y >= map.height / 2 ) { - if ( entx >= startRoomInfo.x1 && entx <= startRoomInfo.x2 - && enty >= startRoomInfo.y1 && enty <= startRoomInfo.y2 ) - { - tempPassableEntities.push_back(entity2); - entity2->flags[PASSABLE] = true; - } + quadrant = 1; // southeast + } + else + { + quadrant = 0; // northeast + } + } + else + { + if ( y >= map.height / 2 ) + { + quadrant = 2; // southwest + } + else + { + quadrant = 3; // northwest } } + goodspots[quadrant].push_back(x + y * 1000); } } } - for ( node = map.entities->first; node != NULL; node = node->next ) + + std::set obstacleSpots; + bool foundspot = false; + while ( quadrantOrder.size() > 0 ) { - entity2 = (Entity*)node->element; - if ( entity2->sprite == 1 ) // note entity->behavior == nullptr at this point + int quadrant = quadrantOrder[0]; + quadrantOrder.erase(quadrantOrder.begin()); + + while ( goodspots[quadrant].size() > 0 ) { - list_t* path = generatePath(x, y, entity2->x / 16, entity2->y / 16, - entity, entity2, GeneratePathTypes::GENERATE_PATH_CHECK_EXIT, hellLadderFix); - if ( path == NULL ) + int index = map_rng.rand() % goodspots[quadrant].size(); + int picked = goodspots[quadrant][index]; + + goodspots[quadrant].erase(goodspots[quadrant].begin() + index); + + int x = picked % 1000; + int y = picked / 1000; + int obstacles = 0; + for ( int x2 = -1; x2 <= 1; x2++ ) + { + for ( int y2 = -1; y2 <= 1; y2++ ) + { + if ( obstacleSpots.find((x + x2) + 1000 * (y + y2)) != obstacleSpots.end() + || checkObstacle((x + x2) * 16, (y + y2) * 16, NULL, NULL, false) ) + { + obstacles++; + obstacleSpots.insert((x + x2) + 1000 * (y + y2)); + if ( obstacles > 1 ) + { + break; + } + } + } + if ( obstacles > 1 ) + { + break; + } + } + if ( obstacles > 1 ) { - nopath = true; + continue; } - else + + // good spot + Entity* entity = newEntity(11, 1, map.entities, nullptr); + entity->behavior = &actLadder; + + // determine if the ladder generated in a viable location + if ( strncmp(map.name, "Underworld", 10) ) { - list_FreeAll(path); - free(path); + bool nopath = false; + bool hellLadderFix = !strncmp(map.name, "Hell", 4); + std::vector tempPassableEntities; + if ( hellLadderFix ) + { + for ( node = map.entities->first; node != NULL; node = node->next ) + { + if ( (entity2 = (Entity*)node->element) ) + { + if ( entity2->sprite == 19 || entity2->sprite == 20 + || entity2->sprite == 113 || entity2->sprite == 114 ) + { + int entx = entity2->x / 16; + int enty = entity2->y / 16; + if ( !entity2->flags[PASSABLE] ) + { + if ( entx >= startRoomInfo.x1 && entx <= startRoomInfo.x2 + && enty >= startRoomInfo.y1 && enty <= startRoomInfo.y2 ) + { + tempPassableEntities.push_back(entity2); + entity2->flags[PASSABLE] = true; + } + } + } + } + } + } + for ( node = map.entities->first; node != NULL; node = node->next ) + { + entity2 = (Entity*)node->element; + if ( entity2->sprite == 1 ) // note entity->behavior == nullptr at this point + { + list_t* path = generatePath(x, y, entity2->x / 16, entity2->y / 16, + entity, entity2, GeneratePathTypes::GENERATE_PATH_CHECK_EXIT, hellLadderFix); + if ( path == NULL ) + { + nopath = true; + } + else + { + list_FreeAll(path); + free(path); + } + break; + } + } + for ( auto ent : tempPassableEntities ) + { + ent->flags[PASSABLE] = false; + } + if ( nopath ) + { + // try again + list_RemoveNode(entity->mynode); + entity = NULL; + break; + } } + + entity->sprite = 190; + entity->behavior = &actDaedalusShrine; + entity->x = 16.0 * x; + entity->y = 16.0 * y; + + --numpossiblelocations; + possiblelocations[y + x * map.height] = false; + + skipPossibleLocationsDecrement = true; + foundspot = true; break; } + + if ( foundspot ) + { + foundspot = false; + --numShrines; + if ( numShrines <= 0 ) + { + break; + } + } } - for ( auto ent : tempPassableEntities ) + } + else + { + // normal exits + if ( strcmp(map.name, "Hell") ) { - ent->flags[PASSABLE] = false; + entity = newEntity(11, 1, map.entities, nullptr); // ladder + entity->behavior = &actLadder; } - if ( nopath ) + else { - // try again - c--; - list_RemoveNode(entity->mynode); - entity = NULL; + entity = newEntity(45, 1, map.entities, nullptr); // hell uses portals instead + entity->behavior = &actPortal; + entity->skill[3] = 1; // not secret portals though + } + + // determine if the ladder generated in a viable location + if ( strncmp(map.name, "Underworld", 10) ) + { + bool nopath = false; + bool hellLadderFix = !strncmp(map.name, "Hell", 4); + std::vector tempPassableEntities; + if ( hellLadderFix ) + { + for ( node = map.entities->first; node != NULL; node = node->next ) + { + if ( (entity2 = (Entity*)node->element) ) + { + if ( entity2->sprite == 19 || entity2->sprite == 20 + || entity2->sprite == 113 || entity2->sprite == 114 ) + { + int entx = entity2->x / 16; + int enty = entity2->y / 16; + if ( !entity2->flags[PASSABLE] ) + { + if ( entx >= startRoomInfo.x1 && entx <= startRoomInfo.x2 + && enty >= startRoomInfo.y1 && enty <= startRoomInfo.y2 ) + { + tempPassableEntities.push_back(entity2); + entity2->flags[PASSABLE] = true; + } + } + } + } + } + } + for ( node = map.entities->first; node != NULL; node = node->next ) + { + entity2 = (Entity*)node->element; + if ( entity2->sprite == 1 ) // note entity->behavior == nullptr at this point + { + list_t* path = generatePath(x, y, entity2->x / 16, entity2->y / 16, + entity, entity2, GeneratePathTypes::GENERATE_PATH_CHECK_EXIT, hellLadderFix); + if ( path == NULL ) + { + nopath = true; + } + else + { + list_FreeAll(path); + free(path); + } + break; + } + } + for ( auto ent : tempPassableEntities ) + { + ent->flags[PASSABLE] = false; + } + if ( nopath ) + { + // try again + c--; + list_RemoveNode(entity->mynode); + entity = NULL; + } + else + { + exit_x = x; + exit_y = y; + } } } } @@ -4131,6 +4362,85 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } } + { + if ( !strcmp(map.name, "The Ruins") ) + { + int numBells = 0; + if ( currentlevel % 2 == 0 ) + { + numBells = 1 + map_rng.rand() % 2; + } + else + { + numBells = 2 + map_rng.rand() % 2; + } + std::vector goodSpots; + for ( int x = 0; x < map.width; ++x ) + { + for ( int y = 0; y < map.height; ++y ) + { + if ( possiblelocations[y + x * map.height] == true ) + { + goodSpots.push_back(x + 10000 * y); + } + } + } + + for ( int c = 0; c < std::min(numBells, (int)goodSpots.size()); ++c ) + { + // choose a random location from those available + int pick = map_rng.rand() % goodSpots.size(); + int x = goodSpots[pick] % 10000; + int y = goodSpots[pick] / 10000; + + goodSpots.erase(goodSpots.begin() + pick); + + bool bellSpot = true; + for ( int x2 = -1; x2 <= 1; x2++ ) + { + for ( int y2 = -1; y2 <= 1; y2++ ) + { + int checkx = x + x2; + int checky = y + y2; + if ( checkx >= 0 && checkx < map.width ) + { + if ( checky >= 0 && checky < map.height ) + { + if ( !possiblelocations[checky + checkx * map.height] ) + { + bellSpot = false; + } + else if ( map.tiles[(MAPLAYERS - 1) + checky * MAPLAYERS + checkx * MAPLAYERS * map.height] + || map.tiles[OBSTACLELAYER + checky * MAPLAYERS + checkx * MAPLAYERS * map.height] ) + { + bellSpot = false; + } + else if ( checkObstacle((checkx) * 16, (checky) * 16, NULL, NULL, false, false) ) + { + bellSpot = false; + } + } + } + } + } + if ( bellSpot ) + { + Entity* bell = newEntity(191, 1, map.entities, nullptr); //Bell entity. + bell->x = x * 16.0; + bell->y = y * 16.0; + + possiblelocations[y + x * map.height] = false; + numpossiblelocations--; + } + else + { + --c; + continue; + } + } + } + } + int numBreakables = std::min(15, numpossiblelocations / 10); struct BreakableNode_t { @@ -4482,7 +4792,12 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int breakableGoodies = breakableLocations.size() * 80 / 100; int breakableMonsters = 0; - const int breakableMonsterLimit = 2 + (currentlevel / LENGTH_OF_LEVEL_REGION) * (1 + map_rng.rand() % 2); + int breakableMonsterLimit = 2 + (currentlevel / LENGTH_OF_LEVEL_REGION) * (1 + map_rng.rand() % 2); + static ConsoleVariable cvar_breakableMonsterLimit("/breakable_monster_limit", 0); + if ( svFlags & SV_FLAG_CHEATS ) + { + breakableMonsterLimit = std::max(*cvar_breakableMonsterLimit, breakableMonsterLimit); + } if ( findBreakables != EditorEntityData_t::colliderRandomGenPool.end() && findBreakables->second.size() > 0 && breakableGoodies > 0 ) { int breakableItemsFromGround = 0; @@ -4541,11 +4856,14 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple int index = (y) * MAPLAYERS + (x) * MAPLAYERS * map.height; + static ConsoleVariable cvar_breakableMonsterChance("/breakable_monster_chance", 10); + if ( !map.tiles[index] && map_rng.rand() % 2 == 1 ) { // nothing over pits 50% } - else if ( (breakableMonsters < breakableMonsterLimit && monsterEventExists && map_rng.rand() % 10 == 0) + else if ( (breakableMonsters < breakableMonsterLimit && monsterEventExists + && map_rng.rand() % ((svFlags & SV_FLAG_CHEATS) ? std::min(10, *cvar_breakableMonsterChance) : 10) == 0) && map.monsterexcludelocations[x + y * map.width] == false ) // 10% monster inside { Monster monsterEvent = NOTHING; @@ -8165,6 +8483,92 @@ void assignActions(map_t* map) entity->sprite = -1; break; } + case 190: + entity->x += 8; + entity->y += 8; + entity->sizex = 4; + entity->sizey = 4; + entity->behavior = &actDaedalusShrine; + entity->flags[PASSABLE] = false; + entity->z = -0.25; + entity->sprite = 1481; + //entity->focalx = 0.75; + entity->yaw = (map_rng.rand() % 360) * PI / 180.0; + entity->seedEntityRNG(map_rng.getU32()); + { + Entity* childEntity = newEntity(1480, 1, map->entities, nullptr); // base + childEntity->parent = entity->getUID(); + childEntity->x = entity->x; + childEntity->y = entity->y; + childEntity->z = entity->z + 6.5; + childEntity->yaw = 0.0; + childEntity->sizex = 4; + childEntity->sizey = 4; + childEntity->flags[PASSABLE] = true; + childEntity->flags[UNCLICKABLE] = false; + TileEntityList.addEntity(*childEntity); + } + break; + case 191: + { + entity->x += 8; + entity->y += 8; + entity->sizex = 4; + entity->sizey = 4; + entity->z = 0.0; + entity->behavior = &actBell; + entity->flags[PASSABLE] = true; + entity->flags[BLOCKSIGHT] = false; + entity->sprite = 1478; // rope + entity->seedEntityRNG(map_rng.getU32()); + entity->skill[11] = map_rng.rand(); // buff type + { + Entity* childEntity = newEntity(1475, 1, map->entities, nullptr); // bell + childEntity->parent = entity->getUID(); + childEntity->x = entity->x - 2 * cos(entity->yaw); + childEntity->y = entity->y - 2 * sin(entity->yaw); + childEntity->z = -22.25; + childEntity->yaw = entity->yaw; + childEntity->sizex = 6; + childEntity->sizey = 6; + childEntity->flags[PASSABLE] = true; + childEntity->flags[UNCLICKABLE] = false; + childEntity->flags[UPDATENEEDED] = true; + childEntity->z = entity->z; + TileEntityList.addEntity(*childEntity); + node_t* tempNode = list_AddNodeLast(&entity->children); + tempNode->element = childEntity; // add the node to the children list. + tempNode->deconstructor = &emptyDeconstructor; + tempNode->size = sizeof(Entity*); + } + + auto& bellRng = entity->entity_rng ? *entity->entity_rng : map_rng; + int roll = bellRng.rand() % 4; + if ( roll == 0 ) + { + Entity* itemEntity = newEntity(8, 1, map->entities, nullptr); // item + setSpriteAttributes(itemEntity, nullptr, nullptr); + itemEntity->x = entity->x - 8.0; + itemEntity->y = entity->y - 8.0; + itemEntity->z = -16; + itemEntity->flags[INVISIBLE] = true; + itemEntity->itemContainer = entity->getUID(); + itemEntity->yaw = entity->yaw; + itemEntity->skill[16] = SPELLBOOK + 1; + entity->skill[1] = itemEntity->getUID(); + } + else if ( roll == 1 ) + { + Entity* goldEntity = newEntity(9, 1, map->entities, nullptr); // gold + goldEntity->x = entity->x - 8.0; + goldEntity->y = entity->y - 8.0; + goldEntity->z = -16; + goldEntity->goldAmount = 50 + bellRng.rand() % 50; + goldEntity->flags[INVISIBLE] = true; + entity->skill[1] = goldEntity->getUID(); + } + } + break; default: break; } From dfa41eae89224522de2f3ebd0f2cd1da88ac2e7e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:02:54 +1100 Subject: [PATCH 171/244] * daedalus particle, minimap highlight, damage gib refactor to draw arbitrary sprites --- src/draw.cpp | 4 + src/interface/clickdescription.cpp | 6 +- src/interface/consolecommand.cpp | 7 +- src/interface/drawminimap.cpp | 157 +++++++++++++++++++++++++++++ src/interface/interface.cpp | 21 +++- src/interface/interface.hpp | 9 +- src/magic/actmagic.cpp | 91 ++++++++++++++++- src/magic/castSpell.cpp | 4 +- src/magic/magic.hpp | 1 + src/opengl.cpp | 18 +++- src/stat.hpp | 7 +- 11 files changed, 309 insertions(+), 16 deletions(-) diff --git a/src/draw.cpp b/src/draw.cpp index d7100246f..d6cf48a73 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -2380,6 +2380,10 @@ void drawEntities3D(view_t* camera, int mode) { glDrawSpriteFromImage(camera, entity, Language::get(6249), mode); } + else if ( entity->skill[7] == 2 ) + { + glDrawSprite(camera, entity, mode); + } else { snprintf(buf, sizeof(buf), "%d", entity->skill[0]); diff --git a/src/interface/clickdescription.cpp b/src/interface/clickdescription.cpp index 9ae461f4e..b429b7c10 100644 --- a/src/interface/clickdescription.cpp +++ b/src/interface/clickdescription.cpp @@ -115,6 +115,10 @@ void clickDescription(int player, Entity* entity) { messagePlayer(player, MESSAGE_INSPECTION, Language::get(254), Language::get(entity->getColliderLangName())); } + else if ( entity->behavior == &actBell || entity->sprite == 1475 || entity->sprite == 1476 || entity->sprite == 1477 || entity->sprite == 1478 ) + { + messagePlayer(player, MESSAGE_INSPECTION, Language::get(254), Language::get(6269)); + } else if ( entity->behavior == &actItem ) { item = newItem(static_cast(entity->skill[10]), static_cast(entity->skill[11]), entity->skill[12], entity->skill[13], entity->skill[14], false, NULL); @@ -183,7 +187,7 @@ void clickDescription(int player, Entity* entity) { messagePlayer(player, MESSAGE_INSPECTION, Language::get(4307)); } - else if ( entity->behavior == &::actDaedalusShrine ) + else if ( entity->behavior == &::actDaedalusShrine || entity->sprite == 1480 ) { messagePlayer(player, MESSAGE_INSPECTION, Language::get(6260)); } diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 156e97ede..06050adb6 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -2745,7 +2745,12 @@ namespace ConsoleCommands { } else { - players[clientnum]->entity->setEffect(effect, true, 500, true); + int duration = 500; + if ( argc >= 3 ) + { + duration = atoi(argv[2]); + } + players[clientnum]->entity->setEffect(effect, true, duration, true); } }); diff --git a/src/interface/drawminimap.cpp b/src/interface/drawminimap.cpp index 5c76bd9ff..d59acbaa2 100644 --- a/src/interface/drawminimap.cpp +++ b/src/interface/drawminimap.cpp @@ -96,6 +96,8 @@ inline real_t getMinimapZoom() return minimapObjectZoom + 50; } +std::map minimapHighlights; + void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) { if ( loading ) @@ -226,6 +228,7 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) Uint8 backgroundAlpha = 255 * ((100 - minimapTransparencyBackground) / 100.f); Uint8 foregroundAlpha = 255 * ((100 - minimapTransparencyForeground) / 100.f); auto foundCustomWall = customWalls.find(x + y * 10000); + int mapkey = x + y * 10000; if ( x < 0 || y < 0 || x >= map.width || y >= map.height ) { // out-of-bounds @@ -233,6 +236,27 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) } else { + auto find = minimapHighlights.find(mapkey); + if ( find != minimapHighlights.end() ) + { + if ( find->second.ticks <= ticks ) + { + if ( map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + if ( !minimap[y][x] ) + { + minimap[y][x] = 4; + } + } + else if ( map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height] ) + { + if ( !minimap[y][x] ) + { + minimap[y][x] = 3; + } + } + } + } Sint8 mapIndex = minimap[y][x]; if ( foundCustomWall != customWalls.end() ) { @@ -265,6 +289,36 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) // mapped but undiscovered wall color = makeColor(128, 128, 128, foregroundAlpha); } + + + if ( find != minimapHighlights.end() ) + { + real_t percent = 100.0; + if ( find->second.ticks > ticks ) + { + percent = 0.0; + } + else if ( (ticks - find->second.ticks) > TICKS_PER_SECOND ) + { + percent = std::max(0.0, percent - 4 * ((ticks - find->second.ticks) - TICKS_PER_SECOND)); + if ( percent < 0.01 ) + { + minimapHighlights.erase(mapkey); + } + } + percent /= 100.0; + if ( percent > 0.0 ) + { + Uint8 r, g, b, a; + int r2, g2, b2, a2; + getColor(color, &r, &g, &b, &a); + r2 = std::min(255, std::max(0, (int)(r + 255.0 * percent))); + g2 = std::min(255, std::max(0, (int)(g - 128.0 * percent))); + b2 = std::min(255, std::max(0, (int)(b + 255.0 * percent))); + a2 = std::min(255, std::max(64, (int)(a - 64 * percent))); + color = makeColor(r2, g2, b2, a2); + } + } } putPixel(minimapSurface, x - xmin, y - ymin, color); } @@ -1189,3 +1243,106 @@ void minimapPingAdd(const int srcPlayer, const int destPlayer, MinimapPing newPi } minimapPings[destPlayer].insert(minimapPings[destPlayer].begin(), newPing); } + +static ConsoleVariable cvar_shrine_reveal_steps("/shrine_reveal_steps", 8.0); +void shrineDaedalusRevealMap(Entity& my) +{ + Entity* exitEntity = nullptr; + for ( node_t* node = map.entities->first; node; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( !entity ) { continue; } + + if ( (entity->behavior == &actLadder && strcmp(map.name, "Hell")) + || (entity->behavior == &actPortal && !strcmp(map.name, "Hell")) ) + { + if ( entity->behavior == &actLadder && entity->skill[3] != 1 ) + { + exitEntity = entity; + break; + } + if ( entity->behavior == &actPortal && entity->portalNotSecret == 1 ) + { + exitEntity = entity; + break; + } + } + } + + if ( !exitEntity ) + { + return; + } + + minimapHighlights.clear(); + + real_t tangent = atan2(exitEntity->y - my.y, exitEntity->x - my.x); + + if ( Entity* lightball = newEntity(1482, 1, map.entities, nullptr) ) + { + lightball->x = my.x; + lightball->y = my.y; + lightball->z = -2; + lightball->vel_x = 0.33 * *cvar_shrine_reveal_steps * cos(tangent); + lightball->vel_y = 0.33 * *cvar_shrine_reveal_steps * sin(tangent); + lightball->yaw = tangent; + lightball->parent = my.getUID(); + lightball->behavior = &actMagiclightMoving; + lightball->skill[0] = TICKS_PER_SECOND * 10; + lightball->skill[1] = 1; + lightball->flags[NOUPDATE] = true; + lightball->flags[PASSABLE] = true; + lightball->flags[UNCLICKABLE] = true; + lightball->flags[NOCLIP_WALLS] = true; + lightball->flags[INVISIBLE] = true; + lightball->flags[INVISIBLE_DITHER] = true; + if ( multiplayer != CLIENT ) + { + entity_uids--; + } + lightball->setUID(-3); + } + + real_t dist = entityDist(&my, exitEntity); + std::set visited; + real_t d = 0.0; + std::vector checkCoords; + Uint32 tickOffset = ticks; + while ( d < dist ) + { + checkCoords.clear(); + + { + real_t tx = (my.x + d * cos(tangent)) / 16.0; + real_t ty = (my.y + d * sin(tangent)) / 16.0; + checkCoords.push_back(static_cast(tx) + 10000 * static_cast(ty)); + + tx = (my.x + d * cos(tangent) + (16.0 * cos(tangent + PI / 2))) / 16.0; + ty = (my.y + d * sin(tangent) + (16.0 * sin(tangent + PI / 2))) / 16.0; + checkCoords.push_back(static_cast(tx) + 10000 * static_cast(ty)); + + tx = (my.x + d * cos(tangent) + (16.0 * cos(tangent - PI / 2))) / 16.0; + ty = (my.y + d * sin(tangent) + (16.0 * sin(tangent - PI / 2))) / 16.0; + checkCoords.push_back(static_cast(tx) + 10000 * static_cast(ty)); + } + + for ( auto coord : checkCoords ) + { + int x = coord % 10000; + int y = coord / 10000; + if ( x < map.width && y < map.height ) + { + if ( visited.find(x + 10000 * y) == visited.end() ) + { + visited.insert(x + 10000 * y); + minimapHighlights[x + 10000 * y].ticks = tickOffset; + } + } + } + + d += *cvar_shrine_reveal_steps; + tickOffset += TICKS_PER_SECOND / 25; + } + + playSoundPlayer(clientnum, 167, 128); +} \ No newline at end of file diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index acaee31d3..1aea0ec97 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -23405,6 +23405,12 @@ std::string CalloutRadialMenu::setCalloutText(Field* field, const char* iconName case CALLOUT_TYPE_SHRINE: key = "shrine"; break; + case CALLOUT_TYPE_BELL: + key = "bell"; + break; + case CALLOUT_TYPE_DAEDALUS: + key = "daedalus"; + break; case CALLOUT_TYPE_EXIT: key = "exit"; break; @@ -23962,7 +23968,11 @@ CalloutRadialMenu::CalloutType CalloutRadialMenu::getCalloutTypeForEntity(const } else if ( parent->behavior == &::actDaedalusShrine ) { - type = CALLOUT_TYPE_SHRINE; + type = CALLOUT_TYPE_DAEDALUS; + } + else if ( parent->behavior == &actBell ) + { + type = CALLOUT_TYPE_BELL; } else if ( parent->behavior == &actBomb || parent->behavior == &actBeartrap ) { @@ -26106,7 +26116,7 @@ bool CalloutRadialMenu::allowedInteractEntity(Entity& selectedEntity, bool updat strcat(interactText, Language::get(4309)); // "shrine" } } - else if ( (selectedEntity.behavior == &::actDaedalusShrine) ) + else if ( (selectedEntity.behavior == &::actDaedalusShrine) && interactWorld ) { if ( updateInteractText ) { @@ -26131,6 +26141,13 @@ bool CalloutRadialMenu::allowedInteractEntity(Entity& selectedEntity, bool updat } } } + else if ( (selectedEntity.behavior == &actBell) && interactWorld ) + { + if ( updateInteractText ) + { + strcat(interactText, Language::get(6270)); // "bell" + } + } else if ( selectedEntity.behavior == &actLadder ) { if ( updateInteractText ) diff --git a/src/interface/interface.hpp b/src/interface/interface.hpp index df90a226c..b892e48fa 100644 --- a/src/interface/interface.hpp +++ b/src/interface/interface.hpp @@ -74,6 +74,11 @@ enum DamageGib { DMG_MISS, DMG_TODO }; +enum DamageGibDisplayType { + DMG_GIB_NUMBER, + DMG_GIB_MISS, + DMG_GIB_SPRITE +}; class EnemyHPDamageBarHandler { public: @@ -1487,7 +1492,9 @@ struct CalloutRadialMenu CALLOUT_TYPE_TELEPORTER_LADDER_DOWN, CALLOUT_TYPE_TELEPORTER_PORTAL, CALLOUT_TYPE_BOMB_TRAP, - CALLOUT_TYPE_COLLIDER_BREAKABLE + CALLOUT_TYPE_COLLIDER_BREAKABLE, + CALLOUT_TYPE_BELL, + CALLOUT_TYPE_DAEDALUS /*,CALLOUT_TYPE_PEDESTAL*/ }; enum CalloutHelpFlags : int diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 36120f6cb..0d6f6a699 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -78,6 +78,68 @@ static const char* colorForSprite(Entity* my, int sprite, bool darker) { } } +void actMagiclightMoving(Entity* my) +{ + Entity* caster = NULL; + if ( !my ) + { + return; + } + + my->removeLightField(); + if ( my->skill[0] <= 0 ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + return; + } + --my->skill[0]; + + if ( my->skill[1] == 1 ) + { + my->light = addLight(my->x / 16, my->y / 16, "magic_daedalus_reveal"); + } + + if ( my->sprite >= 0 ) + { + if ( Entity* particle = spawnMagicParticle(my) ) + { + particle->x = my->x; + particle->y = my->y; + //particle->z = my->z; + particle->flags[INVISIBLE] = my->flags[INVISIBLE]; + particle->flags[INVISIBLE_DITHER] = my->flags[INVISIBLE_DITHER]; + } + } + + real_t dist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my); + if ( dist != sqrt(my->vel_x * my->vel_x + my->vel_y * my->vel_y) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + return; + } + + if ( Entity* parent = uidToEntity(my->parent) ) + { + if ( parent->behavior == &actDaedalusShrine && parent->skill[13] != 0 ) // shrine source + { + if ( Entity* exitEntity = uidToEntity(parent->skill[13]) ) + { + if ( (int)(my->x / 16) == (int)(exitEntity->x / 16) ) + { + if ( (int)(my->y / 16) == (int)(exitEntity->y / 16) ) + { + my->removeLightField(); + list_RemoveNode(my->mynode); + return; + } + } + } + } + } +} + void actMagiclightBall(Entity* my) { Entity* caster = NULL; @@ -4596,9 +4658,31 @@ void actMagicParticle(Entity* my) my->scaley -= 0.05; my->scalez -= 0.05; } - my->scalex -= 0.05; - my->scaley -= 0.05; - my->scalez -= 0.05; + + if ( my->sprite == 1479 ) + { + my->scalex -= 0.025; + my->scaley -= 0.025; + my->scalez -= 0.025; + my->pitch += 0.25; + my->yaw += 0.25; + //if ( my->parent == 0 && local_rng.rand() % 10 == 0 ) + //{ + // if ( Entity* particle = spawnMagicParticle(my) ) + // { + // particle->parent = my->getUID(); + // particle->x = my->x; + // particle->y = my->y; + // //particle->z = my->z; + // } + //} + } + else + { + my->scalex -= 0.05; + my->scaley -= 0.05; + my->scalez -= 0.05; + } if ( my->scalex <= 0 ) { my->scalex = 0; @@ -4698,6 +4782,7 @@ Entity* spawnMagicParticle(Entity* parentent) entity->x = parentent->x + (local_rng.rand() % 50 - 25) / 20.f; entity->y = parentent->y + (local_rng.rand() % 50 - 25) / 20.f; entity->z = parentent->z + (local_rng.rand() % 50 - 25) / 20.f; + entity->parent = 0; entity->scalex = 0.7; entity->scaley = 0.7; entity->scalez = 0.7; diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 7dacf7967..32f63db1a 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -1532,7 +1532,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool totalHeal += std::max(players[i]->entity->getHP() - oldHP, 0); if ( totalHeal > 0 ) { - spawnDamageGib(players[i]->entity, -totalHeal, DamageGib::DMG_HEAL, false, true); + spawnDamageGib(players[i]->entity, -totalHeal, DamageGib::DMG_HEAL, DamageGibDisplayType::DMG_GIB_NUMBER, true); } playSoundEntity(caster, 168, 128); @@ -1556,7 +1556,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool totalHeal += heal; if ( heal > 0 ) { - spawnDamageGib(entity, -heal, DamageGib::DMG_HEAL, false, true); + spawnDamageGib(entity, -heal, DamageGib::DMG_HEAL, DamageGibDisplayType::DMG_GIB_NUMBER, true); } playSoundEntity(entity, 168, 128); spawnMagicEffectParticles(entity->x, entity->y, entity->z, 169); diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index 9654b5c89..a7033662b 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -548,6 +548,7 @@ void actHUDMagicParticleCircling(Entity* my); Entity* spawnMagicParticle(Entity* parentent); Entity* spawnMagicParticleCustom(Entity* parentent, int sprite, real_t scale, real_t spreadReduce); void spawnMagicEffectParticles(Sint16 x, Sint16 y, Sint16 z, Uint32 sprite); +void spawnMagicEffectParticlesBell(Entity* bell, Uint32 sprite); void createParticleCircling(Entity* parent, int duration, int sprite); void actParticleCircle(Entity* my); void actParticleDot(Entity* my); diff --git a/src/opengl.cpp b/src/opengl.cpp index a07223051..679d989b4 100644 --- a/src/opengl.cpp +++ b/src/opengl.cpp @@ -1719,6 +1719,10 @@ void glDrawWorldUISprite(view_t* camera, Entity* entity, int mode) #endif } +#ifndef EDITOR +static ConsoleVariable cvar_dmgSpriteDepthRange("/dmg_sprite_depth_range", 0.49); +#endif // !EDITOR + void glDrawSprite(view_t* camera, Entity* entity, int mode) { // bind texture @@ -1741,6 +1745,14 @@ void glDrawSprite(view_t* camera, Entity* entity, int mode) if (entity->flags[OVERDRAW]) { GL_CHECK_ERR(glDepthRange(0, 0.1)); } + else + { + if ( entity->behavior == &actDamageGib ) { +#ifndef EDITOR + GL_CHECK_ERR(glDepthRange(0.f, *cvar_dmgSpriteDepthRange)); +#endif // !EDITOR + } + } // bind shader auto& dither = entity->dithering[camera]; @@ -1809,15 +1821,11 @@ void glDrawSprite(view_t* camera, Entity* entity, int mode) if (mode == REALCOLORS) { GL_CHECK_ERR(glDisable(GL_BLEND)); } - if (entity->flags[OVERDRAW]) { + if (entity->flags[OVERDRAW] || entity->behavior == &actDamageGib) { GL_CHECK_ERR(glDepthRange(0.f, 1.f)); } } -#ifndef EDITOR -static ConsoleVariable cvar_dmgSpriteDepthRange("/dmg_sprite_depth_range", 0.49); -#endif // !EDITOR - void glDrawSpriteFromImage(view_t* camera, Entity* entity, std::string text, int mode, bool useTextAsImgPath, bool rotate) { if (!camera || !entity || text.empty()) { diff --git a/src/stat.hpp b/src/stat.hpp index e041af578..84378f30f 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -63,6 +63,10 @@ static const int EFF_DISTRACTED_COOLDOWN = 38; static const int EFF_MIMIC_LOCKED = 39; static const int EFF_ROOTED = 40; static const int EFF_NAUSEA_PROTECTION = 41; +static const int EFF_CON_BONUS = 42; +static const int EFF_PWR = 43; +static const int EFF_AGILITY = 44; +static const int EFF_RALLY = 45; static const int NUMEFFECTS = 64; // stats @@ -206,7 +210,8 @@ enum KilledBy { FOUNTAIN, SINK, FAILED_ALCHEMY, - FAILED_CHALLENGE + FAILED_CHALLENGE, + BELL }; class Stat From 06ce1c74c03d68bc8cf3451d6b82498770aba851 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:06:36 +1100 Subject: [PATCH 172/244] * new obituaries, gib stuff from previous commit, bell/daedalus interacts --- src/actgib.cpp | 16 +++++++--- src/actmonster.cpp | 1 + src/actsprite.cpp | 5 +++ src/entity.hpp | 4 ++- src/entity_shared.cpp | 17 +++++++++- src/game.hpp | 4 ++- src/net.cpp | 74 +++++++++++++++++++++++++++++++++++++++++-- src/paths.cpp | 2 +- src/player.cpp | 21 ++++++++++++ src/ui/MainMenu.cpp | 6 ++++ 10 files changed, 139 insertions(+), 11 deletions(-) diff --git a/src/actgib.cpp b/src/actgib.cpp index f00bd6c29..4a0d36cef 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -405,14 +405,14 @@ Entity* spawnGib(Entity* parentent, int customGibSprite) return entity; } -Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool miss, bool updateClients) +Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int displayType, bool updateClients) { if ( !parentent ) { return nullptr; } - Entity* entity = newEntity(-1, 1, map.entities, nullptr); + Entity* entity = newEntity(displayType == DamageGibDisplayType::DMG_GIB_SPRITE ? dmgAmount : -1, 1, map.entities, nullptr); if ( !entity ) { return nullptr; @@ -441,9 +441,17 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool entity->scaley = 0.2; entity->scalez = 0.2; entity->skill[0] = dmgAmount; + if ( displayType == DamageGibDisplayType::DMG_GIB_SPRITE ) + { + entity->scalex = 0.05; + entity->scaley = 0.05; + entity->scalez = 0.05; + entity->skill[0] = 0; + entity->flags[BRIGHT] = true; + } entity->skill[3] = gibDmgType; entity->fskill[3] = 0.04; - entity->skill[7] = miss ? 1 : 0; + entity->skill[7] = displayType; entity->behavior = &actDamageGib; entity->ditheringDisabled = true; entity->flags[SPRITE] = true; @@ -507,7 +515,7 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool SDLNet_Write32(parentent->getUID(), &net_packet->data[4]); SDLNet_Write16((Sint16)dmgAmount, &net_packet->data[8]); net_packet->data[10] = gibDmgType; - net_packet->data[11] = miss ? 1 : 0; + net_packet->data[11] = displayType; net_packet->address.host = net_clients[c - 1].host; net_packet->address.port = net_clients[c - 1].port; net_packet->len = 12; diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 174041155..aebf6fc31 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -13250,6 +13250,7 @@ void batResetIdle(Entity* my) if ( entity->behavior == &actCeilingTile || entity->behavior == &actColliderDecoration || entity->behavior == &actFurniture + || entity->behavior == &actBell || entity->behavior == &actStalagCeiling ) { int x2 = entity->x / 16; diff --git a/src/actsprite.cpp b/src/actsprite.cpp index d5afd9291..846d2b077 100644 --- a/src/actsprite.cpp +++ b/src/actsprite.cpp @@ -122,6 +122,11 @@ void actSpriteWorldTooltip(Entity* my) } } } + else if ( parent->behavior == &actBell ) + { + my->x += parent->focalx * cos(parent->yaw) + parent->focaly * cos(parent->yaw + PI / 2); + my->y += parent->focalx * sin(parent->yaw) + parent->focaly * sin(parent->yaw + PI / 2); + } bool inrange = (my->worldTooltipActive == 1); bool skipUpdating = true; diff --git a/src/entity.hpp b/src/entity.hpp index ad992d410..9cafd9716 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -35,6 +35,7 @@ #define USERFLAG2 15 #define INVISIBLE_DITHER 16 #define NOCLIP_WALLS 17 +#define NOCLIP_CREATURES 18 // number of entity skills and fskills static const int NUMENTITYSKILLS = 60; @@ -1228,7 +1229,7 @@ void actTextSource(Entity* my); static const int NUM_ITEM_STRINGS = 333; static const int NUM_ITEM_STRINGS_BY_TYPE = 129; -static const int NUM_EDITOR_SPRITES = 191; +static const int NUM_EDITOR_SPRITES = 201; static const int NUM_EDITOR_TILES = 350; // furniture types. @@ -1296,6 +1297,7 @@ bool playerRequiresBloodToSustain(int player); // vampire type or accursed class void spawnBloodVialOnMonsterDeath(Entity* entity, Stat* hitstats, Entity* killer); void shrineDaedalusRevealMap(Entity& my); +void daedalusShrineInteract(Entity* my, Entity* touched); enum EntityHungerIntervals : int { diff --git a/src/entity_shared.cpp b/src/entity_shared.cpp index 3761d992a..cc46ed61e 100644 --- a/src/entity_shared.cpp +++ b/src/entity_shared.cpp @@ -55,6 +55,11 @@ int checkSpriteType(Sint32 sprite) case 166: case 188: case 189: + case 193: + case 194: + case 195: + case 196: + case 197: //monsters return 1; break; @@ -1016,7 +1021,17 @@ char spriteEditorNameStrings[NUM_EDITOR_SPRITES][64] = "AND GATE", "BAT", "BUGBEAR", - "DAEDALUS SHRINE" + "DAEDALUS SHRINE", + "BELL", + "NOT USED", + "SLIME (GREEN)", + "SLIME (BLUE)", + "SLIME (RED)", + "SLIME (TAR)", + "SLIME (METAL)", + "NOT USED", + "NOT USED", + "NOT USED" }; char monsterEditorNameStrings[NUMMONSTERS][16] = diff --git a/src/game.hpp b/src/game.hpp index f164460e4..e250f175a 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -245,7 +245,7 @@ void actGoldBag(Entity* my); void actGib(Entity* my); void actDamageGib(Entity* my); Entity* spawnGib(Entity* parentent, int customGibSprite = -1); -Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, bool miss = false, bool updateClients = false); +Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int displayType = 0, bool updateClients = false); Entity* spawnGibClient(Sint16 x, Sint16 y, Sint16 z, Sint16 sprite); void serverSpawnGibForClient(Entity* gib); void actLadder(Entity* my); @@ -295,6 +295,8 @@ void actTeleporter(Entity* my); void actMagicTrapCeiling(Entity* my); void actTeleportShrine(Entity* my); void actDaedalusShrine(Entity* my); +void actBell(Entity* my); +void bellBreakBulb(Entity* my, bool minotaurBreak); void actSpellShrine(Entity* my); void actExpansionEndGamePortal(Entity* my); void actSoundSource(Entity* my); diff --git a/src/net.cpp b/src/net.cpp index 46a981a36..09779b131 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1919,7 +1919,7 @@ void clientActions(Entity* entity) case 1379: entity->behavior = &actGoldBag; break; - case 1369: + case 1481: entity->behavior = &actDaedalusShrine; break; case Player::Ghost_t::GHOST_MODEL_P1: @@ -2698,6 +2698,56 @@ static std::unordered_map clientPacketHandlers = { } }}, + { 'DAED', []() { + Uint32 uid = SDLNet_Read32(&net_packet->data[4]); + if ( Entity* shrine = uidToEntity(uid) ) + { + if ( shrine->behavior == &::actDaedalusShrine ) + { + daedalusShrineInteract(shrine, nullptr); + } + } + }}, + + // bell dropped item + { 'BELI', []() { + Uint32 uid = SDLNet_Read32(&net_packet->data[4]); + Entity* entity = uidToEntity(uid); + if ( entity ) + { + if ( entity->behavior == &actItem ) + { + //entity->flags[UPDATENEEDED] = true; + if ( entity->flags[INVISIBLE] ) + { + playSoundEntityLocal(entity, 47 + local_rng.rand() % 3, 64); + entity->flags[INVISIBLE] = false; + entity->vel_x = 0.0; //(0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = 0.0; //(0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-2 - local_rng.rand() % 5) * .01; + entity->itemContainer = 0; + entity->z = -16; + entity->itemNotMoving = 0; + entity->itemNotMovingClient = 0; + entity->flags[USERFLAG1] = false; // enable collision + } + } + else if ( entity->behavior == &actGoldBag ) + { + if ( entity->flags[INVISIBLE] ) + { + playSoundEntityLocal(entity, 242 + local_rng.rand() % 4, 64); + entity->vel_x = 0.0; + entity->vel_y = 0.0; + entity->vel_z = (-2 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; + entity->z = -16; + entity->flags[INVISIBLE] = false; + } + } + } + } }, + // ghost interact item { 'GHOI', []() { Uint32 uid = SDLNet_Read32(&net_packet->data[4]); @@ -2792,6 +2842,16 @@ static std::unordered_map clientPacketHandlers = { spawnMagicEffectParticles(x, y, z, sprite); }}, + // spawn magical bell effect particles + { 'MAGB', []() { + Uint32 uid = (Uint32)SDLNet_Read32(&net_packet->data[4]); + if ( Entity* entity = uidToEntity(uid) ) + { + Uint32 sprite = (Uint32)SDLNet_Read32(&net_packet->data[8]); + spawnMagicEffectParticlesBell(entity, sprite); + } + } }, + // spawn misc particle effect {'SPPE', [](){ Entity *entity = uidToEntity((int)SDLNet_Read32(&net_packet->data[4])); @@ -3035,8 +3095,16 @@ static std::unordered_map clientPacketHandlers = { Sint16 dmg = (Sint16)SDLNet_Read16(&net_packet->data[8]); DamageGib gib = DMG_DEFAULT; gib = (DamageGib)(net_packet->data[10]); - bool miss = net_packet->data[11] != 0 ? 1 : 0; - spawnDamageGib(uidToEntity(uid), dmg, gib, miss); + DamageGibDisplayType displayType = DamageGibDisplayType::DMG_GIB_NUMBER; + if ( net_packet->data[11] == 1 ) + { + displayType = DamageGibDisplayType::DMG_GIB_MISS; + } + else if ( net_packet->data[11] == 2 ) + { + displayType = DamageGibDisplayType::DMG_GIB_SPRITE; + } + spawnDamageGib(uidToEntity(uid), dmg, gib, displayType); }}, // ping diff --git a/src/paths.cpp b/src/paths.cpp index 19bd6197f..984c05469 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -372,7 +372,7 @@ int pathCheckObstacle(int x, int y, Entity* my, Entity* target) || entity->sprite == 169 // statue || entity->sprite == 177 // shrine || entity->sprite == 178 // spell shrine - || entity->sprite == 1369 // daedalus shrine + || entity->sprite == 1481 // daedalus shrine ) { if ( (int)floor(entity->x / 16) == u && (int)floor(entity->y / 16) == v ) diff --git a/src/player.cpp b/src/player.cpp index b38b4bc90..9007888ff 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3377,6 +3377,18 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) { return 0.0; } + else if ( parent->behavior == &actBell && parent->skill[7] > 0 ) + { + return 0.0; + } + else if ( parent->behavior == &actTeleportShrine && parent->shrineActivateDelay > 0 ) + { + return 0.0; + } + else if ( parent->behavior == &actDaedalusShrine && parent->shrineActivateDelay > 0 ) + { + return 0.0; + } else if ( parent->behavior == &actPlayer ) { return 0.0; @@ -3454,6 +3466,10 @@ real_t Player::WorldUI_t::tooltipInRange(Entity& tooltip) { dist += 8.0; // distance penalty for rocks from digging etc } + else if ( parent->behavior == &actBell ) + { + dist += 1.0; // distance penalty + } else if ( parent->behavior == &actGoldBag ) { dist = std::max(0.02, dist - 4.0); // bonus priority for goldbag @@ -3979,6 +3995,11 @@ void Player::WorldUI_t::setTooltipActive(Entity& tooltip) { interactText = Language::get(4027); // "Inspect trapdoor" } + else if ( parent->behavior == &actBell ) + { + interactText = Language::get(6271); + + } else if ( parent->behavior == &actLadder ) { if ( secretlevel && parent->skill[3] == 1 ) // secret ladder diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 97ed7b815..a9c262e6d 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -8740,6 +8740,9 @@ namespace MainMenu { case KilledBy::FAILED_CHALLENGE: cause_of_death = Language::get(6153); break; + case KilledBy::BELL: + cause_of_death = Language::get(6278); + break; default: { cause_of_death = Language::get(5794 + (int)score->stats->killer); @@ -26515,6 +26518,9 @@ namespace MainMenu { case KilledBy::FAILED_CHALLENGE: cause_of_death = Language::get(6153); break; + case KilledBy::BELL: + cause_of_death = Language::get(6278); + break; default: { cause_of_death = Language::get(5794 + (int)stats[player]->killer); break; From 062f3b1d2fae5a54b68b87645fc86e9b7b1a3817 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:15:41 +1100 Subject: [PATCH 173/244] * minotaur longer arrive on mines.labyrinth, * minotaur bell/daedalus shrine collide --- src/entity.cpp | 59 ++++++++++++++++++++++++++++++---------- src/magic/spell.cpp | 8 ++++++ src/maps.cpp | 4 +++ src/monster.hpp | 1 + src/monster_minotaur.cpp | 31 +++++++++++++++------ 5 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 61071b895..af6f6bc6a 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -5790,6 +5790,11 @@ Sint32 statGetDEX(Stat* entitystats, Entity* my) //DEX -= 5; } + if ( entitystats->EFFECTS[EFF_AGILITY] ) + { + DEX += 3; + } + if ( my && my->monsterAllyGetPlayerLeader() ) { if ( stats[my->monsterAllyIndex] ) @@ -6025,6 +6030,14 @@ Sint32 statGetCON(Stat* entitystats, Entity* my) { CON += std::max(4, static_cast(CON * 0.25)); } + if ( entitystats->EFFECTS[EFF_CON_BONUS] ) + { + CON += 3; + int percentHP = static_cast(100.0 * (real_t)entitystats->HP / std::max(1, entitystats->MAXHP)); + percentHP = std::min(100, std::max(0, percentHP)); + percentHP = 100 - percentHP; + CON += percentHP / 10; + } return CON; } @@ -7784,25 +7797,34 @@ void Entity::attack(int pose, int charge, Entity* target) dist = lineTrace(this, x, y, yaw, STRIKERANGE, LINETRACE_ATK_CHECK_FRIENDLYFIRE, false); } - if ( hit.entity && hit.entity->behavior == &actMonster ) + if ( hit.entity && (hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer) ) { - if ( hit.entity->getMonsterTypeFromSprite() == BAT_SMALL ) + Stat* hitstats = hit.entity->getStats(); + bool bat = hitstats && hitstats->type == BAT_SMALL; + if ( bat && hit.entity->isUntargetableBat() ) { - if ( hit.entity->isUntargetableBat() ) - { miss = true; } - else if ( hit.entity->monsterSpecialState == BAT_REST ) + else if ( bat && hit.entity->monsterSpecialState == BAT_REST ) { miss = false; } - else + else if ( bat || (hitstats && hitstats->EFFECTS[EFF_AGILITY]) ) { Sint32 previousMonsterState = hit.entity->monsterState; bool backstab = false; bool flanking = false; real_t hitAngle = hit.entity->yawDifferenceFromEntity(this); if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc + { + if ( hit.entity->behavior == &actPlayer ) + { + if ( local_rng.rand() % 2 == 0 ) + { + flanking = true; + } + } + else { if ( previousMonsterState == MONSTER_STATE_WAIT || previousMonsterState == MONSTER_STATE_PATH @@ -7818,19 +7840,21 @@ void Entity::attack(int pose, int charge, Entity* target) flanking = true; } } + } if ( backstab ) { miss = false; } - else if ( flanking ) - { - miss = local_rng.rand() % 10 < 4; - } else { - miss = local_rng.rand() % 10 < 6; + int baseChance = bat ? 6 : 2; + if ( flanking ) + { + baseChance = std::max(1, baseChance - 2); } + miss = local_rng.rand() % 10 < baseChance; + } if ( myStats->weapon ) { @@ -7845,11 +7869,18 @@ void Entity::attack(int pose, int charge, Entity* target) { if ( !hit.entity->isUntargetableBat() ) { - if ( player >= 0 || (behavior == &actMonster && monsterAllyGetPlayerLeader()) ) + if ( player >= 0 || (behavior == &actMonster && monsterAllyGetPlayerLeader()) + || hit.entity->behavior == &actPlayer || hit.entity->monsterAllyGetPlayerLeader() ) { - spawnDamageGib(hit.entity, 0, DamageGib::DMG_MISS, true, true); + spawnDamageGib(hit.entity, 0, DamageGib::DMG_MISS, DamageGibDisplayType::DMG_GIB_MISS, true); } + if ( hit.entity->behavior == &actPlayer ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6286)); + } + else if ( hit.entity->behavior == &actMonster ) + { bool doHitAlert = true; if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) ) { @@ -7890,12 +7921,12 @@ void Entity::attack(int pose, int charge, Entity* target) } } } + } hit.entity = nullptr; } } } - } else { hit.entity = target; diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index 6a4a1eb64..256c8d0af 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -696,6 +696,14 @@ real_t getBonusFromCasterOfSpellElement(Entity* caster, Stat* casterStats, spell if ( casterStats ) { + if ( casterStats->EFFECTS[EFF_PWR] ) + { + bonus += 0.25; + int percentMP = static_cast(100.0 * (real_t)casterStats->MP / std::max(1, casterStats->MAXMP)); + percentMP = std::min(100, std::max(0, percentMP)); + percentMP = (100 - percentMP) / 10; + bonus += 0.5 * percentMP / 10.0; + } if ( casterStats->helmet ) { if ( casterStats->helmet->type == HAT_MITER ) diff --git a/src/maps.cpp b/src/maps.cpp index a234712c8..d62fa2efc 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -8507,6 +8507,10 @@ void assignActions(map_t* map) childEntity->flags[PASSABLE] = true; childEntity->flags[UNCLICKABLE] = false; TileEntityList.addEntity(*childEntity); + //node_t* tempNode = list_AddNodeLast(&entity->children); + //tempNode->element = childEntity; // add the node to the children list. + //tempNode->deconstructor = &emptyDeconstructor; + //tempNode->size = sizeof(Entity*); } break; case 191: diff --git a/src/monster.hpp b/src/monster.hpp index 7faa941b4..6982deb61 100644 --- a/src/monster.hpp +++ b/src/monster.hpp @@ -825,6 +825,7 @@ void bugbearMoveBodyparts(Entity* my, Stat* myStats, double dist); //--misc functions-- void actMinotaurTrap(Entity* my); +int getMinotaurTimeToArrive(); void actMinotaurTimer(Entity* my); void actMinotaurCeilingBuster(Entity* my); void actDemonCeilingBuster(Entity* my); diff --git a/src/monster_minotaur.cpp b/src/monster_minotaur.cpp index 5aaf75586..0178bc000 100644 --- a/src/monster_minotaur.cpp +++ b/src/monster_minotaur.cpp @@ -672,6 +672,15 @@ void actMinotaurTrap(Entity* my) #define MINOTAURTIMER_LIFE my->skill[0] #define MINOTAURTIMER_ACTIVE my->skill[1] +int getMinotaurTimeToArrive() +{ + int minotaurDuration = TICKS_PER_SECOND * 150; + if ( currentlevel >= 25 || currentlevel < 5 || (currentlevel >= 10 && currentlevel < 15) ) + { + minotaurDuration = TICKS_PER_SECOND * 210; + } + return minotaurDuration; +} void actMinotaurTimer(Entity* my) { @@ -679,10 +688,8 @@ void actMinotaurTimer(Entity* my) auto& rng = my->entity_rng ? *my->entity_rng : local_rng; MINOTAURTIMER_LIFE++; - if (( (currentlevel < 25 && MINOTAURTIMER_LIFE == TICKS_PER_SECOND * 120) - || (currentlevel >= 25 && MINOTAURTIMER_LIFE == TICKS_PER_SECOND * 180) - ) - && rng.rand() % 5 == 0 ) // two minutes if currentlevel < 25, else 3 minutes. + if ( MINOTAURTIMER_LIFE == (getMinotaurTimeToArrive() - (TICKS_PER_SECOND * 30)) + && rng.rand() % 5 == 0 ) { int c; bool spawnedsomebody = false; @@ -724,10 +731,7 @@ void actMinotaurTimer(Entity* my) } } } - else if (( (currentlevel < 25 && MINOTAURTIMER_LIFE >= TICKS_PER_SECOND * 150) - || (currentlevel >= 25 && MINOTAURTIMER_LIFE >= TICKS_PER_SECOND * 210) - ) - && !MINOTAURTIMER_ACTIVE ) // two and a half minutes if currentlevel < 25, else 3.5 minutes + else if ( (MINOTAURTIMER_LIFE >= getMinotaurTimeToArrive()) && !MINOTAURTIMER_ACTIVE ) { Entity* monster = summonMonster(MINOTAUR, my->x, my->y); if ( monster ) @@ -1010,6 +1014,13 @@ void actMinotaurCeilingBuster(Entity* my) entity->colliderKillerUid = 0; } } + else if ( entity->behavior == &actBell ) + { + if ( multiplayer != CLIENT ) + { + bellBreakBulb(entity, true); + } + } else if ( entity->behavior == &actGate ) { if ( multiplayer != CLIENT ) @@ -1032,6 +1043,10 @@ void actMinotaurCeilingBuster(Entity* my) magicDig(my, nullptr, 0, 1); hit.entity = ohitentity; } + else if ( entity->sprite == 1480 ) // daedalus base + { + list_RemoveNode(entity->mynode); + } else if ( entity->behavior == &actStalagCeiling || entity->behavior == &actStalagFloor || entity->behavior == &actStalagColumn From 203ab8edeff75daeeac207c7b4ef24664c1386b6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:22:04 +1100 Subject: [PATCH 174/244] * bell behavior, evasion for players, miss messages --- src/actgeneral.cpp | 1251 ++++++++++++++++++++++++++++++++++++++++++++ src/collision.cpp | 112 +++- 2 files changed, 1342 insertions(+), 21 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index c56bdb84c..e3965345c 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -23,6 +23,7 @@ #include "items.hpp" #include "scores.hpp" #include "mod_tools.hpp" +#include "paths.hpp" /*------------------------------------------------------------------------------- @@ -3595,3 +3596,1253 @@ void TextSourceScript::parseScriptInMapGeneration(Entity& src) } } } + +void bellAttractMonsters(Entity* my) +{ + if ( !my ) { return; } + + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange); + + for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( entity->behavior == &actMonster && entity->monsterAllyGetPlayerLeader() == nullptr ) + { + if ( (entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0) ) + { + Stat* myStats = entity->getStats(); + if ( !entity->isBossMonster() && !entity->monsterIsTinkeringCreation() + && entity->isMobile() + && myStats + && entityDist(my, entity) > TOUCHRANGE ) + { + if ( !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] + && entity->monsterSetPathToLocation(my->x / 16, my->y / 16, 1, + GeneratePathTypes::GENERATE_PATH_DEFAULT, true) && entity->children.first ) + { + entity->monsterTarget = my->getUID(); + entity->monsterState = MONSTER_STATE_HUNT; // hunt state + serverUpdateEntitySkill(entity, 0); + if ( entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false) ) + { + spawnFloatingSpriteMisc(134, entity->x + (-4 + local_rng.rand() % 9) + cos(entity->yaw) * 2, + entity->y + (-4 + local_rng.rand() % 9) + sin(entity->yaw) * 2, entity->z + local_rng.rand() % 4); + } + } + } + } + } + } + } +} + +#define BELL_ACTIVE_TIMER my->skill[0] +#define BELL_HAS_ITEM my->skill[1] +#define BELL_AMBIENCE my->skill[3] +#define BELL_USES my->skill[4] +#define BELL_CURRENT_EVENT my->skill[5] +#define BELL_LAST_TOUCHED_PLAYER my->skill[6] +#define BELL_USE_DELAY my->skill[7] +#define BELL_INIT my->skill[8] +#define BELL_CLAPPER_BROKEN my->skill[9] +#define BELL_BULB_BROKEN my->skill[10] +#define BELL_BUFF_TYPE my->skill[11] + +int getBellDmgOnEntity(Entity* entity) +{ + if ( !entity ) { return 0; } + + Stat* stats = entity->getStats(); + if ( !stats ) + { + return 0; + } + + int damage = 50; + int trapResist = entity->getFollowerBonusTrapResist(); + if ( trapResist != 0 ) + { + real_t mult = std::max(0.0, 1.0 - (trapResist / 100.0)); + damage *= mult; + } + + if ( stats->helmet ) + { + bool shapeshifted = (entity->behavior == &actPlayer && entity->effectShapeshift != NOTHING); + + if ( !shapeshifted + && (stats->helmet->type == HELM_MINING || stats->helmet->type == HAT_TOPHAT) ) + { + if ( stats->helmet->type == HAT_TOPHAT ) + { + bool cursedItemIsBuff = shouldInvertEquipmentBeatitude(stats); + if ( stats->helmet->beatitude >= 0 || cursedItemIsBuff ) + { + if ( stats->HP <= damage ) + { + // saved us + //steamAchievementEntity(entity, "BARONY_ACH_CRUMPLE_ZONES"); + } + damage = 0; + } + stats->helmet->status = BROKEN; + } + else if ( stats->helmet->type == HELM_MINING ) + { + real_t mult = 0.5; + bool cursedItemIsBuff = shouldInvertEquipmentBeatitude(stats); + if ( stats->helmet->beatitude >= 0 || cursedItemIsBuff ) + { + mult -= 0.25 * abs(stats->helmet->beatitude); + mult = std::max(0.0, mult); + } + else + { + mult = 1.0; + mult += 0.25 * abs(stats->helmet->beatitude); + } + + if ( stats->HP <= damage ) + { + // saved us + if ( stats->HP > (damage * mult) ) + { + //steamAchievementEntity(entity, "BARONY_ACH_CRUMPLE_ZONES"); + } + } + damage *= mult; + if ( stats->helmet->status > BROKEN ) + { + stats->helmet->status = (Status)((int)stats->helmet->status - 1); + } + } + + playSoundEntity(entity, 76, 64); + + if ( entity->behavior == &actPlayer ) + { + int player = entity->skill[2]; + if ( stats->helmet->status > BROKEN ) + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(681), stats->helmet->getName()); + } + else + { + messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(682), stats->helmet->getName()); + } + + if ( multiplayer == SERVER && player > 0 && !players[player]->isLocalPlayer() ) + { + strcpy((char*)net_packet->data, "ARMR"); + net_packet->data[4] = 0; + net_packet->data[5] = stats->helmet->status; + net_packet->address.host = net_clients[player - 1].host; + net_packet->address.port = net_clients[player - 1].port; + net_packet->len = 6; + sendPacketSafe(net_sock, -1, net_packet, player - 1); + } + } + } + } + + return damage; +} + +void spawnMagicEffectParticlesBell(Entity* my, Uint32 sprite) +{ + if ( !my ) { return; } + int baseX = my->x / 16; + int baseY = my->y / 16; + + real_t posx = baseX * 16.0 + 8; + real_t posy = baseY * 16.0 + 8; + real_t z = 8.0; + const int numParticles = 64; + for ( int c = 0; c < numParticles; c++ ) + { + Entity* entity = newEntity(1479, 1, map.entities, nullptr); //Particle entity. + entity->x = posx + 24.0 * cos(2 * PI * (c / (real_t)numParticles)); + entity->y = posy + 24.0 * sin(2 * PI * (c / (real_t)numParticles)); + entity->z = z; + entity->scalex = 0.7; + entity->scaley = 0.7; + entity->scalez = 0.7; + entity->sizex = 1; + entity->sizey = 1; + entity->yaw = (local_rng.rand() % 360) * PI / 180.f; + entity->pitch = (local_rng.rand() % 360) * PI / 180.f; + entity->flags[PASSABLE] = true; + entity->flags[NOUPDATE] = true; + entity->flags[UNCLICKABLE] = true; + entity->flags[INVISIBLE] = true; + entity->flags[INVISIBLE_DITHER] = true; + entity->lightBonus = vec4(0.25f, 0.25f, + 0.25f, 0.f); + entity->behavior = &actMagicParticle; + entity->vel_z = -0.5; + if ( multiplayer != CLIENT ) + { + entity_uids--; + } + entity->setUID(-3); + } + + if ( multiplayer == SERVER ) + { + for ( int c = 1; c < MAXPLAYERS; c++ ) + { + if ( client_disconnected[c] || players[c]->isLocalPlayer() ) + { + continue; + } + strcpy((char*)net_packet->data, "MAGB"); + SDLNet_Write32(my->getUID(), &net_packet->data[4]); + SDLNet_Write32(sprite, &net_packet->data[8]); + net_packet->address.host = net_clients[c - 1].host; + net_packet->address.port = net_clients[c - 1].port; + net_packet->len = 12; + sendPacketSafe(net_sock, -1, net_packet, c - 1); + } + } +} + +void bellBreakBulb(Entity* my, bool minotaurBreak) +{ + if ( !my ) { return; } + if ( BELL_BULB_BROKEN == 1 ) + { + return; + } + BELL_BULB_BROKEN = 1; + if ( minotaurBreak ) + { + BELL_LAST_TOUCHED_PLAYER = -1; + } + + Entity* bell = nullptr; + for ( node_t* node = my->children.first; node; node = node->next ) + { + if ( node->element != nullptr ) + { + Entity* child = (Entity*)node->element; + if ( child ) + { + if ( child->sprite == 1475 && !child->flags[INVISIBLE] ) // bell + { + bell = child; + } + } + } + } + + if ( bell ) + { + bellAttractMonsters(my); + auto& rng = my->entity_rng ? *my->entity_rng : local_rng; + int dir = rng.rand() % 9; + if ( dir == 8 ) + { + bell->vel_x = 0.0; + bell->vel_y = 0.0; + } + else + { + bell->vel_x = 0.25 * cos(my->yaw + dir * PI / 4); + bell->vel_y = 0.25 * sin(my->yaw + dir * PI / 4); + } + } + + serverUpdateEntitySkill(my, 10); + playSoundEntity(my, 76, 64); + + if ( minotaurBreak ) + { + playSoundEntity(my, 689, 128); + playSoundPlayer(clientnum, 689, 32); + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + playSoundPlayer(i, 689, 32); + } + } + } + + if ( BELL_CLAPPER_BROKEN == 0 ) + { + BELL_CLAPPER_BROKEN = 1; + serverUpdateEntitySkill(my, 9); + } +} + +void actBell(Entity* my) +{ + if ( !my ) + { + return; + } + + enum BellEvents : int + { + BELL_RING_BUFF = 1, + BELL_MONSTER, + BELL_ITEM, + BELL_CLAPPER_BREAK, + BELL_CRASH, + BELL_NOTHING, + BELL_ENUM_END + }; + + enum BellBuffs + { + BUFF_STR, + BUFF_CON, + BUFF_PWR, + BUFF_DEX, + BUFF_ENUM_END, + }; + + auto& rng = my->entity_rng ? *my->entity_rng : local_rng; + if ( !BELL_INIT ) + { + BELL_INIT = 1; + + { + Entity* childEntity = newEntity(1476, 1, map.entities, nullptr); // clapper + childEntity->parent = my->getUID(); + childEntity->x = my->x; + childEntity->y = my->y; + childEntity->sizex = 2; + childEntity->sizey = 2; + childEntity->flags[PASSABLE] = true; + childEntity->flags[UNCLICKABLE] = false; + childEntity->flags[NOUPDATE] = true; + childEntity->z = my->z; + if ( multiplayer != CLIENT ) + { + entity_uids--; + } + childEntity->setUID(-3); + node_t* tempNode = list_AddNodeLast(&my->children); + tempNode->element = childEntity; // add the node to the children list. + tempNode->deconstructor = &emptyDeconstructor; + tempNode->size = sizeof(Entity*); + } + { + Entity* childEntity = newEntity(1477, 1, map.entities, nullptr); // headstock + childEntity->parent = my->getUID(); + childEntity->x = my->x; + childEntity->y = my->y; + childEntity->sizex = 4; + childEntity->sizey = 4; + childEntity->flags[PASSABLE] = true; + childEntity->flags[UNCLICKABLE] = false; + childEntity->flags[NOUPDATE] = true; + childEntity->z = my->z; + if ( multiplayer != CLIENT ) + { + entity_uids--; + } + childEntity->setUID(-3); + node_t* tempNode = list_AddNodeLast(&my->children); + tempNode->element = childEntity; // add the node to the children list. + tempNode->deconstructor = &emptyDeconstructor; + tempNode->size = sizeof(Entity*); + } + } + + if ( my->ticks == 1 ) + { + my->createWorldUITooltip(); + BELL_LAST_TOUCHED_PLAYER = -1; + } + + my->z = 0; +#ifndef NDEBUG + if ( keystatus[SDLK_KP_5] && enableDebugKeys ) + { + keystatus[SDLK_KP_5]; + bellBreakBulb(my, true); + //my->yaw += 0.01; + } +#endif // !NDEBUG + my->focalx = 4; + my->focaly = -6; + my->focalz = -10.75; + +#ifdef USE_FMOD + if ( BELL_AMBIENCE == 0 ) + { + BELL_AMBIENCE--; + my->stopEntitySound(); + my->entity_sound = playSoundEntityLocal(my, 149, 16); + } + if ( my->entity_sound ) + { + bool playing = false; + my->entity_sound->isPlaying(&playing); + if ( !playing ) + { + my->entity_sound = nullptr; + } + } +#else + BELL_AMBIENCE--; + if ( BELL_AMBIENCE <= 0 ) + { + BELL_AMBIENCE = TICKS_PER_SECOND * 30; + playSoundEntityLocal(my, 149, 16); + } +#endif + + const int pullTimerStart = 100; +#ifndef NDEBUG + if ( keystatus[SDLK_KP_3] && enableDebugKeys ) + { + keystatus[SDLK_KP_3] = 0; + BELL_ACTIVE_TIMER = pullTimerStart; // active pull timer + } + + if ( keystatus[SDLK_g] && enableDebugKeys ) + { + keystatus[SDLK_g] = 0; + BELL_CURRENT_EVENT = (BellEvents)(BELL_CURRENT_EVENT + 1); + if ( BELL_CURRENT_EVENT >= BELL_ENUM_END ) + { + BELL_CURRENT_EVENT = BELL_RING_BUFF; + } + messagePlayer(0, MESSAGE_DEBUG, "Bell event: %d", BELL_CURRENT_EVENT); + } +#endif + + if ( multiplayer == CLIENT ) + { + // server encoded current bell event into the timer, separate it out + Sint32 upperbits = (BELL_ACTIVE_TIMER >> 8) & 0xFF; + if ( upperbits > 0 ) + { + BELL_CURRENT_EVENT = upperbits; + } + BELL_ACTIVE_TIMER = (BELL_ACTIVE_TIMER & 0xFF); + } + + Entity* touched = nullptr; + if ( multiplayer != CLIENT ) // interaction + { + if ( my->isInteractWithMonster() ) + { + Entity* monsterInteracting = uidToEntity(my->interactedByMonster); + if ( monsterInteracting ) + { + my->clearMonsterInteract(); + if ( BELL_USE_DELAY <= 0 ) + { + touched = monsterInteracting; + if ( auto leader = monsterInteracting->monsterAllyGetPlayerLeader() ) + { + BELL_LAST_TOUCHED_PLAYER = leader->skill[2]; + } + else + { + BELL_LAST_TOUCHED_PLAYER = -1; + } + } + } + my->clearMonsterInteract(); + } + + for ( int i = 0; i < MAXPLAYERS; i++ ) + { + if ( selectedEntity[i] == my || client_selected[i] == my ) + { + if ( inrange[i] && Player::getPlayerInteractEntity(i) ) + { + if ( BELL_USE_DELAY <= 0 ) + { + touched = Player::getPlayerInteractEntity(i); + BELL_LAST_TOUCHED_PLAYER = i; + break; + } + } + } + } + if ( touched ) + { + if ( BELL_CURRENT_EVENT == BELL_CRASH + || BELL_CURRENT_EVENT == BELL_CLAPPER_BREAK + || BELL_CURRENT_EVENT == BELL_NOTHING ) + { + BELL_CURRENT_EVENT = BELL_NOTHING; + } + else + { + if ( BELL_CURRENT_EVENT == 0 ) + { + BELL_USES = 3 + rng.rand() % 2; + if ( BELL_HAS_ITEM != 0 ) + { + BELL_CURRENT_EVENT = BELL_ITEM; + BELL_USES = 2 + rng.rand() % 3; + } + else + { + if ( rng.rand() % 4 == 0 ) + { + // bats + BELL_CURRENT_EVENT = BELL_MONSTER; + BELL_USES = 2 + rng.rand() % 3; + } + else + { + BELL_CURRENT_EVENT = BELL_RING_BUFF; + } + } + } + else + { + BELL_CURRENT_EVENT = BELL_RING_BUFF; + --BELL_USES; + if ( BELL_USES <= 0 ) + { + if ( rng.rand() % 5 == 0 ) + { + BELL_CURRENT_EVENT = BELL_CRASH; + } + else + { + BELL_CURRENT_EVENT = BELL_CLAPPER_BREAK; + } + } + } + } + + BELL_ACTIVE_TIMER = pullTimerStart; + BELL_ACTIVE_TIMER |= (BELL_CURRENT_EVENT << 8); + serverUpdateEntitySkill(my, 0); + BELL_ACTIVE_TIMER = pullTimerStart; + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6268)); + } + + if ( BELL_CURRENT_EVENT == BELL_RING_BUFF ) + { + BELL_USE_DELAY = TICKS_PER_SECOND * 9; + } + else if ( BELL_CURRENT_EVENT == BELL_NOTHING ) + { + BELL_USE_DELAY = TICKS_PER_SECOND * 3; + } + else + { + BELL_USE_DELAY = TICKS_PER_SECOND * 5; + } + serverUpdateEntitySkill(my, 7); // use delay + } + + } + + if ( BELL_USE_DELAY > 0 ) + { + --BELL_USE_DELAY; + } + if ( BELL_ACTIVE_TIMER == pullTimerStart ) + { + playSoundEntityLocal(my, 688, 64); + } + + if ( BELL_ACTIVE_TIMER > 0 ) + { + --BELL_ACTIVE_TIMER; + } + else + { + BELL_ACTIVE_TIMER = 0; + } + + bool bellEventTriggered = false; + bool shortRing = (BELL_CURRENT_EVENT != BELL_RING_BUFF); + bool startBellAnim = false; + static ConsoleVariable cvar_bell_max_spd("/bell_max_spd", 4.0); + static ConsoleVariable cvar_bell_clap_rot("/bell_clap_rot", 245); + static ConsoleVariable cvar_bell_anim_tick("/bell_anim_tick", 90); + static ConsoleVariable cvar_bell_pull_tick("/bell_pull_tick", 30); + static ConsoleVariable cvar_bell_dong1("/bell_dong1", 100); + static ConsoleVariable cvar_bell_dong2("/bell_dong2", 140); + static ConsoleVariable cvar_bell_dong3("/bell_dong3", 190); + if ( my->skill[0] == *cvar_bell_anim_tick ) + { + startBellAnim = true; + } + const int pullTimerFirstAnim = *cvar_bell_pull_tick; + if ( my->skill[0] > pullTimerFirstAnim ) + { + //const int interval = pullTimerStart - pullTimerFirstAnim; + //my->focalz += -2 + (interval - (my->skill[0] - pullTimerFirstAnim)) * 2.0 / (interval / 2.0); + + const int interval = pullTimerStart - pullTimerFirstAnim; + my->focalz += 2.0 * cos(PI * (interval - (my->skill[0] - pullTimerFirstAnim) / (real_t)interval)); + } + else + { + //my->focalz += 2.0 * cos((-my->skill[0] + pullTimerFirstAnim) * PI / (real_t)pullTimerFirstAnim); + + const int interval = pullTimerFirstAnim; + my->focalz += -2.0 + 4.0 * cos(1.5 * PI * (interval - my->skill[0]) / (real_t)interval); + } + + const real_t baseZ = 0.0; + + Entity* bell = nullptr; + Entity* clapper = nullptr; + node_t* nextnode = nullptr; + static ConsoleVariable cvar_bell_crash_sfx("/bell_crash_sfx", 691); + for ( node_t* node = my->children.first; node; node = nextnode ) + { + nextnode = node->next; + if ( node->element != nullptr ) + { + Entity* child = (Entity*)node->element; + if ( child ) + { + if ( child->sprite == 1475 ) // bell + { + if ( child->flags[INVISIBLE] ) + { + bell = nullptr; + continue; + } + bell = child; + if ( BELL_BULB_BROKEN ) + { + child->vel_z += 0.04; + child->z += child->vel_z; + child->yaw += 0.15; + real_t dist = clipMove(&child->x, &child->y, child->vel_x, child->vel_y, child); + + Entity* collided = nullptr; + if ( multiplayer != CLIENT ) + { + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(child, 2); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !collided; ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr && !collided; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( !entity ) { continue; } + + if ( (entity->behavior == &actMonster && !(entity->getRace() == MIMIC)) + || entity->behavior == &actPlayer ) + { + Stat* stats = entity->getStats(); + if ( stats && entityInsideEntity(entity, child) ) + { + if ( !(stats->type == MINOTAUR || child->z >= -16.0) ) + { + continue; + } + + if ( ParticleEmitterHit_t* particleEmitterHitProps = getParticleEmitterHitProps(my->getUID(), entity) ) + { + if ( particleEmitterHitProps->hits > 0 ) + { + continue; + } + particleEmitterHitProps->hits++; + } + + if ( entity->behavior == &actPlayer ) + { + const Uint32 color = makeColorRGB(255, 0, 0); + messagePlayerColor(entity->skill[2], MESSAGE_STATUS, color, Language::get(6276)); + if ( players[entity->skill[2]]->isLocalPlayer() ) + { + cameravars[entity->skill[2]].shakex += .1; + cameravars[entity->skill[2]].shakey += 10; + } + else + { + if ( entity->skill[2] > 0 ) + { + strcpy((char*)net_packet->data, "SHAK"); + net_packet->data[4] = 10; // turns into .1 + net_packet->data[5] = 10; + net_packet->address.host = net_clients[entity->skill[2] - 1].host; + net_packet->address.port = net_clients[entity->skill[2] - 1].port; + net_packet->len = 6; + sendPacketSafe(net_sock, -1, net_packet, entity->skill[2] - 1); + } + } + } + playSoundEntity(entity, 28, 64); + Entity* gib = spawnGib(entity); + int dmg = getBellDmgOnEntity(entity); + Sint32 oldHP = stats->HP; + entity->modHP(-dmg); + if ( entity->behavior == &actPlayer && stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "bell", oldHP - stats->HP); + } + entity->setObituary(Language::get(6277)); + stats->killer = KilledBy::BELL; + + if ( entity->behavior == &actPlayer ) + { + if ( stats->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "bell", 1); + } + } + + if ( stats->type == MINOTAUR ) + { + collided = entity; // break this thing over its head + } + break; + } + } + } + } + } + if ( child->z >= -3.5 || collided ) + { + if ( multiplayer != CLIENT ) + { + for ( int i = 0; i < 6; ++i ) + { + Entity* dropped = dropItemMonster(newItem(TOOL_METAL_SCRAP, DECREPIT, 0, 2 + rng.rand() % 3, 0, true, nullptr), child, nullptr, 1); + if ( dropped ) + { + dropped->z = child->z + child->focalz; + } + } + playSoundEntity(my, *cvar_bell_crash_sfx, 128); + playSoundPlayer(clientnum, *cvar_bell_crash_sfx, 32); + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + playSoundPlayer(i, *cvar_bell_crash_sfx, 32); + } + } + } + child->flags[INVISIBLE] = true; + if ( multiplayer == SERVER ) + { + serverUpdateEntityFlag(child, INVISIBLE); + } + bell = nullptr; + } + continue; + } + + child->yaw = my->yaw; + child->z = baseZ - 22.25; + child->focalz = 6; + child->x = my->x - 2 * cos(child->yaw); + child->y = my->y - 2 * sin(child->yaw); + + Sint32& dongs = child->skill[1]; + real_t& rotation = child->fskill[1]; + Sint32& endDamp = child->skill[5]; + + Entity* clapper = nullptr; + if ( nextnode ) + { + clapper = (Entity*)nextnode->element; + } + if ( startBellAnim ) + { + // target pitch + child->fskill[0] = PI / 8; + child->pitch = 0.0; + dongs = 0; + rotation = 0.0; + child->skill[3] = ticks; + endDamp = 0; + + if ( clapper && BELL_CLAPPER_BROKEN == 0 ) + { + clapper->pitch = 0.0; + clapper->skill[1] = 0; // dongs + clapper->skill[4] = 0; // rotation + clapper->skill[5] = 0; // end damp + clapper->fskill[0] = 0.0; + } + if ( clapper ) + { + clapper->skill[6] = 0; // clapper active + } + } + + if ( abs(child->fskill[0]) > 0.0001 ) + { + real_t oldPitch = child->pitch; + child->fskill[0] = (1 / (real_t)((1 + std::max(0, (dongs - 1))))) * PI / 8; + if ( dongs >= 3 || (shortRing && dongs >= 1) ) + { + endDamp = std::min(endDamp + 1, 100); + child->fskill[0] *= (float)(100 - endDamp) / 100.f; + } + + if ( (int)(*cvar_bell_max_spd * rotation) >= *cvar_bell_clap_rot ) + { + if ( clapper && clapper->skill[6] == 0 ) + { + if ( BELL_CLAPPER_BROKEN == 0 ) + { + if ( shortRing ) + { + playSoundEntityLocal(my, 689, 128); + playSoundPlayer(clientnum, 689, 32); + } + } + bellEventTriggered = true; + clapper->skill[6] = 1; + } + } + + real_t speed = *cvar_bell_max_spd * rotation; + child->pitch = child->fskill[0] * sin((speed) * PI / 180.f); + if ( child->pitch < -0.0 && oldPitch >= 0.0 ) + { + ++dongs; + } + if ( rotation < 15.0 ) + { + rotation += 0.25; + } + else + { + rotation += 1.0; + } + + if ( shortRing ) + { + if ( clapper && BELL_CLAPPER_BROKEN == 0 ) + { + if ( (ticks - child->skill[3] == *cvar_bell_dong1) ) + { + if ( Entity* gib = spawnDamageGib(child, 192, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE) ) + { + gib->z += 8.0; + } + } + } + } + else if ( ((ticks - child->skill[3]) == *cvar_bell_dong1) + || ((ticks - child->skill[3]) == *cvar_bell_dong2) + || ((ticks - child->skill[3]) == *cvar_bell_dong3) ) + { + if ( clapper && BELL_CLAPPER_BROKEN == 0 ) + { + int vol = 128; + if ( (ticks - child->skill[3]) == *cvar_bell_dong2 ) + { + vol = 92; + } + else if ( ((ticks - child->skill[3]) == *cvar_bell_dong3) ) + { + vol = 64; + } + playSoundEntity(my, 690, vol); + playSoundPlayer(clientnum, 690, vol / 4); + if ( Entity* gib = spawnDamageGib(child, 192, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE) ) + { + gib->z += 8.0; + } + } + } + } + else + { + child->pitch = 0.0; + child->fskill[0] = 0.0; + dongs = 0; + rotation = 0.0; + endDamp = 0; + } + while ( child->pitch >= PI ) + { + child->pitch -= PI; + } + while ( child->pitch < -PI ) + { + child->pitch += PI; + } + } + else if ( child->sprite == 1476 ) // clapper + { + if ( child->flags[INVISIBLE] ) + { + clapper = nullptr; + continue; + } + child->yaw = my->yaw; + clapper = child; + if ( BELL_CLAPPER_BROKEN == 1 ) // broken + { + if ( child->focalz > 5 ) + { + child->z = baseZ - 10; + } + child->focalz = 0; + bool onground = false; + real_t groundheight = 7.5; + const real_t yawSpeed = 0.2; + const real_t pitchSpeed = 0.1; + if ( child->z < groundheight ) + { + child->vel_z += 0.04; + child->z += child->vel_z; + child->pitch += pitchSpeed; + child->yaw += yawSpeed; + } + else + { + if ( child->x >= 0 && child->y >= 0 && child->x < map.width << 4 && child->y < map.height << 4 ) + { + const int tile = map.tiles[(int)(child->y / 16) * MAPLAYERS + (int)(child->x / 16) * MAPLAYERS * map.height]; + if ( tile ) + { + onground = true; + + child->vel_z *= -.35; // bounce + if ( child->vel_z > -.35 ) + { + child->z = groundheight; + child->vel_z = 0.0; + child->pitch = -PI / 2; + } + else + { + // just bounce off the ground. + child->z = groundheight - .0001; + } + } + else + { + // fall (no ground here) + child->vel_z += 0.04; + child->z += child->vel_z; + child->pitch += pitchSpeed; + child->yaw += yawSpeed; + } + } + else + { + // fall (out of bounds) + child->vel_z += 0.04; + child->z += child->vel_z; + child->pitch += pitchSpeed; + child->yaw += yawSpeed; + } + } + + // falling out of the map + if ( child->z > 128 ) + { + child->flags[INVISIBLE] = true; + clapper = nullptr; + } + continue; + } + + child->z = baseZ - 20; + child->x = my->x - 2 * cos(child->yaw); + child->y = my->y - 2 * sin(child->yaw); + child->focalz = 10; + auto& dongs = child->skill[1]; + Sint32& rotation = child->skill[4]; + Sint32& endDamp = child->skill[5]; + if ( child->skill[6] == 1 ) + { + child->skill[6] = 2; + child->fskill[0] = PI / 8; + } + + if ( abs(child->fskill[0]) > 0.0001 ) + { + real_t oldPitch = child->pitch; + child->fskill[0] = -(1 / (real_t)((1 + std::max(0, (dongs - 1))))) * PI / 8; + if ( dongs >= 3 || (shortRing && dongs >= 1) ) + { + endDamp = std::min(endDamp + 1, 100); + child->fskill[0] *= (float)(100 - endDamp) / 100.f; + } + child->pitch = child->fskill[0] * sin((*cvar_bell_max_spd * rotation) * PI / 180.f); + if ( child->pitch < -0.0 && oldPitch >= 0.0 ) + { + ++dongs; + } + ++rotation; + } + else + { + child->pitch = 0.0; + child->fskill[0] = 0.0; + dongs = 0; + rotation = 0; + endDamp = 0; + } + + while ( child->pitch >= PI ) + { + child->pitch -= PI; + } + while ( child->pitch < -PI ) + { + child->pitch += PI; + } + } + else if ( child->sprite == 1477 ) // headstock + { + child->yaw = my->yaw; + child->z = baseZ - 21.75; + } + } + } + } + + if ( multiplayer == CLIENT ) + { + return; + } + + if ( BELL_ACTIVE_TIMER == 1 && BELL_BULB_BROKEN ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6274)); + return; + } + + if ( bellEventTriggered ) + { + if ( BELL_BULB_BROKEN ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6274)); + return; + } + else if ( BELL_CLAPPER_BROKEN ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6273)); + return; + } + + if ( BELL_CURRENT_EVENT == BELL_RING_BUFF ) + { + bellAttractMonsters(my); + spawnMagicEffectParticlesBell(my, 1479); + + auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2); + for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) + { + list_t* currentList = *it; + node_t* node; + for ( node = currentList->first; node != nullptr; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( (entity->behavior == &actMonster && !(entity->getRace() == MIMIC)) + || entity->behavior == &actPlayer ) + { + if ( entityDist(my, entity) <= 26.0 ) + { + const char* lang = nullptr; + int statusEffect = 0; + switch ( BELL_BUFF_TYPE % BellBuffs::BUFF_ENUM_END ) + { + case BUFF_STR: + lang = Language::get(6281); + statusEffect = EFF_POTION_STR; + break; + case BUFF_CON: + lang = Language::get(6282); + statusEffect = EFF_CON_BONUS; + break; + case BUFF_DEX: + lang = Language::get(6283); + statusEffect = EFF_AGILITY; + break; + case BUFF_PWR: + lang = Language::get(6284); + statusEffect = EFF_PWR; + break; + default: + break; + } + int duration = TICKS_PER_SECOND * 60; + if ( statusEffect > 0 ) + { + if ( Stat* stats = entity->getStats() ) + { + if ( entity->setEffect(statusEffect, true, std::max(stats->EFFECTS_TIMERS[statusEffect], duration), false) ) + { + playSoundEntity(entity, 166, 128); + spawnDamageGib(entity, 200, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); + if ( entity->behavior == &actPlayer ) + { + messagePlayerColor(entity->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(6280), lang); + } + } + } + } + } + + } + } + } + } + else if ( BELL_CURRENT_EVENT == BELL_CRASH ) + { + if ( bell && BELL_BULB_BROKEN == 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + bellBreakBulb(my, false); + } + } + else if ( BELL_CURRENT_EVENT == BELL_CLAPPER_BREAK ) + { + if ( clapper && BELL_CLAPPER_BROKEN == 0 ) + { + BELL_CLAPPER_BROKEN = 1; + serverUpdateEntitySkill(my, 9); + playSoundEntity(my, 76, 64); + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6279)); + bellAttractMonsters(my); + } + } + else if ( BELL_CURRENT_EVENT == BELL_MONSTER ) + { + int successes = 0; + Monster type = BAT_SMALL; + std::vector enemies; + for ( int i = 0; i < 4; ++i ) + { + if ( Entity* monster = summonMonster(type, ((int)(my->x / 16)) * 16 + 8, ((int)(my->y / 16)) * 16 + 8) ) + { + ++successes; + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + if ( players[BELL_LAST_TOUCHED_PLAYER]->entity ) + { + monster->monsterAcquireAttackTarget(*players[BELL_LAST_TOUCHED_PLAYER]->entity, MONSTER_STATE_PATH); + } + } + } + } + + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + if ( successes == 1 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName((Monster)type).c_str(), Language::get(6269)); + } + else if ( successes > 1 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6253), + getMonsterLocalizedPlural((Monster)type).c_str(), Language::get(6269)); + } + } + + if ( rng.rand() % 8 == 0 ) + { + // sometimes it just falls straight after + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + } + bellBreakBulb(my, false); + } + } + else if ( BELL_CURRENT_EVENT == BELL_ITEM ) + { + if ( BELL_HAS_ITEM != 0 ) + { + if ( Entity* entity = uidToEntity(BELL_HAS_ITEM) ) + { + BELL_HAS_ITEM = 0; + if ( entity->behavior == &actItem ) + { + playSoundEntityLocal(entity, 47 + local_rng.rand() % 3, 64); + entity->vel_x = 0.0; //(0.25 + .025 * (local_rng.rand() % 11)) * cos(entity->yaw); + entity->vel_y = 0.0; //(0.25 + .025 * (local_rng.rand() % 11)) * sin(entity->yaw); + entity->vel_z = (-2 - local_rng.rand() % 5) * .01; + entity->itemContainer = 0; + entity->z = -16; + entity->itemNotMoving = 0; + entity->itemNotMovingClient = 0; + entity->flags[USERFLAG1] = false; // enable collision + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "BELI"); + SDLNet_Write32(static_cast(entity->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6272)); + } + } + else if ( entity->behavior == &actGoldBag ) + { + playSoundEntityLocal(entity, 242 + local_rng.rand() % 4, 64); + entity->vel_x = 0.0; + entity->vel_y = 0.0; + entity->vel_z = (-2 - local_rng.rand() % 5) * .01; + entity->goldBouncing = 0; + entity->z = -16; + entity->flags[INVISIBLE] = false; + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "BELI"); + SDLNet_Write32(static_cast(entity->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6272)); + } + } + + if ( rng.rand() % 16 == 0 ) + { + // sometimes it just falls straight after + if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) + { + messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + } + bellBreakBulb(my, false); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/collision.cpp b/src/collision.cpp index 7aa3da26a..94afaaa66 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -512,39 +512,49 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { return true; } - if ( behavior == &actMonster ) + if ( behavior == &actMonster || behavior == &actPlayer ) { if ( Stat* myStats = getStats() ) { - if ( myStats->type == BAT_SMALL ) + if ( myStats->type == BAT_SMALL || myStats->EFFECTS[EFF_AGILITY] ) { bool miss = false; - if ( isUntargetableBat() ) + if ( myStats->type == BAT_SMALL && isUntargetableBat() ) { projectile->collisionIgnoreTargets.insert(getUID()); return true; } - if ( monsterSpecialState == BAT_REST ) + if ( myStats->type == BAT_SMALL && monsterSpecialState == BAT_REST ) { - miss = false; + return false; } bool backstab = false; bool flanking = false; real_t hitAngle = this->yawDifferenceFromEntity(projectile); if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc { - if ( monsterState == MONSTER_STATE_WAIT - || monsterState == MONSTER_STATE_PATH - || (monsterState == MONSTER_STATE_HUNT && uidToEntity(monsterTarget) == nullptr) ) + if ( behavior == &actPlayer ) { - // unaware monster, get backstab damage. - backstab = true; + if ( local_rng.rand() % 2 == 0 ) + { + flanking = true; + } } - else if ( local_rng.rand() % 2 == 0 ) + else { - // monster currently engaged in some form of combat maneuver - // 1 in 2 chance to flank defenses. - flanking = true; + if ( monsterState == MONSTER_STATE_WAIT + || monsterState == MONSTER_STATE_PATH + || (monsterState == MONSTER_STATE_HUNT && uidToEntity(monsterTarget) == nullptr) ) + { + // unaware monster, get backstab damage. + backstab = true; + } + else if ( local_rng.rand() % 2 == 0 ) + { + // monster currently engaged in some form of combat maneuver + // 1 in 2 chance to flank defenses. + flanking = true; + } } } @@ -553,13 +563,19 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { miss = false; } - else if ( flanking ) - { - miss = local_rng.rand() % 10 < (4 + (accuracyBonus ? -2 : 0)); - } else { - miss = local_rng.rand() % 10 < (6 + (accuracyBonus ? -2 : 0)); + int baseChance = myStats->type == BAT_SMALL ? 6 : 2; + if ( accuracyBonus ) + { + baseChance -= 2; + } + if ( flanking ) + { + baseChance -= 2; + } + baseChance = std::max(1, baseChance); + miss = local_rng.rand() % 10 < baseChance; } if ( miss ) @@ -567,9 +583,54 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) if ( projectile->collisionIgnoreTargets.find(getUID()) == projectile->collisionIgnoreTargets.end() ) { projectile->collisionIgnoreTargets.insert(getUID()); - if ( (parent && parent->behavior == &actPlayer) || (parent && parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader()) ) + if ( (parent && parent->behavior == &actPlayer) + || (parent && parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader()) + || (behavior == &actPlayer) + || (behavior == &actMonster && monsterAllyGetPlayerLeader()) ) + { + spawnDamageGib(this, 0, DamageGib::DMG_MISS, DamageGibDisplayType::DMG_GIB_MISS, true); + } + + if ( behavior == &actPlayer ) { - spawnDamageGib(this, 0, DamageGib::DMG_MISS, true, true); + if ( projectile->behavior == &actMagicMissile ) + { + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6287), Language::get(6295)); + } + else if ( projectile->behavior == &actArrow ) + { + if ( projectile->sprite == 167 ) + { + // bolt + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6287), Language::get(6292)); + } + else if ( projectile->sprite == 78 ) + { + // rock + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6287), Language::get(6293)); + } + else + { + // arrow + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6287), Language::get(6291)); + } + } + else if ( projectile->behavior == &actThrown ) + { + if ( projectile->skill[10] >= 0 && projectile->skill[10] < NUMITEMS ) + { + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6294), items[projectile->skill[10]].getUnidentifiedName()); + } + else + { + // generic "projectile" + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6294), Language::get(6296)); + } + } + else + { + messagePlayerColor(skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6286)); + } } } } @@ -831,12 +892,21 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { continue; } + if ( my->flags[NOCLIP_CREATURES] + && (entity->behavior == &actMonster || entity->behavior == &actPlayer) ) + { + continue; + } Stat* myStats = stats; //my->getStats(); //SEB <<< Stat* yourStats = entity->getStats(); if ( my->behavior == &actPlayer && entity->behavior == &actPlayer ) { continue; } + if ( projectileAttack && yourStats && yourStats->EFFECTS[EFF_AGILITY] ) + { + entityDodgeChance = true; + } if ( myStats && yourStats ) { if ( yourStats->leader_uid == my->getUID() ) From 6b064fa4e9559cd06c13f584df468cade64e7c2e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:22:37 +1100 Subject: [PATCH 175/244] * footstep noises - ignore some odd clicking sounds --- src/entity.cpp | 86 ++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index af6f6bc6a..dd541321e 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7803,20 +7803,20 @@ void Entity::attack(int pose, int charge, Entity* target) bool bat = hitstats && hitstats->type == BAT_SMALL; if ( bat && hit.entity->isUntargetableBat() ) { - miss = true; - } + miss = true; + } else if ( bat && hit.entity->monsterSpecialState == BAT_REST ) - { - miss = false; - } + { + miss = false; + } else if ( bat || (hitstats && hitstats->EFFECTS[EFF_AGILITY]) ) + { + Sint32 previousMonsterState = hit.entity->monsterState; + bool backstab = false; + bool flanking = false; + real_t hitAngle = hit.entity->yawDifferenceFromEntity(this); + if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc { - Sint32 previousMonsterState = hit.entity->monsterState; - bool backstab = false; - bool flanking = false; - real_t hitAngle = hit.entity->yawDifferenceFromEntity(this); - if ( (hitAngle >= 0 && hitAngle <= 2 * PI / 3) ) // 120 degree arc - { if ( hit.entity->behavior == &actPlayer ) { if ( local_rng.rand() % 2 == 0 ) @@ -7842,12 +7842,12 @@ void Entity::attack(int pose, int charge, Entity* target) } } - if ( backstab ) - { - miss = false; - } - else - { + if ( backstab ) + { + miss = false; + } + else + { int baseChance = bat ? 6 : 2; if ( flanking ) { @@ -7856,24 +7856,24 @@ void Entity::attack(int pose, int charge, Entity* target) miss = local_rng.rand() % 10 < baseChance; } - if ( myStats->weapon ) + if ( myStats->weapon ) + { + if ( myStats->weapon->type == ARTIFACT_SPEAR && !shapeshifted ) { - if ( myStats->weapon->type == ARTIFACT_SPEAR && !shapeshifted ) - { - miss = false; - } + miss = false; } } + } - if ( miss ) + if ( miss ) + { + if ( !hit.entity->isUntargetableBat() ) { - if ( !hit.entity->isUntargetableBat() ) - { if ( player >= 0 || (behavior == &actMonster && monsterAllyGetPlayerLeader()) || hit.entity->behavior == &actPlayer || hit.entity->monsterAllyGetPlayerLeader() ) - { + { spawnDamageGib(hit.entity, 0, DamageGib::DMG_MISS, DamageGibDisplayType::DMG_GIB_MISS, true); - } + } if ( hit.entity->behavior == &actPlayer ) { @@ -7923,10 +7923,10 @@ void Entity::attack(int pose, int charge, Entity* target) } } - hit.entity = nullptr; - } + hit.entity = nullptr; } } + } else { hit.entity = target; @@ -16026,30 +16026,32 @@ Uint32 Entity::getMonsterFootstepSound(int footstepType, int bootSprite) sound = 115; break; case MONSTER_FOOTSTEP_LEATHER: - sound = local_rng.rand() % 7; + { + static std::vector leatherSteps{ 0, 3, 4, 5, 6 }; + sound = leatherSteps[local_rng.rand() % leatherSteps.size()]; break; + } case MONSTER_FOOTSTEP_USE_BOOTS: + { if ( bootSprite >= 152 && bootSprite <= 155 ) // iron boots { - sound = 7 + local_rng.rand() % 7; + static std::vector ironSteps{ 7, 10, 11, 12, 13 }; + sound = ironSteps[local_rng.rand() % ironSteps.size()]; } - else if ( bootSprite >= 156 && bootSprite <= 159 ) // steel boots + else if ( (bootSprite >= 156 && bootSprite <= 159) // steel boots + || (bootSprite >= 499 && bootSprite <= 502) // crystal boots + || (bootSprite >= 521 && bootSprite <= 524) ) // artifact boots { - sound = 14 + local_rng.rand() % 7; - } - else if ( bootSprite >= 499 && bootSprite <= 502 ) // crystal boots - { - sound = 14 + local_rng.rand() % 7; - } - else if ( bootSprite >= 521 && bootSprite <= 524 ) // artifact boots - { - sound = 14 + local_rng.rand() % 7; + static std::vector steelSteps{ 14, 15, 16, 17, 18, 19, 20 }; + sound = steelSteps[local_rng.rand() % steelSteps.size()]; } else { - sound = local_rng.rand() % 7; + static std::vector defaultSteps{ 0, 3, 4, 5, 6 }; + sound = defaultSteps[local_rng.rand() % defaultSteps.size()]; } break; + } case MONSTER_FOOTSTEP_NONE: default: break; From 4ac0504bcccc59780ffe44084b5827e86854ba9b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:23:00 +1100 Subject: [PATCH 176/244] * fix player obituary not being % sanitised --- src/actplayer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 1a9553b60..73f59ea91 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -7550,7 +7550,8 @@ void actPlayer(Entity* my) continue; } char whatever[256]; - snprintf(whatever, sizeof(whatever), "%s %s", stats[PLAYER_NUM]->name, stats[PLAYER_NUM]->obituary); + std::string nameStr = messageSanitizePercentSign(stats[PLAYER_NUM]->name, nullptr); + snprintf(whatever, sizeof(whatever), "%s %s", nameStr.c_str(), stats[PLAYER_NUM]->obituary); whatever[255] = '\0'; messagePlayer(c, MESSAGE_OBITUARY, whatever); } From b5c0fcef98e3af70688288fdb2225787a4821be4 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:23:42 +1100 Subject: [PATCH 177/244] * daedalus shrine behavior --- src/actmagictrap.cpp | 363 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp index 69cdfd15c..323aa0ef0 100644 --- a/src/actmagictrap.cpp +++ b/src/actmagictrap.cpp @@ -501,3 +501,366 @@ void Entity::actTeleportShrine() } } } + +void actDaedalusShrine(Entity* my) +{ + if ( !my ) + { + return; + } + + my->actDaedalusShrine(); +} + +#define SHRINE_DAEDALUS_EXIT_UID my->skill[13] +#define SHRINE_TURN_DIR my->skill[14] +#define SHRINE_LAST_TOUCHED my->skill[15] +#define SHRINE_START_DIR my->fskill[0] +#define SHRINE_TARGET_DIR my->fskill[1] + +void daedalusShrineInteract(Entity* my, Entity* touched) +{ + if ( !my ) { return; } + + if ( multiplayer == SERVER ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + strcpy((char*)net_packet->data, "DAED"); + SDLNet_Write32(static_cast(my->getUID()), &net_packet->data[4]); + net_packet->address.host = net_clients[i - 1].host; + net_packet->address.port = net_clients[i - 1].port; + net_packet->len = 8; + sendPacketSafe(net_sock, -1, net_packet, i - 1); + } + } + } + + if ( my->shrineDaedalusState == 0 ) // default + { + if ( multiplayer != CLIENT ) + { + SHRINE_LAST_TOUCHED = touched ? touched->getUID() : 0; + } + + Entity* exitEntity = nullptr; + for ( node_t* node = map.entities->first; node; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( !entity ) { continue; } + if ( (entity->behavior == &actLadder && strcmp(map.name, "Hell")) || (entity->behavior == &actPortal && !strcmp(map.name, "Hell")) ) + { + if ( entity->behavior == &actLadder && entity->skill[3] != 1 ) + { + exitEntity = entity; + break; + } + if ( entity->behavior == &actPortal && entity->portalNotSecret == 1 ) + { + exitEntity = entity; + break; + } + } + } + + if ( exitEntity ) + { + real_t tangent = atan2(exitEntity->y - my->y, exitEntity->x - my->x); + while ( tangent >= 2 * PI ) + { + tangent -= 2 * PI; + } + while ( tangent < 0 ) + { + tangent += 2 * PI; + } + while ( my->yaw >= 2 * PI ) + { + my->yaw -= 2 * PI; + } + while ( my->yaw < 0 ) + { + my->yaw += 2 * PI; + } + SHRINE_TARGET_DIR = tangent; + SHRINE_DAEDALUS_EXIT_UID = exitEntity->getUID(); + SHRINE_START_DIR = my->yaw; + playSoundEntityLocal(my, 248, 128); + my->shrineDaedalusState = 1; + } + } + else if ( my->shrineDaedalusState == 2 ) + { + shrineDaedalusRevealMap(*my); + + if ( multiplayer != CLIENT ) + { + SHRINE_LAST_TOUCHED = touched ? touched->getUID() : 0; + spawnMagicEffectParticles(my->x, my->y, my->z, 174); + + if ( my->ticks >= (getMinotaurTimeToArrive() - TICKS_PER_SECOND * 30) && my->ticks <= getMinotaurTimeToArrive() ) + { + if ( touched && touched->getStats() ) + { + Stat* myStats = touched->getStats(); + if ( myStats->EFFECTS[EFF_SLOW] ) + { + touched->setEffect(EFF_SLOW, false, 0, true); + } + if ( touched->setEffect(EFF_FAST, true, std::max(TICKS_PER_SECOND * 15, myStats->EFFECTS_TIMERS[EFF_FAST]), true) ) + { + playSoundEntity(touched, 178, 128); + spawnMagicEffectParticles(touched->x, touched->y, touched->z, 174); + if ( touched->behavior == &actPlayer ) + { + messagePlayerColor(touched->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(768)); + } + } + } + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + messagePlayerColor(i, MESSAGE_INTERACTION, makeColorRGB(255, 0, 255), Language::get(6285)); + } + } + else + { + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + messagePlayerColor(i, MESSAGE_INTERACTION, makeColorRGB(255, 0, 255), Language::get(6267)); + } + } + } + } +} + +void Entity::actDaedalusShrine() +{ + if ( this->ticks == 1 ) + { + this->createWorldUITooltip(); + } + + this->removeLightField(); + if ( shrineActivateDelay == 0 ) + { + this->light = addLight(this->x / 16, this->y / 16, "teleport_shrine"); + spawnAmbientParticles(80, 576, 10 + local_rng.rand() % 40, 1.0, false); + } + +#ifdef USE_FMOD + if ( shrineAmbience == 0 ) + { + shrineAmbience--; + stopEntitySound(); + entity_sound = playSoundEntityLocal(this, 149, 16); + } + if ( entity_sound ) + { + bool playing = false; + entity_sound->isPlaying(&playing); + if ( !playing ) + { + entity_sound = nullptr; + } + } +#else + shrineAmbience--; + if ( shrineAmbience <= 0 ) + { + shrineAmbience = TICKS_PER_SECOND * 30; + playSoundEntityLocal(this, 149, 16); + } +#endif + + if ( !shrineInit ) + { + shrineInit = 1; + shrineSpellEffect = SPELL_SPEED; + } + + + if ( shrineActivateDelay > 0 ) + { + --shrineActivateDelay; + if ( shrineActivateDelay == 0 ) + { + if ( multiplayer != CLIENT ) + { + serverUpdateEntitySkill(this, 7); + } + } + } + + if ( shrineDaedalusState == 1 ) + { + // point to exit + int dir = 0; + real_t startDir = fskill[0]; + real_t targetDir = fskill[1]; + + int diff = static_cast((startDir - targetDir) * 180.0 / PI) % 360; + if ( diff < 0 ) + { + diff += 360; + } + if ( diff < 180 ) + { + dir = 1; + } + + real_t speed = dir == 1 ? -2.f : 2.f; + real_t scale = 1.0; + { + if ( diff > 180 ) + { + diff -= 360; + } + scale = std::max(0.05, (abs(diff) / 180.0)) / (real_t)TICKS_PER_SECOND; + speed *= scale; + } + + if ( limbAnimateToLimit(this, ANIMATE_YAW, speed, targetDir, false, 0.0) ) + { + shrineDaedalusState = 2; + shrineDaedalusRevealMap(*this); + + if ( multiplayer != CLIENT ) + { + spawnMagicEffectParticles(x, y, z, 174); + Entity* touched = nullptr; + if ( skill[15] != 0 ) + { + touched = uidToEntity(skill[15]); + } + + if ( this->ticks >= (getMinotaurTimeToArrive() - TICKS_PER_SECOND * 30) && this->ticks <= getMinotaurTimeToArrive() ) + { + if ( touched && touched->getStats() ) + { + Stat* myStats = touched->getStats(); + if ( myStats->EFFECTS[EFF_SLOW] ) + { + touched->setEffect(EFF_SLOW, false, 0, true); + } + if ( touched->setEffect(EFF_FAST, true, std::max(TICKS_PER_SECOND * 15, myStats->EFFECTS_TIMERS[EFF_FAST]), true) ) + { + playSoundEntity(touched, 178, 128); + spawnMagicEffectParticles(touched->x, touched->y, touched->z, 174); + if ( touched->behavior == &actPlayer ) + { + messagePlayerColor(touched->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(768)); + } + } + } + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + messagePlayerColor(i, MESSAGE_INTERACTION, makeColorRGB(255, 0, 255), Language::get(6285)); + } + } + else + { + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + messagePlayerColor(i, MESSAGE_INTERACTION, makeColorRGB(255, 0, 255), Language::get(6267)); + } + } + } + } + } + + if ( multiplayer == CLIENT ) + { + return; + } + + // using + //if ( this->isInteractWithMonster() ) + //{ + // Entity* monsterInteracting = uidToEntity(this->interactedByMonster); + // if ( monsterInteracting ) + // { + // if ( shrineActivateDelay == 0 ) + // { + // std::vector>> allShrines; + // for ( node_t* node = map.entities->first; node; node = node->next ) + // { + // Entity* entity = (Entity*)node->element; + // if ( !entity ) { continue; } + // if ( entity->behavior == &::actTeleportShrine ) + // { + // allShrines.push_back(std::make_pair(entity, std::make_pair((int)(entity->x / 16), (int)(entity->y / 16)))); + // } + // } + + // Entity* selectedShrine = nullptr; + // for ( size_t s = 0; s < allShrines.size(); ++s ) + // { + // if ( allShrines[s].first == this ) + // { + // // find next one in list + // if ( (s + 1) >= allShrines.size() ) + // { + // selectedShrine = allShrines[0].first; + // } + // else + // { + // selectedShrine = allShrines[s + 1].first; + // } + // break; + // } + // } + + // if ( selectedShrine ) + // { + // playSoundEntity(this, 252, 128); + // //messagePlayer(i, MESSAGE_INTERACTION, Language::get(4301)); + + // if ( auto leader = monsterInteracting->monsterAllyGetPlayerLeader() ) + // { + // Compendium_t::Events_t::eventUpdateWorld(leader->skill[2], Compendium_t::CPDM_OBELISK_FOLLOWER_USES, "obelisk", 1); + // } + + // Entity* spellTimer = createParticleTimer(this, 200, 625); + // spellTimer->particleTimerPreDelay = 0; // wait x ticks before animation. + // spellTimer->particleTimerEndAction = PARTICLE_EFFECT_SHRINE_TELEPORT; // teleport behavior of timer. + // spellTimer->particleTimerEndSprite = 625; // sprite to use for end of timer function. + // spellTimer->particleTimerCountdownAction = 1; + // spellTimer->particleTimerCountdownSprite = 625; + // spellTimer->particleTimerTarget = static_cast(selectedShrine->getUID()); // get the target to teleport around. + // spellTimer->particleTimerVariable1 = 1; // distance of teleport in tiles + // spellTimer->particleTimerVariable2 = monsterInteracting->getUID(); // which player to teleport + // if ( multiplayer == SERVER ) + // { + // serverSpawnMiscParticles(this, PARTICLE_EFFECT_SHRINE_TELEPORT, 625); + // } + // shrineActivateDelay = 250; + // serverUpdateEntitySkill(this, 7); + // } + // } + // this->clearMonsterInteract(); + // return; + // } + // this->clearMonsterInteract(); + //} + for ( int i = 0; i < MAXPLAYERS; i++ ) + { + if ( selectedEntity[i] == this || client_selected[i] == this ) + { + if ( inrange[i] && Player::getPlayerInteractEntity(i) ) + { + if ( shrineActivateDelay > 0 ) + { + messagePlayer(i, MESSAGE_INTERACTION, Language::get(4300)); + break; + } + + daedalusShrineInteract(this, Player::getPlayerInteractEntity(i)); + shrineActivateDelay = TICKS_PER_SECOND * 5; + serverUpdateEntitySkill(this, 7); + break; + } + } + } +} \ No newline at end of file From 20335fc6dc2cfebc010bde049fab78d3970f06e9 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:23:55 +1100 Subject: [PATCH 178/244] * bell noclip creatures --- src/maps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/maps.cpp b/src/maps.cpp index d62fa2efc..a076096d7 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -8538,6 +8538,7 @@ void assignActions(map_t* map) childEntity->flags[PASSABLE] = true; childEntity->flags[UNCLICKABLE] = false; childEntity->flags[UPDATENEEDED] = true; + childEntity->flags[NOCLIP_CREATURES] = true; childEntity->z = entity->z; TileEntityList.addEntity(*childEntity); node_t* tempNode = list_AddNodeLast(&entity->children); From 97d1ce50ee6cf083164b17ccf9cee7797d545512 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 04:24:08 +1100 Subject: [PATCH 179/244] * lang update --- lang/en.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index 81f236491..da7bdef63 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6495,5 +6495,34 @@ Magic Required: %d (%s)# 6266 Not enough Lore Points available. Unlock Achievements to earn more!# 6267 The exit is revealed!# +6268 You pull the rope...# +6269 bell# +6270 bell# +6271 Ring bell# +6272 Something was lodged inside the bell!# +6273 This bell is broken!# +6274 But nothing happens.# +6275 The bell comes crashing down!# +6276 You are struck by a falling bell!# +6277 gets their bell rung.# +6278 Bell# +6279 The clapper breaks off!# +6280 The chime invigorates your %s!# +6281 might# +6282 stamina# +6283 agility# +6284 mentality# +6285 The minotaur is nigh, hurry!# +6286 You dodged an attack!# +6287 You dodged a %s!# +6288 Your stamina returns back to normal.# +6289 Your agility returns back to normal.# +6290 Your mentality returns back to normal.# +6291 flying arrow# +6292 flying bolt# +6293 flying rock# +6294 You dodged a flying %s!# +6295 spell# +6296 projectile# 6300 END# From 62154a2b1bc17bd2d732052437025f4e9f9997fd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 16:06:41 +1100 Subject: [PATCH 180/244] * bell add new note sprites --- src/actgeneral.cpp | 31 +++++++++++++++++++++---------- src/actgib.cpp | 4 ++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index e3965345c..e23f302b5 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -3886,6 +3886,14 @@ void actBell(Entity* my) return; } + enum BellSpriteNotes : int + { + NOTE_DOUBLE_EIGHTH = 192, + NOTE_EIGHTH = 198, + NOTE_REST = 199, + NOTE_CRASH = 200 + }; + enum BellEvents : int { BELL_RING_BUFF = 1, @@ -4018,6 +4026,7 @@ void actBell(Entity* my) messagePlayer(0, MESSAGE_DEBUG, "Bell event: %d", BELL_CURRENT_EVENT); } #endif + static ConsoleVariable cvar_bell_crash("/bell_crash", false); if ( multiplayer == CLIENT ) { @@ -4106,7 +4115,13 @@ void actBell(Entity* my) { BELL_CURRENT_EVENT = BELL_RING_BUFF; --BELL_USES; - if ( BELL_USES <= 0 ) + + if ( *cvar_bell_crash && (svFlags & SV_FLAG_CHEATS) ) + { + BELL_USES = 0; + BELL_CURRENT_EVENT = BELL_CRASH; + } + else if ( BELL_USES <= 0 ) { if ( rng.rand() % 5 == 0 ) { @@ -4331,6 +4346,8 @@ void actBell(Entity* my) playSoundPlayer(i, *cvar_bell_crash_sfx, 32); } } + + spawnDamageGib(child, NOTE_CRASH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); } child->flags[INVISIBLE] = true; if ( multiplayer == SERVER ) @@ -4429,10 +4446,7 @@ void actBell(Entity* my) { if ( (ticks - child->skill[3] == *cvar_bell_dong1) ) { - if ( Entity* gib = spawnDamageGib(child, 192, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE) ) - { - gib->z += 8.0; - } + spawnDamageGib(child, NOTE_REST, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE); } } } @@ -4453,10 +4467,7 @@ void actBell(Entity* my) } playSoundEntity(my, 690, vol); playSoundPlayer(clientnum, 690, vol / 4); - if ( Entity* gib = spawnDamageGib(child, 192, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE) ) - { - gib->z += 8.0; - } + spawnDamageGib(child, NOTE_EIGHTH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE); } } } @@ -4683,7 +4694,7 @@ void actBell(Entity* my) if ( entity->setEffect(statusEffect, true, std::max(stats->EFFECTS_TIMERS[statusEffect], duration), false) ) { playSoundEntity(entity, 166, 128); - spawnDamageGib(entity, 200, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); + spawnDamageGib(entity, NOTE_DOUBLE_EIGHTH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); if ( entity->behavior == &actPlayer ) { messagePlayerColor(entity->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(6280), lang); diff --git a/src/actgib.cpp b/src/actgib.cpp index 4a0d36cef..e56753fa1 100644 --- a/src/actgib.cpp +++ b/src/actgib.cpp @@ -434,6 +434,10 @@ Entity* spawnDamageGib(Entity* parentent, Sint32 dmgAmount, int gibDmgType, int { entity->z -= 4; } + if ( parentent->sprite == 1475 ) // bell + { + entity->z += 8.0; + } entity->yaw = (local_rng.rand() % 360) * PI / 180.0; entity->vel_x = vel * cos(entity->yaw); entity->vel_y = vel * sin(entity->yaw); From 18299f4df604ee8e91527f17b67644d1a2254fce Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 16:07:07 +1100 Subject: [PATCH 181/244] * agility effect increase to +5 / 25% and 30% evade --- src/collision.cpp | 4 ++-- src/entity.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 94afaaa66..a3b0ea40d 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -558,14 +558,14 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) } } - bool accuracyBonus = projectile->behavior == &actMagicMissile; + bool accuracyBonus = projectile->behavior == &actMagicMissile && myStats->type == BAT_SMALL; if ( backstab ) { miss = false; } else { - int baseChance = myStats->type == BAT_SMALL ? 6 : 2; + int baseChance = myStats->type == BAT_SMALL ? 6 : 3; if ( accuracyBonus ) { baseChance -= 2; diff --git a/src/entity.cpp b/src/entity.cpp index dd541321e..1e50ad168 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -5792,7 +5792,7 @@ Sint32 statGetDEX(Stat* entitystats, Entity* my) if ( entitystats->EFFECTS[EFF_AGILITY] ) { - DEX += 3; + DEX += (std::max(5, DEX / 4)); } if ( my && my->monsterAllyGetPlayerLeader() ) @@ -7848,7 +7848,7 @@ void Entity::attack(int pose, int charge, Entity* target) } else { - int baseChance = bat ? 6 : 2; + int baseChance = bat ? 6 : 3; if ( flanking ) { baseChance = std::max(1, baseChance - 2); From b39eabbda1ddb1ed5beb7057ebd422d6c22df00a Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 16 Oct 2024 16:08:28 +1100 Subject: [PATCH 182/244] * end bell effect msgs --- lang/en.txt | 2 +- src/entity.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lang/en.txt b/lang/en.txt index da7bdef63..8b7767519 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6508,7 +6508,7 @@ Unlock Achievements to earn more!# 6278 Bell# 6279 The clapper breaks off!# 6280 The chime invigorates your %s!# -6281 might# +6281 strength# 6282 stamina# 6283 agility# 6284 mentality# diff --git a/src/entity.cpp b/src/entity.cpp index 1e50ad168..31a4ef3b9 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1358,6 +1358,15 @@ void Entity::effectTimes() case EFF_POTION_STR: messagePlayer(player, MESSAGE_STATUS, Language::get(3355)); break; + case EFF_AGILITY: + messagePlayer(player, MESSAGE_STATUS, Language::get(6289)); + break; + case EFF_CON_BONUS: + messagePlayer(player, MESSAGE_STATUS, Language::get(6288)); + break; + case EFF_PWR: + messagePlayer(player, MESSAGE_STATUS, Language::get(6290)); + break; case EFF_LEVITATING: ; //To make the compiler shut up: "error: a label can only be part of a statement and a declaration is not a statement" dissipate = true; //Remove the effect by default. From 58a24c7823683dffa1124496b0c84883c9381b5d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 00:51:35 +1100 Subject: [PATCH 183/244] * lobby shows visibility floating status --- src/ui/MainMenu.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index a9c262e6d..df3197855 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -18624,6 +18624,57 @@ namespace MainMenu { { float_warning_add(lobbyWarnings, "3", "Hardcore Enabled"); } + if ( multiplayer == SERVER ) + { + if ( LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_CROSSPLAY ) { +#ifdef USE_EOS + if ( EOS.currentPermissionLevel == EOS_ELobbyPermissionLevel::EOS_LPL_PUBLICADVERTISED ) + { + if ( EOS.bFriendsOnly ) + { +#ifdef NINTENDO + float_warning_add(lobbyWarnings, "3", Language::get(6301)); // friends shows as invite only just in case +#else + float_warning_add(lobbyWarnings, "3", Language::get(6300)); +#endif + } + else + { + float_warning_add(lobbyWarnings, "3", Language::get(6299)); + } + } + else + { + // private + float_warning_add(lobbyWarnings, "3", Language::get(6301)); + } +#endif // USE_EOS + } + else if ( LobbyHandler.getHostingType() == LobbyHandler_t::LobbyServiceType::LOBBY_STEAM ) { +#ifdef STEAMWORKS + if ( ::currentLobbyType == k_ELobbyTypeInvisible ) + { + float_warning_add(lobbyWarnings, "3", Language::get(6301)); + } + else if ( ::currentLobbyType == k_ELobbyTypePublic ) + { + auto lobby = static_cast(::currentLobby); + if ( lobby ) + { + const char* val = SteamMatchmaking()->GetLobbyData(*lobby, "friends_only"); + if ( val && !strcmp(val, "true") ) + { + float_warning_add(lobbyWarnings, "3", Language::get(6300)); + } + else + { + float_warning_add(lobbyWarnings, "3", Language::get(6299)); + } + } + } +#endif // STEAMWORKS + } + } int totalWidth = 0; for ( auto f : lobbyWarnings->getFrames() ) From 26a815e28183450d532fef4ae5ebd105f0bdc797 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 00:53:12 +1100 Subject: [PATCH 184/244] * bell add heal effect, burning interaction --- src/actgeneral.cpp | 125 ++++++++++++++++++++++++++++++++++++++++----- src/collision.cpp | 63 +++++++++++++++++++++-- src/game.cpp | 5 ++ 3 files changed, 178 insertions(+), 15 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index e23f302b5..616e023ce 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -3651,6 +3651,8 @@ void bellAttractMonsters(Entity* my) #define BELL_CLAPPER_BROKEN my->skill[9] #define BELL_BULB_BROKEN my->skill[10] #define BELL_BUFF_TYPE my->skill[11] +#define BELL_BURNING_TIMER my->skill[12] +#define BELL_PULLED_TO_BREAK my->skill[13] int getBellDmgOnEntity(Entity* entity) { @@ -3911,6 +3913,7 @@ void actBell(Entity* my) BUFF_CON, BUFF_PWR, BUFF_DEX, + BUFF_HEAL, BUFF_ENUM_END, }; @@ -4040,7 +4043,7 @@ void actBell(Entity* my) } Entity* touched = nullptr; - if ( multiplayer != CLIENT ) // interaction + if ( multiplayer != CLIENT && !my->flags[BURNING] && !my->flags[INVISIBLE] ) // interaction { if ( my->isInteractWithMonster() ) { @@ -4132,6 +4135,11 @@ void actBell(Entity* my) BELL_CURRENT_EVENT = BELL_CLAPPER_BREAK; } } + + if ( BELL_CURRENT_EVENT == BELL_CRASH ) + { + BELL_PULLED_TO_BREAK = touched->getUID(); + } } } @@ -4158,7 +4166,32 @@ void actBell(Entity* my) } serverUpdateEntitySkill(my, 7); // use delay } + } + else if ( multiplayer != CLIENT && my->flags[BURNING] ) + { + if ( BELL_BURNING_TIMER == 0 ) + { + BELL_BURNING_TIMER = (1 + local_rng.rand() % 3) * TICKS_PER_SECOND; + playSoundEntity(my, 512, 64); + } + else if ( BELL_BURNING_TIMER > 0 ) + { + BELL_BURNING_TIMER--; + if ( BELL_BURNING_TIMER <= 0 ) + { + BELL_BURNING_TIMER = -1; + bellBreakBulb(my, true); + my->flags[BURNING] = false; + my->flags[INVISIBLE] = true; + serverUpdateEntitySkill(my, INVISIBLE); + serverUpdateEntitySkill(my, BURNING); + } + } + } + if ( multiplayer == CLIENT && my->flags[INVISIBLE] && my->flags[BURNING] ) + { + my->flags[BURNING] = false; } if ( BELL_USE_DELAY > 0 ) @@ -4242,6 +4275,7 @@ void actBell(Entity* my) Entity* collided = nullptr; if ( multiplayer != CLIENT ) { + Entity* puller = uidToEntity(BELL_PULLED_TO_BREAK); auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(child, 2); for ( std::vector::iterator it = entLists.begin(); it != entLists.end() && !collided; ++it ) { @@ -4263,14 +4297,11 @@ void actBell(Entity* my) continue; } - if ( ParticleEmitterHit_t* particleEmitterHitProps = getParticleEmitterHitProps(my->getUID(), entity) ) + if ( child->collisionIgnoreTargets.find(entity->getUID()) != child->collisionIgnoreTargets.end() ) { - if ( particleEmitterHitProps->hits > 0 ) - { - continue; - } - particleEmitterHitProps->hits++; + continue; } + child->collisionIgnoreTargets.insert(entity->getUID()); if ( entity->behavior == &actPlayer ) { @@ -4295,24 +4326,68 @@ void actBell(Entity* my) } } } + playSoundEntity(entity, 28, 64); Entity* gib = spawnGib(entity); int dmg = getBellDmgOnEntity(entity); Sint32 oldHP = stats->HP; entity->modHP(-dmg); - if ( entity->behavior == &actPlayer && stats->HP < oldHP ) + if ( entity->behavior == &actPlayer ) { - Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "bell", oldHP - stats->HP); + if ( stats->HP < oldHP ) + { + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_DAMAGE, "bell", oldHP - stats->HP); + } + + if ( !puller ) + { + int playertarget = entity->skill[2]; + if ( playertarget >= 0 && players[playertarget]->isLocalPlayer() ) + { + DamageIndicatorHandler.insert(playertarget, child->x, child->y, stats->HP < oldHP); + } + else if ( playertarget > 0 && multiplayer == SERVER && !players[playertarget]->isLocalPlayer() ) + { + strcpy((char*)net_packet->data, "DAMI"); + SDLNet_Write32(child->x, &net_packet->data[4]); + SDLNet_Write32(child->y, &net_packet->data[8]); + net_packet->data[12] = (stats->HP < oldHP) ? 1 : 0; + net_packet->address.host = net_clients[playertarget - 1].host; + net_packet->address.port = net_clients[playertarget - 1].port; + net_packet->len = 13; + sendPacketSafe(net_sock, -1, net_packet, playertarget - 1); + } + } } entity->setObituary(Language::get(6277)); stats->killer = KilledBy::BELL; - if ( entity->behavior == &actPlayer ) + + if ( !strcmp(stats->name, "") ) + { + updateEnemyBar(puller, entity, getMonsterLocalizedName(stats->type).c_str(), stats->HP, stats->MAXHP, + false, DamageGib::DMG_STRONGEST); + } + else { - if ( stats->HP <= 0 ) + updateEnemyBar(puller, entity, stats->name, stats->HP, stats->MAXHP, + false, DamageGib::DMG_STRONGEST); + } + + if ( stats->HP <= 0 && oldHP > 0 ) + { + if ( entity->behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], Compendium_t::CPDM_TRAP_KILLED_BY, "bell", 1); } + if ( puller && puller != entity ) + { + puller->awardXP(entity, true, true); + if ( puller->behavior == &actPlayer ) + { + messagePlayerMonsterEvent(puller->skill[2], makeColorRGB(0, 255, 0), *stats, Language::get(692), Language::get(697), MSG_COMBAT); + } + } } if ( stats->type == MINOTAUR ) @@ -4683,6 +4758,9 @@ void actBell(Entity* my) lang = Language::get(6284); statusEffect = EFF_PWR; break; + case BUFF_HEAL: + statusEffect = SPELL_HEALING; + break; default: break; } @@ -4691,7 +4769,30 @@ void actBell(Entity* my) { if ( Stat* stats = entity->getStats() ) { - if ( entity->setEffect(statusEffect, true, std::max(stats->EFFECTS_TIMERS[statusEffect], duration), false) ) + if ( statusEffect == SPELL_HEALING ) + { + int amount = 15; + entity->modHP(amount); + entity->modMP(amount); + if ( svFlags & SV_FLAG_HUNGER ) + { + if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + { + Sint32 hungerPointPerMana = entity->playerInsectoidHungerValueOfManaPoint(*stats); + stats->HUNGER += amount * hungerPointPerMana; + stats->HUNGER = std::min(999, stats->HUNGER); + serverUpdateHunger(entity->skill[2]); + } + } + playSoundEntity(entity, 168, 128); + spawnDamageGib(entity, NOTE_DOUBLE_EIGHTH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); + spawnMagicEffectParticles(entity->x, entity->y, entity->z, 169); + if ( entity->behavior == &actPlayer ) + { + messagePlayerColor(entity->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(6298)); + } + } + else if ( entity->setEffect(statusEffect, true, std::max(stats->EFFECTS_TIMERS[statusEffect], duration), false) ) { playSoundEntity(entity, 166, 128); spawnDamageGib(entity, NOTE_DOUBLE_EIGHTH, DamageGib::DMG_STRONGEST, DamageGibDisplayType::DMG_GIB_SPRITE, true); diff --git a/src/collision.cpp b/src/collision.cpp index a3b0ea40d..9988643fa 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -512,6 +512,48 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { return true; } + + if ( behavior == &actBell ) + { + if ( !flags[BURNING] ) + { + if ( projectile->behavior == &actMagicMissile ) + { + if ( projectile->children.first && projectile->children.first->element ) + { + if ( spell_t* spell = (spell_t*)projectile->children.first->element ) + { + if ( spell->ID == SPELL_FIREBALL || spell->ID == SPELL_SLIME_FIRE ) + { + SetEntityOnFire(); + if ( parent && flags[BURNING] ) + { + skill[13] = parent->getUID(); // burning inflicted by for bell + if ( parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_INTERACTION, Language::get(6297)); + } + } + } + } + } + } + else if ( projectile->behavior == &actArrow && projectile->arrowQuiverType == QUIVER_FIRE ) + { + SetEntityOnFire(parent); + if ( parent && flags[BURNING] ) + { + skill[13] = parent->getUID(); // burning inflicted by for bell + if ( parent->behavior == &actPlayer ) + { + messagePlayer(parent->skill[2], MESSAGE_INTERACTION, Language::get(6297)); + } + } + } + } + return true; + } + if ( behavior == &actMonster || behavior == &actPlayer ) { if ( Stat* myStats = getStats() ) @@ -818,6 +860,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { type = my->getMonsterTypeFromSprite(); } + bool entityDodgeChance = false; for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) { list_t* currentList = *it; @@ -828,18 +871,23 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { continue; } + entityDodgeChance = false; if ( entity->flags[PASSABLE] ) { if ( my->behavior == &actBoulder && (entity->behavior == &actMonster && entity->sprite == 886) ) { // 886 is gyrobot, as they are passable, force collision here. } + else if ( projectileAttack && entity->sprite == 1478 && multiplayer != CLIENT ) + { + // bell rope, check for burning + entityDodgeChance = true; + } else { continue; } } - bool entityDodgeChance = false; if ( entity->behavior == &actParticleTimer && static_cast(entity->particleTimerTarget) == my->getUID() ) { continue; @@ -1039,8 +1087,17 @@ int barony_clear(real_t tx, real_t ty, Entity* my) sizex /= *cvar_linetrace_smallcollision; sizey /= *cvar_linetrace_smallcollision; } - const real_t eymin = entity->y - sizey, eymax = entity->y + sizey; - const real_t exmin = entity->x - sizex, exmax = entity->x + sizex; + real_t eymin = entity->y - sizey, eymax = entity->y + sizey; + real_t exmin = entity->x - sizex, exmax = entity->x + sizex; + if ( entity->sprite == 1478 ) + { + real_t xoffset = entity->focalx * cos(entity->yaw) + entity->focaly * cos(entity->yaw + PI / 2); + real_t yoffset = entity->focalx * sin(entity->yaw) + entity->focaly * sin(entity->yaw + PI / 2); + eymin += yoffset; + eymax += yoffset; + exmin += xoffset; + exmax += xoffset; + } if ( (entity->sizex > 0) && ((txmin >= exmin && txmin < exmax) || (txmax >= exmin && txmax < exmax) || (txmin <= exmin && txmax > exmax)) ) { if ( (entity->sizey > 0) && ((tymin >= eymin && tymin < eymax) || (tymax >= eymin && tymax < eymax) || (tymin <= eymin && tymax > eymax)) ) diff --git a/src/game.cpp b/src/game.cpp index 69124d670..f6a1e592b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1209,6 +1209,11 @@ void gameLogic(void) flame->x += local_rng.rand() % (entity->sizex * 2 + 1) - entity->sizex; flame->y += local_rng.rand() % (entity->sizey * 2 + 1) - entity->sizey; flame->z += local_rng.rand() % 5 - 2; + if ( entity->behavior == &actBell ) + { + flame->x += entity->focalx * cos(entity->yaw) + entity->focaly * cos(entity->yaw + PI / 2); + flame->y += entity->focalx * sin(entity->yaw) + entity->focaly * sin(entity->yaw + PI / 2); + } } } } From c8892f11258eb672248ec5c9113a4d5df7e4fb9f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 00:53:43 +1100 Subject: [PATCH 185/244] * bell spawn citadel, add burnable tag, reduce size of rope --- src/maps.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index a076096d7..bf7b6f42d 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -4363,7 +4363,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } { - if ( !strcmp(map.name, "The Ruins") ) + if ( !strcmp(map.name, "The Ruins") || !strcmp(map.name, "Citadel") ) { int numBells = 0; if ( currentlevel % 2 == 0 ) @@ -8517,12 +8517,13 @@ void assignActions(map_t* map) { entity->x += 8; entity->y += 8; - entity->sizex = 4; - entity->sizey = 4; + entity->sizex = 2; + entity->sizey = 2; entity->z = 0.0; entity->behavior = &actBell; entity->flags[PASSABLE] = true; entity->flags[BLOCKSIGHT] = false; + entity->flags[BURNABLE] = true; entity->sprite = 1478; // rope entity->seedEntityRNG(map_rng.getU32()); entity->skill[11] = map_rng.rand(); // buff type From af47b8b520003ffa0046c2770d4f5e8dde775619 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 01:05:18 +1100 Subject: [PATCH 186/244] * flaming creatures can touch bell rope --- src/collision.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/collision.cpp b/src/collision.cpp index 9988643fa..b09fc7567 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -508,9 +508,12 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) { return false; } - if ( projectile->collisionIgnoreTargets.find(getUID()) != projectile->collisionIgnoreTargets.end() ) + if ( !(projectile->behavior == &actMonster || projectile->behavior == &actPlayer) ) { - return true; + if ( projectile->collisionIgnoreTargets.find(getUID()) != projectile->collisionIgnoreTargets.end() ) + { + return true; + } } if ( behavior == &actBell ) @@ -550,12 +553,28 @@ bool Entity::collisionProjectileMiss(Entity* parent, Entity* projectile) } } } + else if ( projectile->flags[BURNING] && (projectile->behavior == &actMonster || projectile->behavior == &actPlayer) ) + { + SetEntityOnFire(projectile); + if ( flags[BURNING] ) + { + skill[13] = projectile->getUID(); // burning inflicted by for bell + if ( projectile->behavior == &actPlayer ) + { + messagePlayer(projectile->skill[2], MESSAGE_INTERACTION, Language::get(6297)); + } + } + } } return true; } if ( behavior == &actMonster || behavior == &actPlayer ) { + if ( projectile->behavior == &actMonster || projectile->behavior == &actPlayer ) + { + return false; + } if ( Stat* myStats = getStats() ) { if ( myStats->type == BAT_SMALL || myStats->EFFECTS[EFF_AGILITY] ) @@ -878,7 +897,9 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { // 886 is gyrobot, as they are passable, force collision here. } - else if ( projectileAttack && entity->sprite == 1478 && multiplayer != CLIENT ) + else if ( entity->sprite == 1478 + && (projectileAttack + || (my && my->flags[BURNING] && (my->behavior == &actMonster || my->behavior == &actPlayer))) && multiplayer != CLIENT ) { // bell rope, check for burning entityDodgeChance = true; @@ -1104,7 +1125,7 @@ int barony_clear(real_t tx, real_t ty, Entity* my) { if ( multiplayer != CLIENT ) { - if ( projectileAttack && entityDodgeChance ) + if ( entityDodgeChance ) { if ( entity->collisionProjectileMiss(parent, my) ) { From b9e8dec0eef796695c334e2b70ed056c5a42c24f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 01:07:35 +1100 Subject: [PATCH 187/244] * bat harder to raise block --- src/entity.cpp | 54 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 31a4ef3b9..608db426b 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -9441,29 +9441,41 @@ void Entity::attack(int pose, int charge, Entity* target) if ( itemCategory(hitstats->shield) == ARMOR || (hitstats->defending) ) { - if ( (local_rng.rand() % 15 == 0 && damage > 0) || (damage == 0 && local_rng.rand() % 8 == 0) ) + int roll = 16; + if ( damage == 0 ) { - bool increaseSkill = true; - if ( hit.entity->behavior == &actPlayer && behavior == &actPlayer ) - { - increaseSkill = false; - } - else if ( hit.entity->behavior == &actPlayer && this->monsterAllyGetPlayerLeader() ) - { - increaseSkill = false; - } - else if ( hitstats->EFFECTS[EFF_SHAPESHIFT] ) - { - increaseSkill = false; - } - else if ( itemCategory(hitstats->shield) != ARMOR - && hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) - { - increaseSkill = false; // non-shield offhands dont increase skill past 40. - } - if ( increaseSkill ) + roll /= 2; + } + if ( myStats->type == BAT_SMALL ) + { + roll = 64; + } + if ( roll > 0 ) + { + if ( (local_rng.rand() % roll == 0 && damage > 0) || (damage == 0 && local_rng.rand() % roll == 0) ) { - hit.entity->increaseSkill(PRO_SHIELD); // increase shield skill + bool increaseSkill = true; + if ( hit.entity->behavior == &actPlayer && behavior == &actPlayer ) + { + increaseSkill = false; + } + else if ( hit.entity->behavior == &actPlayer && this->monsterAllyGetPlayerLeader() ) + { + increaseSkill = false; + } + else if ( hitstats->EFFECTS[EFF_SHAPESHIFT] ) + { + increaseSkill = false; + } + else if ( itemCategory(hitstats->shield) != ARMOR + && hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) + { + increaseSkill = false; // non-shield offhands dont increase skill past 40. + } + if ( increaseSkill ) + { + hit.entity->increaseSkill(PRO_SHIELD); // increase shield skill + } } } } From 4ef9790c96eabccc60e37d0f4554d471d5453d83 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 18:55:29 +1100 Subject: [PATCH 188/244] * debug stuffs --- src/actmonster.cpp | 10 +++++----- src/game.cpp | 9 ++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index aebf6fc31..0ca2847ae 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -4630,7 +4630,7 @@ void actMonster(Entity* my) messagePlayer(0, "defending!"); }*/ - //if ( myStats->type == BUGBEAR ) + //if ( myStats->type == GNOME ) //{ // std::string state_string; // @@ -4657,8 +4657,8 @@ void actMonster(Entity* my) // break; // } // - // messagePlayer(0, MESSAGE_DEBUG, "%s, ATK: %d hittime:%d, atktime:%d, (%d|%d), timer:%d", - // state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message. + // messagePlayer(0, MESSAGE_DEBUG, "uid: %d | ticks: %d | %s, ATK: %d hittime:%d, atktime:%d, (%d|%d), timer:%d", + // my->getUID(), ticks, state_string.c_str(), my->monsterAttack, my->monsterHitTime, MONSTER_ATTACKTIME, devilstate, devilacted, my->monsterSpecialTimer); //Debug message. //} //Begin state machine @@ -4959,7 +4959,7 @@ void actMonster(Entity* my) } // follow the leader :) - if ( myStats->leader_uid != 0 + if ( myStats->leader_uid != 0 && my->monsterAllyState == ALLY_STATE_DEFAULT && !myStats->EFFECTS[EFF_FEAR] && !myStats->EFFECTS[EFF_DISORIENTED] @@ -6664,7 +6664,7 @@ void actMonster(Entity* my) } // follow the leader :) - if ( uidToEntity(my->monsterTarget) == nullptr + if ( uidToEntity(my->monsterTarget) == nullptr && myStats->leader_uid != 0 && my->monsterAllyState == ALLY_STATE_DEFAULT && !monsterIsImmobileTurret(my, myStats) diff --git a/src/game.cpp b/src/game.cpp index f6a1e592b..cf72afced 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1807,6 +1807,13 @@ void gameLogic(void) // 1000 * std::chrono::duration_cast>(t2 - t).count()); accum += 1000 * std::chrono::duration_cast>(t2 - t).count(); entityAccum[entity->sprite] += 1000 * std::chrono::duration_cast>(t2 - t).count(); + /*if ( entity->sprite == 1426 || entity->sprite == 1430 ) + { + if ( 1000 * std::chrono::duration_cast>(t2 - t).count() > 1.0 ) + { + printlog("gnome time: uid %d | time %.2f", entity->getUID(), 1000 * std::chrono::duration_cast>(t2 - t).count()); + } + }*/ } } @@ -2533,9 +2540,9 @@ void gameLogic(void) } if ( debugMonsterTimer ) { - //printlog("accum: %f", accum); if ( accum > 5.0 ) { + printlog("accum: %f, tick: %d", accum, ticks); for ( auto& pair : entityAccum ) { printlog("entity: %d, accum: %.2f", pair.first, pair.second); From 741a7d5aad8619a09c3a4ea897574002a528d389 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 20:26:44 +1100 Subject: [PATCH 189/244] * /player_can_move_in_gui variable to prevent movement in inventory --- src/actplayer.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index 73f59ea91..cad9ec716 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -74,6 +74,18 @@ static ConsoleVariable cvar_calloutStartZLimit("/callout_start_z_limit", #define GHOSTCAM_WEAVE my->fskill[10] #define GHOSTCAM_HOVER my->fskill[11] +static ConsoleVariable cvar_player_can_move_in_gui("/player_can_move_in_gui", true); +bool playerAllowedMovement(const int playernum) +{ + if ( !*cvar_player_can_move_in_gui ) + { + if ( !players[playernum]->shootmode ) + { + return false; + } + } + return true; +} void Player::Ghost_t::handleGhostCameraBobbing(bool useRefreshRateDelta) { if ( !my ) @@ -98,7 +110,7 @@ void Player::Ghost_t::handleGhostCameraBobbing(bool useRefreshRateDelta) { bool reset = false; - if ( !gamePaused + if ( !gamePaused && playerAllowedMovement(playernum) && ((!inputs.hasController(playernum) && ((input.binary("Move Forward") || input.binary("Move Backward")) || (input.binary("Move Left") - input.binary("Move Right")))) @@ -165,7 +177,7 @@ void Player::Ghost_t::handleGhostMovement(const bool useRefreshRateDelta) } // calculate movement forces - bool allowMovement = isControllable(); + bool allowMovement = isControllable() && playerAllowedMovement(player.playernum); static ConsoleVariable cvar_ghostSpeed("/ghost_speed", 1.5); static ConsoleVariable cvar_ghostDrag("/ghost_drag", 0.95); @@ -2870,7 +2882,7 @@ void Player::PlayerMovement_t::handlePlayerCameraBobbing(bool useRefreshRateDelt PLAYER_BOBMOVE -= .03 * refreshRateDelta; } } - else if ( !gamePaused + else if ( !gamePaused && playerAllowedMovement(player.playernum) && ((!inputs.hasController(PLAYER_NUM) && ((input.binary("Move Forward") || input.binary("Move Backward")) || (input.binary("Move Left") - input.binary("Move Right")))) @@ -3217,7 +3229,7 @@ void Player::PlayerMovement_t::handlePlayerMovement(bool useRefreshRateDelta) // calculate movement forces - bool allowMovement = my->isMobile(); + bool allowMovement = my->isMobile() && playerAllowedMovement(player.playernum); bool pacified = stats[PLAYER_NUM]->EFFECTS[EFF_PACIFY]; bool rooted = stats[PLAYER_NUM]->EFFECTS[EFF_ROOTED]; if ( rooted ) From 0227cdc47f710f21e0ead30985ba9f6015f01984 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 20:28:13 +1100 Subject: [PATCH 190/244] * gnome thief compendium stuff - increase lvl to 10, add custom names --- src/entity.cpp | 2 ++ src/mod_tools.cpp | 6 +++++- src/mod_tools.hpp | 19 +++++++++++++++++++ src/monster_gnome.cpp | 13 +++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 608db426b..cea9da337 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -20724,6 +20724,8 @@ bool monsterNameIsGeneric(Stat& monsterStats) || strstr(monsterStats.name, "Training") || strstr(monsterStats.name, "Mysterious") || strstr(monsterStats.name, "shaman") + || !strcmp(monsterStats.name, Language::get(6302)) // gnome thief + || !strcmp(monsterStats.name, Language::get(6303)) // gnome thief leader || strstr(monsterStats.name, getMonsterLocalizedName(SLIME).c_str()) ) { // If true, pretend the monster doesn't have a name and use the generic message "You hit the lesser skeleton!" diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index ad224bd35..f9e57d601 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -14750,7 +14750,11 @@ void Compendium_t::Events_t::eventUpdateMonster(int playernum, const EventTags t { if ( auto stats = entity->getStats() ) { - if ( stats->type == SHOPKEEPER && stats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 ) + if ( stats->type == GNOME && stats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) + { + monsterStrLookup = "gnome thief"; + } + else if ( stats->type == SHOPKEEPER && stats->MISC_FLAGS[STAT_FLAG_MYSTERIOUS_SHOPKEEP] > 0 ) { monsterStrLookup = "mysterious shop"; } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 24f4f4cb7..ee4a9caa5 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3920,6 +3920,25 @@ struct Compendium_t CPDM_SPELL_TARGETS, CPDM_GYROBOT_FLIPS, CPDM_KILL_XP, + CPDM_CONTAINER_BROKEN, + CPDM_CONTAINER_ITEMS, + CPDM_CONTAINER_GOLD, + CPDM_CONTAINER_MONSTERS, + CPDM_DAED_USES, + CPDM_DAED_EXIT_REVEALS, + CPDM_DAED_SPEED_BUFFS, + CPDM_DAED_KILLED_MINO, + CPDM_BELL_RUNG_TIMES, + CPDM_BELL_LOOT_ITEMS, + CPDM_BELL_BROKEN, + CPDM_BELL_BUFFS_AGILITY, + CPDM_BELL_BUFFS_STAMINA, + CPDM_BELL_BUFFS_STRENGTH, + CPDM_BELL_BUFFS_MENTALITY, + CPDM_BELL_BUFFS_HEALS, + CPDM_BELL_LOOT_GOLD, + CPDM_BELL_LOOT_BATS, + CPDM_BELL_CLAPPER_BROKEN, CPDM_EVENT_TAGS_MAX }; diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index c9298a882..3ac255459 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -124,6 +124,10 @@ void initGnome(Entity* my, Stat* myStats) } ++i; } + if ( !strcmp(myStats->name, "") ) + { + strcpy(myStats->name, Language::get(6303)); + } } else { @@ -142,6 +146,14 @@ void initGnome(Entity* my, Stat* myStats) } } + if ( myStats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) + { + if ( !strcmp(myStats->name, "") ) + { + strcpy(myStats->name, Language::get(6302)); + } + } + if ( myStats->getAttribute("gnome_type").find("gnome2F") != std::string::npos ) { myStats->sex = sex_t::FEMALE; @@ -172,6 +184,7 @@ void initGnome(Entity* my, Stat* myStats) myStats->CON = 8; myStats->PER = 5; myStats->STR = 7; + myStats->LVL = 10; myStats->DEX = 5; if ( rng.rand() % 2 ) { From 99b4bcb8ead73c4742f10e3a514bda37df31b629 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 20:31:15 +1100 Subject: [PATCH 191/244] * daedalus/bell/container compendium --- lang/compendium_lang/contents_monsters.json | 4 +- lang/compendium_lang/contents_world.json | 8 +- lang/en.txt | 11 ++- src/actgeneral.cpp | 99 +++++++++++++++++---- src/actmagictrap.cpp | 2 + src/interface/drawminimap.cpp | 18 +++- 6 files changed, 122 insertions(+), 20 deletions(-) diff --git a/lang/compendium_lang/contents_monsters.json b/lang/compendium_lang/contents_monsters.json index 094f9bac1..68d47edb3 100644 --- a/lang/compendium_lang/contents_monsters.json +++ b/lang/compendium_lang/contents_monsters.json @@ -3,9 +3,10 @@ "contents": [ {" HUMANOIDS": "-"}, {"HUMANS": "human"}, - {"GNOME": "gnome"}, {"GOBLIN": "goblin"}, {"THE POTATO KING": "potato king"}, + {"GNOME": "gnome"}, + {"GNOME THIEF": "gnome thief"}, {"SHOPKEEPER": "shopkeeper"}, {"MYSTERIOUS MERCHANT": "mysterious shop"}, {" BEASTS": "-"}, @@ -89,6 +90,7 @@ {"GHOST": "ghost"}, {"GHOULS": "ghoul"}, {"GNOME": "gnome"}, + {"GNOME THIEF": "gnome thief"}, {"GOATMAN": "goatman"}, {"GOBLIN": "goblin"}, {"HUMANS": "human"}, diff --git a/lang/compendium_lang/contents_world.json b/lang/compendium_lang/contents_world.json index 25d031842..66cf4c1f9 100644 --- a/lang/compendium_lang/contents_world.json +++ b/lang/compendium_lang/contents_world.json @@ -8,6 +8,7 @@ {"MURKY WATER": "murky water"}, {"LAVA": "lava"}, {"PITS": "pits"}, + {"CONTAINERS": "containers"}, {"BREAKABLE BARRIERS": "breakable barriers"}, {" LOCATIONS": "-"}, {"MINEHEAD": "minehead"}, @@ -32,7 +33,9 @@ {"FOUNTAIN": "fountain"}, {"CHEST": "chest"}, {"GRAVESTONE": "gravestone"}, - {"OBELISK": "obelisk"}, + {"DAEDALUS SHRINE": "daedalus"}, + {"OBELISK": "obelisk"}, + {"BELL": "bell"}, {" TRAPS": "-"}, {"BOULDER TRAP": "boulder trap"}, {"ARROW TRAP": "arrow trap"}, @@ -61,6 +64,7 @@ "contents_alphabetical": [ {"ARCANE CITADEL": "arcane citadel"}, {"ARROW TRAP": "arrow trap"}, + {"BELL": "bell"}, {"BOULDER TRAP": "boulder trap"}, {"BRAM'S CASTLE": "brams castle"}, {"BREAKABLE BARRIERS": "breakable barriers"}, @@ -69,7 +73,9 @@ {"CHEST": "chest"}, {"CITADEL SANCTUM": "citadel sanctum"}, {"COCKATRICE LAIR": "cockatrice lair"}, + {"CONTAINERS": "containers"}, {"CRYSTAL CAVES": "crystal caves"}, + {"DAEDALUS SHRINE": "daedalus"}, {"DOOR": "door"}, {"FOUNTAIN": "fountain"}, {"GATE": "portcullis"}, diff --git a/lang/en.txt b/lang/en.txt index 8b7767519..9c250c7fd 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6524,5 +6524,12 @@ Unlock Achievements to earn more!# 6294 You dodged a flying %s!# 6295 spell# 6296 projectile# - -6300 END# +6297 The rope catches fire!# +6298 The chime refreshes you!# +6299 Lobby: Open# +6300 Lobby: Friends Only# +6301 Lobby: Invite Only# +6302 gnome thief# +6303 thief leader# + +6350 END# diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 616e023ce..7e4d261ac 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -665,6 +665,21 @@ void Entity::colliderOnDestroy() { if ( multiplayer == CLIENT ) { return; } flags[PASSABLE] = true; + + Entity* killer = nullptr; + if ( colliderKillerUid != 0 ) + { + killer = uidToEntity(colliderKillerUid); + if ( killer ) + { + auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; + if ( colliderData.damageCalculationType.find("breakable") != std::string::npos ) + { + Compendium_t::Events_t::eventUpdateWorld(killer->skill[2], Compendium_t::CPDM_CONTAINER_BROKEN, "containers", 1); + } + } + } + if ( colliderHideMonster != 0 ) { int type = colliderHideMonster % 1000; @@ -696,22 +711,23 @@ void Entity::colliderOnDestroy() } } - if ( colliderKillerUid != 0 ) + if ( killer ) { - if ( Entity* killer = uidToEntity(colliderKillerUid) ) + if ( killer->behavior == &actPlayer ) { - if ( killer->behavior == &actPlayer ) + if ( successes >= 1 ) { - if ( successes == 1 ) - { - messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234), - getMonsterLocalizedName((Monster)type).c_str(), Language::get(getColliderLangName())); - } - else if ( successes > 1 ) - { - messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6253), - getMonsterLocalizedPlural((Monster)type).c_str(), Language::get(getColliderLangName())); - } + Compendium_t::Events_t::eventUpdateWorld(killer->skill[2], Compendium_t::CPDM_CONTAINER_MONSTERS, "containers", successes); + } + if ( successes == 1 ) + { + messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6234), + getMonsterLocalizedName((Monster)type).c_str(), Language::get(getColliderLangName())); + } + else if ( successes > 1 ) + { + messagePlayer(killer->skill[2], MESSAGE_INTERACTION, Language::get(6253), + getMonsterLocalizedPlural((Monster)type).c_str(), Language::get(getColliderLangName())); } } } @@ -749,6 +765,8 @@ void Entity::colliderOnDestroy() } } + int totalGold = entity->goldAmount; + // find other matching gold piles auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(entity, 2); for ( std::vector::iterator it = entLists.begin(); it != entLists.end(); ++it ) @@ -784,9 +802,18 @@ void Entity::colliderOnDestroy() } } } + + totalGold += ent->goldAmount; } } } + if ( totalGold > 0 && killer ) + { + if ( killer->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(killer->skill[2], Compendium_t::CPDM_CONTAINER_GOLD, "containers", totalGold); + } + } entity->goldInContainer = 0; } else if ( entity->behavior == &actItem ) @@ -816,6 +843,14 @@ void Entity::colliderOnDestroy() } } } + + if ( killer ) + { + if ( killer->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(killer->skill[2], Compendium_t::CPDM_CONTAINER_ITEMS, "containers", 1); + } + } } } } @@ -1023,6 +1058,10 @@ void actColliderDecoration(Entity* my) if ( found->behavior == &actPlayer ) { + if ( successes >= 1 ) + { + Compendium_t::Events_t::eventUpdateWorld(found->skill[2], Compendium_t::CPDM_CONTAINER_MONSTERS, "containers", successes); + } if ( successes == 1 ) { messagePlayer(found->skill[2], MESSAGE_INTERACTION, Language::get(6234), @@ -3973,6 +4012,7 @@ void actBell(Entity* my) } my->z = 0; + static ConsoleVariable cvar_bell_crash("/bell_crash", false); #ifndef NDEBUG if ( keystatus[SDLK_KP_5] && enableDebugKeys ) { @@ -4018,7 +4058,7 @@ void actBell(Entity* my) BELL_ACTIVE_TIMER = pullTimerStart; // active pull timer } - if ( keystatus[SDLK_g] && enableDebugKeys ) + if ( keystatus[SDLK_g] && enableDebugKeys && *cvar_bell_crash ) { keystatus[SDLK_g] = 0; BELL_CURRENT_EVENT = (BellEvents)(BELL_CURRENT_EVENT + 1); @@ -4029,7 +4069,6 @@ void actBell(Entity* my) messagePlayer(0, MESSAGE_DEBUG, "Bell event: %d", BELL_CURRENT_EVENT); } #endif - static ConsoleVariable cvar_bell_crash("/bell_crash", false); if ( multiplayer == CLIENT ) { @@ -4077,6 +4116,7 @@ void actBell(Entity* my) { touched = Player::getPlayerInteractEntity(i); BELL_LAST_TOUCHED_PLAYER = i; + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_BELL_RUNG_TIMES, "bell", 1); break; } } @@ -4185,6 +4225,17 @@ void actBell(Entity* my) my->flags[INVISIBLE] = true; serverUpdateEntitySkill(my, INVISIBLE); serverUpdateEntitySkill(my, BURNING); + + if ( BELL_PULLED_TO_BREAK != 0 ) + { + if ( Entity* puller = uidToEntity(BELL_PULLED_TO_BREAK) ) + { + if ( puller->behavior == &actPlayer ) + { + Compendium_t::Events_t::eventUpdateWorld(puller->skill[2], Compendium_t::CPDM_BELL_BROKEN, "bell", 1); + } + } + } } } } @@ -4740,26 +4791,32 @@ void actBell(Entity* my) { const char* lang = nullptr; int statusEffect = 0; + Compendium_t::EventTags tag = Compendium_t::EventTags::CPDM_EVENT_TAGS_MAX; switch ( BELL_BUFF_TYPE % BellBuffs::BUFF_ENUM_END ) { case BUFF_STR: lang = Language::get(6281); statusEffect = EFF_POTION_STR; + tag = Compendium_t::EventTags::CPDM_BELL_BUFFS_STRENGTH; break; case BUFF_CON: lang = Language::get(6282); statusEffect = EFF_CON_BONUS; + tag = Compendium_t::EventTags::CPDM_BELL_BUFFS_STAMINA; break; case BUFF_DEX: lang = Language::get(6283); statusEffect = EFF_AGILITY; + tag = Compendium_t::EventTags::CPDM_BELL_BUFFS_AGILITY; break; case BUFF_PWR: lang = Language::get(6284); statusEffect = EFF_PWR; + tag = Compendium_t::EventTags::CPDM_BELL_BUFFS_MENTALITY; break; case BUFF_HEAL: statusEffect = SPELL_HEALING; + tag = Compendium_t::EventTags::CPDM_BELL_BUFFS_HEALS; break; default: break; @@ -4790,6 +4847,7 @@ void actBell(Entity* my) if ( entity->behavior == &actPlayer ) { messagePlayerColor(entity->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(6298)); + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], tag, "bell", 1); } } else if ( entity->setEffect(statusEffect, true, std::max(stats->EFFECTS_TIMERS[statusEffect], duration), false) ) @@ -4799,6 +4857,7 @@ void actBell(Entity* my) if ( entity->behavior == &actPlayer ) { messagePlayerColor(entity->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(6280), lang); + Compendium_t::Events_t::eventUpdateWorld(entity->skill[2], tag, "bell", 1); } } } @@ -4814,6 +4873,7 @@ void actBell(Entity* my) if ( bell && BELL_BULB_BROKEN == 0 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); bellBreakBulb(my, false); } } @@ -4826,6 +4886,7 @@ void actBell(Entity* my) playSoundEntity(my, 76, 64); messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6279)); bellAttractMonsters(my); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_CLAPPER_BROKEN, "bell", 1); } } else if ( BELL_CURRENT_EVENT == BELL_MONSTER ) @@ -4850,6 +4911,10 @@ void actBell(Entity* my) if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) { + if ( successes > 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_LOOT_BATS, "bell", successes); + } if ( successes == 1 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6234), @@ -4868,6 +4933,7 @@ void actBell(Entity* my) if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); } bellBreakBulb(my, false); } @@ -4910,6 +4976,7 @@ void actBell(Entity* my) if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6272)); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_LOOT_ITEMS, "bell", 1); } } else if ( entity->behavior == &actGoldBag ) @@ -4941,6 +5008,7 @@ void actBell(Entity* my) if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6272)); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_LOOT_GOLD, "bell", entity->goldAmount); } } @@ -4950,6 +5018,7 @@ void actBell(Entity* my) if ( BELL_LAST_TOUCHED_PLAYER >= 0 ) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); + Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); } bellBreakBulb(my, false); } diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp index 323aa0ef0..ca0a695ab 100644 --- a/src/actmagictrap.cpp +++ b/src/actmagictrap.cpp @@ -751,6 +751,7 @@ void Entity::actDaedalusShrine() if ( touched->behavior == &actPlayer ) { messagePlayerColor(touched->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(768)); + Compendium_t::Events_t::eventUpdateWorld(touched->skill[2], Compendium_t::CPDM_DAED_SPEED_BUFFS, "daedalus", 1); } } } @@ -857,6 +858,7 @@ void Entity::actDaedalusShrine() } daedalusShrineInteract(this, Player::getPlayerInteractEntity(i)); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_DAED_USES, "daedalus", 1); shrineActivateDelay = TICKS_PER_SECOND * 5; serverUpdateEntitySkill(this, 7); break; diff --git a/src/interface/drawminimap.cpp b/src/interface/drawminimap.cpp index d59acbaa2..39d22b217 100644 --- a/src/interface/drawminimap.cpp +++ b/src/interface/drawminimap.cpp @@ -222,6 +222,7 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) const int xmax = map.width - xmin; const int ymin = ((int)map.height - mapGCD) / 2; const int ymax = map.height - ymin; + bool checkedDaedalusEvent = false; for ( int x = xmin; x < xmax; ++x ) { for ( int y = ymin; y < ymax; ++y ) { Uint32 color = 0; @@ -253,6 +254,21 @@ void drawMinimap(const int player, SDL_Rect rect, bool drawingSharedMap) if ( !minimap[y][x] ) { minimap[y][x] = 3; + if ( !checkedDaedalusEvent ) + { + for ( auto ent : entityPointsOfInterest ) + { + if ( ent->behavior == &actLadder || ent->behavior == &actPortal ) + { + if ( static_cast(ent->x / 16) == x && static_cast(ent->y / 16) == y ) + { + Compendium_t::Events_t::eventUpdateWorld(clientnum, Compendium_t::CPDM_DAED_EXIT_REVEALS, "daedalus", 1); + checkedDaedalusEvent = true; + break; + } + } + } + } } } } @@ -1330,7 +1346,7 @@ void shrineDaedalusRevealMap(Entity& my) { int x = coord % 10000; int y = coord / 10000; - if ( x < map.width && y < map.height ) + if ( x >= 0 && y >= 0 && x < map.width && y < map.height ) { if ( visited.find(x + 10000 * y) == visited.end() ) { From 15d1ba64005c718b55ff7958692d58cca56d571e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 20:31:26 +1100 Subject: [PATCH 192/244] * container compendium entries hide if not unlocked area --- src/ui/MainMenu.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index df3197855..3a74a6175 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -32828,10 +32828,65 @@ namespace MainMenu { } auto& entry = CompendiumEntries.worldObjects[name]; - Compendium_t::compendiumEntityCurrent.set( - name, - entry.models.empty() ? "" : entry.models[Compendium_t::compendiumEntityCurrent.modelRNG % entry.models.size()] - ); + if ( name == "containers" && entry.models.size() > 0 ) + { + // hack - only show breakables on levels weve unlocked + std::vector revealed_models = entry.models; + std::vector> areaUnlockNames = { + {"mines", "mines"}, + {"swamps", "swamps"}, + {"labyrinth", "labyrinth"}, + {"ruins", "ruins"}, + {"underworld", "underworld"}, + {"hell", "hell"}, + {"crystal caves", "caves"}, + {"arcane citadel", "citadel"} + }; + for ( auto it = revealed_models.begin(); it != revealed_models.end(); ) + { + bool found = false; + for ( auto pair : areaUnlockNames ) + { + if ( it->find(pair.second) != std::string::npos ) + { + if ( pair.second == "mines" ) + { + found = true; + break; + } + auto find = Compendium_t::CompendiumWorld_t::unlocks.find(pair.first); + if ( find != Compendium_t::CompendiumWorld_t::unlocks.end() ) + { + if ( find->second != Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + found = true; + } + } + break; + } + } + if ( !found ) + { + it = revealed_models.erase(it); + } + else + { + ++it; + } + } + + Compendium_t::compendiumEntityCurrent.set( + name, + revealed_models.empty() ? "" : revealed_models[Compendium_t::compendiumEntityCurrent.modelRNG % revealed_models.size()] + ); + } + else + { + Compendium_t::compendiumEntityCurrent.set( + name, + entry.models.empty() ? "" : entry.models[Compendium_t::compendiumEntityCurrent.modelRNG % entry.models.size()] + ); + } refreshCompendiumCamera(Compendium_t::compendiumEntityCurrent.modelName); From a198e9d63d4f18dd27bc118d15fd2d99a493cd42 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 17 Oct 2024 22:10:00 +1100 Subject: [PATCH 193/244] * enchant armor/weapon scrolls now targetable, add a check to the netcode --- src/interface/interface.cpp | 224 ++++++++++++++++++++++++++++++++++++ src/interface/interface.hpp | 9 +- src/item_usage_funcs.cpp | 172 +++++++++++++-------------- src/net.cpp | 8 +- 4 files changed, 314 insertions(+), 99 deletions(-) diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index 1aea0ec97..e5e0c532f 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -4871,6 +4871,58 @@ bool GenericGUIMenu::isItemIdentifiable(const Item* item) return true; } +bool GenericGUIMenu::isItemEnchantArmorable(const Item* item) +{ + if ( !item ) + { + return false; + } + if ( !item->identified ) + { + return false; + } + + if ( items[item->type].item_slot != NO_EQUIP ) + { + if ( items[item->type].item_slot != EQUIPPABLE_IN_SLOT_AMULET + && items[item->type].item_slot != EQUIPPABLE_IN_SLOT_RING + && items[item->type].item_slot != EQUIPPABLE_IN_SLOT_WEAPON ) + { + return true; + } + } + + return false; +} + +bool GenericGUIMenu::isItemEnchantWeaponable(const Item* item) +{ + if ( !item ) + { + return false; + } + if ( !item->identified ) + { + return false; + } + + if ( items[item->type].item_slot == EQUIPPABLE_IN_SLOT_WEAPON ) + { + return true; + /*if ( itemCategory(item) == WEAPON || itemCategory(item) == THROWN ) + { + }*/ + } + else if ( item->type == BRASS_KNUCKLES + || item->type == IRON_KNUCKLES + || item->type == SPIKED_GAUNTLETS ) + { + return true; + } + + return false; +} + bool GenericGUIMenu::isItemRepairable(const Item* item, int repairScroll) { if ( !item ) @@ -6012,6 +6064,14 @@ bool GenericGUIMenu::shouldDisplayItemInGUI(Item* item) { return isItemIdentifiable(item); } + else if ( itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_ARMOR ) + { + return isItemEnchantArmorable(item); + } + else if ( itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_WEAPON ) + { + return isItemEnchantWeaponable(item); + } else if ( itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_REPAIR || itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_CHARGING ) { @@ -6174,6 +6234,95 @@ void GenericGUIMenu::identifyItem(Item* item) closeGUI(); } +void GenericGUIMenu::enchantItem(Item* item) +{ + if ( !item ) + { + return; + } + if ( !shouldDisplayItemInGUI(item) ) + { + messagePlayer(gui_player, MESSAGE_MISC, Language::get(6307), item->getName()); + return; + } + + if ( itemEffectItemBeatitude >= 0 ) + { + if ( gui_player >= 0 && gui_player < MAXPLAYERS && players[gui_player]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_BLESSED_TOTAL, item->type, 1); + } + item->beatitude += 1 + itemEffectItemBeatitude; + if ( itemEffectItemBeatitude == 0 ) + { + messagePlayer(gui_player, MESSAGE_HINT, Language::get(859), item->getName()); // glows blue + } + else if ( itemEffectItemBeatitude >= 0 ) + { + messagePlayer(gui_player, MESSAGE_HINT, Language::get(860), item->getName()); // glows violently blue + } + + if ( multiplayer == CLIENT ) + { + Item** slot = itemSlot(stats[gui_player], item); + int armornum = -1; + if ( slot ) + { + if ( slot == &stats[gui_player]->weapon ) + { + armornum = 0; + } + else if ( slot == &stats[gui_player]->helmet ) + { + armornum = 1; + } + else if ( slot == &stats[gui_player]->breastplate ) + { + armornum = 2; + } + else if ( slot == &stats[gui_player]->gloves ) + { + armornum = 3; + } + else if ( slot == &stats[gui_player]->shoes ) + { + armornum = 4; + } + else if ( slot == &stats[gui_player]->shield ) + { + armornum = 5; + } + else if ( slot == &stats[gui_player]->cloak ) + { + armornum = 6; + } + else if ( slot == &stats[gui_player]->mask ) + { + armornum = 7; + } + else if ( slot == &stats[gui_player]->mask ) + { + armornum = 7; + } + } + if ( armornum >= 0 ) + { + strcpy((char*)net_packet->data, "BEAT"); + net_packet->data[4] = gui_player; + net_packet->data[5] = armornum; + net_packet->data[6] = item->beatitude + 100; + SDLNet_Write16((Sint16)item->type, &net_packet->data[7]); + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 9; + sendPacketSafe(net_sock, -1, net_packet, 0); + //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); + } + } + } + closeGUI(); +} + void GenericGUIMenu::repairItem(Item* item) { if ( !item ) @@ -6508,6 +6657,14 @@ void GenericGUIMenu::openGUI(int type, Item* effectItem, int effectBeatitude, in { itemfxGUI.currentMode = ItemEffectGUI_t::ITEMFX_MODE_SCROLL_IDENTIFY; } + else if ( itemEffectItemType == SCROLL_ENCHANTWEAPON ) + { + itemfxGUI.currentMode = ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_WEAPON; + } + else if ( itemEffectItemType == SCROLL_ENCHANTARMOR ) + { + itemfxGUI.currentMode = ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_ARMOR; + } else if ( usingSpellID == SPELL_IDENTIFY ) { itemfxGUI.currentMode = ItemEffectGUI_t::ITEMFX_MODE_SPELL_IDENTIFY; @@ -6682,6 +6839,17 @@ bool GenericGUIMenu::executeOnItemClick(Item* item) identifyItem(item); return true; } + else if ( itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_WEAPON + || itemfxGUI.currentMode == ItemEffectGUI_t::ITEMFX_MODE_SCROLL_ENCHANT_ARMOR ) + { + if ( itemEffectScrollItem && itemCategory(itemEffectScrollItem) == SCROLL ) + { + messagePlayer(gui_player, MESSAGE_INVENTORY, Language::get(848)); // as you read the scroll it disappears... + consumeItem(itemEffectScrollItem, gui_player); + } + enchantItem(item); + return true; + } return false; } else if ( guiType == GUI_TYPE_ALCHEMY ) @@ -20672,6 +20840,50 @@ GenericGUIMenu::ItemEffectGUI_t::ItemEffectActions_t GenericGUIMenu::ItemEffectG result = ITEMFX_ACTION_INVALID_ITEM; } } + else if ( currentMode == ITEMFX_MODE_SCROLL_ENCHANT_ARMOR ) + { + if ( itemCategory(item) == SPELL_CAT ) + { + result = ITEMFX_ACTION_INVALID_ITEM; + } + else if ( !item->identified ) + { + result = ITEMFX_ACTION_NOT_IDENTIFIED_YET; + } + else + { + if ( parentGUI.isItemEnchantArmorable(item) ) + { + result = ITEMFX_ACTION_OK; + } + else + { + result = ITEMFX_ACTION_INVALID_ITEM; + } + } + } + else if ( currentMode == ITEMFX_MODE_SCROLL_ENCHANT_WEAPON ) + { + if ( itemCategory(item) == SPELL_CAT ) + { + result = ITEMFX_ACTION_INVALID_ITEM; + } + else if ( !item->identified ) + { + result = ITEMFX_ACTION_NOT_IDENTIFIED_YET; + } + else + { + if ( parentGUI.isItemEnchantWeaponable(item) ) + { + result = ITEMFX_ACTION_OK; + } + else + { + result = ITEMFX_ACTION_INVALID_ITEM; + } + } + } else if ( currentMode == ITEMFX_MODE_SCROLL_REPAIR ) { if ( itemCategory(item) == SPELL_CAT ) @@ -21588,6 +21800,12 @@ void GenericGUIMenu::ItemEffectGUI_t::updateItemEffectMenu() case ITEMFX_MODE_SCROLL_IDENTIFY: actionPromptTxt->setText(Language::get(4208)); break; + case ITEMFX_MODE_SCROLL_ENCHANT_ARMOR: + actionPromptTxt->setText(Language::get(6305)); + break; + case ITEMFX_MODE_SCROLL_ENCHANT_WEAPON: + actionPromptTxt->setText(Language::get(6304)); + break; case ITEMFX_MODE_SPELL_IDENTIFY: actionPromptTxt->setText(Language::get(4208)); break; @@ -21734,6 +21952,12 @@ void GenericGUIMenu::ItemEffectGUI_t::updateItemEffectMenu() case ITEMFX_MODE_SCROLL_IDENTIFY: actionPromptUnselectedTxt->setText(Language::get(4209)); break; + case ITEMFX_MODE_SCROLL_ENCHANT_ARMOR: + actionPromptUnselectedTxt->setText(Language::get(6306)); + break; + case ITEMFX_MODE_SCROLL_ENCHANT_WEAPON: + actionPromptUnselectedTxt->setText(Language::get(6306)); + break; case ITEMFX_MODE_SCROLL_REMOVECURSE: actionPromptUnselectedTxt->setText(Language::get(4205)); break; diff --git a/src/interface/interface.hpp b/src/interface/interface.hpp index b892e48fa..1852c1f85 100644 --- a/src/interface/interface.hpp +++ b/src/interface/interface.hpp @@ -425,6 +425,11 @@ class GenericGUIMenu bool isItemIdentifiable(const Item* item); void identifyItem(Item* item); + //enchant + bool isItemEnchantWeaponable(const Item* item); + bool isItemEnchantArmorable(const Item* item); + void enchantItem(Item* item); + //alchemy menu funcs bool isItemMixable(const Item* item); void alchemyCombinePotions(); @@ -643,7 +648,9 @@ class GenericGUIMenu ITEMFX_MODE_SCROLL_IDENTIFY, ITEMFX_MODE_SCROLL_REMOVECURSE, ITEMFX_MODE_SPELL_IDENTIFY, - ITEMFX_MODE_SPELL_REMOVECURSE + ITEMFX_MODE_SPELL_REMOVECURSE, + ITEMFX_MODE_SCROLL_ENCHANT_WEAPON, + ITEMFX_MODE_SCROLL_ENCHANT_ARMOR }; void openItemEffectMenu(ItemEffectModes mode); ItemEffectModes currentMode = ITEMFX_MODE_NONE; diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 5d990d201..f4194aece 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -345,9 +345,10 @@ bool item_PotionWater(Item*& item, Entity* entity, Entity* usedBy) net_packet->data[4] = player; net_packet->data[5] = armornum; net_packet->data[6] = toCurse->beatitude + 100; + SDLNet_Write16((Sint16)toCurse->type, &net_packet->data[7]); net_packet->address.host = net_server.host; net_packet->address.port = net_server.port; - net_packet->len = 7; + net_packet->len = 9; sendPacketSafe(net_sock, -1, net_packet, 0); //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); } @@ -2668,14 +2669,14 @@ void item_ScrollEnchantWeapon(Item* item, int player) conductIlliterate = false; - messagePlayer(player, MESSAGE_INVENTORY, Language::get(848)); - - Item** toEnchant = nullptr; - bool hasMeleeGloves = false; - if ( stats[player]->gloves ) + if ( item->beatitude < 0 ) { - switch ( stats[player]->gloves->type ) + Item** toEnchant = nullptr; + bool hasMeleeGloves = false; + if ( stats[player]->gloves ) { + switch ( stats[player]->gloves->type ) + { case BRASS_KNUCKLES: case IRON_KNUCKLES: case SPIKED_GAUNTLETS: @@ -2683,25 +2684,23 @@ void item_ScrollEnchantWeapon(Item* item, int player) break; default: break; + } } - } - if ( stats[player]->weapon ) - { - toEnchant = &stats[player]->weapon; - } - else if ( hasMeleeGloves ) - { - toEnchant = &stats[player]->gloves; - } - - if ( toEnchant == nullptr) - { - messagePlayer(player, MESSAGE_HINT, Language::get(853)); - } - else - { - if (item->beatitude < 0) + if ( stats[player]->weapon ) + { + toEnchant = &stats[player]->weapon; + } + else if ( hasMeleeGloves ) + { + toEnchant = &stats[player]->gloves; + } + messagePlayer(player, MESSAGE_INVENTORY, Language::get(848)); + if ( toEnchant == nullptr ) + { + messagePlayer(player, MESSAGE_HINT, Language::get(853)); + } + else { if ( toEnchant == &stats[player]->gloves ) { @@ -2724,51 +2723,39 @@ void item_ScrollEnchantWeapon(Item* item, int player) { (*toEnchant)->beatitude -= 1; } - } - else - { - if (item->beatitude == 0) - { - if ( toEnchant == &stats[player]->gloves ) - { - messagePlayer(player, MESSAGE_HINT, Language::get(859), (*toEnchant)->getName()); - } - else - { - messagePlayer(player, MESSAGE_HINT, Language::get(855)); - } - } - else + + if ( multiplayer == CLIENT ) { + strcpy((char*)net_packet->data, "BEAT"); + net_packet->data[4] = player; if ( toEnchant == &stats[player]->gloves ) { - messagePlayer(player, MESSAGE_HINT, Language::get(860), (*toEnchant)->getName()); + net_packet->data[5] = 3; // glove index } else { - messagePlayer(player, MESSAGE_HINT, Language::get(856)); + net_packet->data[5] = 0; // weapon index } + net_packet->data[6] = (*toEnchant)->beatitude + 100; + SDLNet_Write16((Sint16)(*toEnchant)->type, &net_packet->data[7]); + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 9; + sendPacketSafe(net_sock, -1, net_packet, 0); + //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); } - (*toEnchant)->beatitude += 1 + item->beatitude; - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BLESSED_TOTAL, (*toEnchant)->type, item->beatitude); - } - - if ( multiplayer == CLIENT ) - { - strcpy((char*)net_packet->data, "BEAT"); - net_packet->data[4] = player; - net_packet->data[5] = 0; // weapon index - net_packet->data[6] = (*toEnchant)->beatitude + 100; - net_packet->address.host = net_server.host; - net_packet->address.port = net_server.port; - net_packet->len = 7; - sendPacketSafe(net_sock, -1, net_packet, 0); - //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); } + onScrollUseAppraisalIncrease(item, player); + item->identified = true; + consumeItem(item, player); + } + else + { + // Bless an item + onScrollUseAppraisalIncrease(item, player); + item->identified = true; + GenericGUI[player].openGUI(GUI_TYPE_ITEMFX, item, item->beatitude, item->type, SPELL_NONE); } - onScrollUseAppraisalIncrease(item, player); - item->identified = true; - consumeItem(item, player); } void item_ScrollEnchantArmor(Item* item, int player) @@ -2799,8 +2786,6 @@ void item_ScrollEnchantArmor(Item* item, int player) conductIlliterate = false; - messagePlayer(player, MESSAGE_INVENTORY, Language::get(848)); - // choose a random piece of worn equipment to curse! int tryIndex = 1 + local_rng.rand() % 7; int startIndex = tryIndex; @@ -2887,13 +2872,14 @@ void item_ScrollEnchantArmor(Item* item, int player) } } - if (armor == nullptr) - { - messagePlayer(player, MESSAGE_HINT, Language::get(857)); - } - else if ( armor != nullptr ) + if (item->beatitude < 0) { - if (item->beatitude < 0) + messagePlayer(player, MESSAGE_INVENTORY, Language::get(848)); + if (armor == nullptr) + { + messagePlayer(player, MESSAGE_HINT, Language::get(857)); + } + else if ( armor != nullptr ) { messagePlayer(player, MESSAGE_HINT, Language::get(858), armor->getName()); @@ -2905,38 +2891,33 @@ void item_ScrollEnchantArmor(Item* item, int player) { armor->beatitude -= 1; } - } - else - { - if (item->beatitude == 0) - { - messagePlayer(player, MESSAGE_HINT, Language::get(859), armor->getName()); - } - else + + if ( multiplayer == CLIENT ) { - messagePlayer(player, MESSAGE_HINT, Language::get(860), armor->getName()); + strcpy((char*)net_packet->data, "BEAT"); + net_packet->data[4] = player; + net_packet->data[5] = armornum; + net_packet->data[6] = armor->beatitude + 100; + SDLNet_Write16((Sint16)armor->type, &net_packet->data[7]); + net_packet->address.host = net_server.host; + net_packet->address.port = net_server.port; + net_packet->len = 9; + sendPacketSafe(net_sock, -1, net_packet, 0); + //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); } - armor->beatitude += 1 + item->beatitude; - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_BLESSED_TOTAL, armor->type, item->beatitude); } + onScrollUseAppraisalIncrease(item, player); + item->identified = true; + consumeItem(item, player); - if ( multiplayer == CLIENT ) - { - strcpy((char*)net_packet->data, "BEAT"); - net_packet->data[4] = player; - net_packet->data[5] = armornum; - net_packet->data[6] = armor->beatitude + 100; - net_packet->address.host = net_server.host; - net_packet->address.port = net_server.port; - net_packet->len = 7; - sendPacketSafe(net_sock, -1, net_packet, 0); - //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); - } } - - onScrollUseAppraisalIncrease(item, player); - item->identified = true; - consumeItem(item, player); + else + { + // Bless an item + onScrollUseAppraisalIncrease(item, player); + item->identified = true; + GenericGUI[player].openGUI(GUI_TYPE_ITEMFX, item, item->beatitude, item->type, SPELL_NONE); + } } void item_ScrollRemoveCurse(Item* item, int player) @@ -3090,9 +3071,10 @@ void item_ScrollRemoveCurse(Item* item, int player) net_packet->data[4] = player; net_packet->data[5] = armornum; net_packet->data[6] = toCurse->beatitude + 100; + SDLNet_Write16((Sint16)toCurse->type, &net_packet->data[7]); net_packet->address.host = net_server.host; net_packet->address.port = net_server.port; - net_packet->len = 7; + net_packet->len = 9; sendPacketSafe(net_sock, -1, net_packet, 0); //messagePlayer(player, "sent server: %d, %d, %d", net_packet->data[4], net_packet->data[5], net_packet->data[6]); } diff --git a/src/net.cpp b/src/net.cpp index 09779b131..cdec121fa 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -7001,7 +7001,6 @@ static std::unordered_map serverPacketHandlers = { const int player = std::min(net_packet->data[4], (Uint8)(MAXPLAYERS - 1)); Item* equipment = nullptr; //messagePlayer(0, "client: %d, armornum: %d, status %d", player, net_packet->data[5], net_packet->data[6]); - switch ( net_packet->data[5] ) { case 0: @@ -7037,8 +7036,11 @@ static std::unordered_map serverPacketHandlers = { { return; } - - equipment->beatitude = net_packet->data[6] - 100; // we sent the data beatitude + 100 + int itemType = SDLNet_Read16(&net_packet->data[7]); + if ( (int)equipment->type == itemType ) // sanity check the item type is what was changed + { + equipment->beatitude = net_packet->data[6] - 100; // we sent the data beatitude + 100 + } //messagePlayer(0, "%d", equipment->beatitude); }}, From da299e7b3dad464603dedf30cd880dedcb36a77b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 18 Oct 2024 04:02:40 +1100 Subject: [PATCH 194/244] * spellbook/shield changes * spellbook sustained spells can lvl every 10 mp spent * spellbook if blessed will reduce blessing when degrading * spellbook degrades with grace timer of 5 ish base cycles, minor increase with blessing, increases with casting skill * blocking - improved shield effectiveness +10 * blocking - non-shields no longer improve active block beyond 40 block * blocking - only can gain 1 skill pt per monster * blocking - shields degrade with grace timer of 5+ cycles * blocking - better quality shields have longer grace timer, also if blessed --- src/entity.cpp | 132 ++++++++++++++++++++++++++++++--- src/game.cpp | 4 +- src/magic/act_HandMagic.cpp | 17 +++++ src/magic/castSpell.cpp | 114 ++++++++++++++++++++++++---- src/mod_tools.cpp | 4 +- src/mod_tools.hpp | 2 +- src/net.cpp | 46 ++++++++++++ src/player.cpp | 144 +++++++++++++++++++++++++++++++++++- src/player.hpp | 17 +++++ src/scores.cpp | 19 ++++- src/scores.hpp | 4 + src/stat.cpp | 21 ++++-- src/stat.hpp | 2 +- src/ui/GameUI.cpp | 2 +- 14 files changed, 485 insertions(+), 43 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index cea9da337..93347f742 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1298,12 +1298,18 @@ void Entity::effectTimes() if ( caster ) { //Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false). + int oldMP = caster->getMP(); bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds if ( deducted ) { sustained = true; myStats->EFFECTS[c] = true; myStats->EFFECTS_TIMERS[c] = invisibility_hijacked->channel_duration; + + if ( caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - caster->getMP()); + } } else { @@ -1377,12 +1383,18 @@ void Entity::effectTimes() if ( caster ) { //Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false). + int oldMP = caster->getMP(); bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds if ( deducted ) { sustained = true; myStats->EFFECTS[c] = true; myStats->EFFECTS_TIMERS[c] = levitation_hijacked->channel_duration; + + if ( caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - caster->getMP()); + } } else { @@ -1511,12 +1523,18 @@ void Entity::effectTimes() if ( caster ) { //Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false). + int oldMP = caster->getMP(); bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds if ( deducted ) { sustained = true; myStats->EFFECTS[c] = true; myStats->EFFECTS_TIMERS[c] = reflectMagic_hijacked->channel_duration; + + if ( caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - caster->getMP()); + } } else { @@ -1553,12 +1571,18 @@ void Entity::effectTimes() if ( caster ) { //Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false). + int oldMP = caster->getMP(); bool deducted = caster->safeConsumeMP(1); //Consume 1 mana ever duration / mana seconds if ( deducted ) { sustained = true; myStats->EFFECTS[c] = true; myStats->EFFECTS_TIMERS[c] = amplifyMagic_hijacked->channel_duration; + + if ( caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - caster->getMP()); + } } else { @@ -1594,6 +1618,7 @@ void Entity::effectTimes() if ( caster ) { //Deduct mana from caster. Cancel spell if not enough mana (simply leave sustained at false). + int oldMP = caster->getMP(); bool deducted = caster->safeConsumeMP(1); //Consume 3 mana ever duration / mana seconds if ( deducted ) { @@ -1601,6 +1626,11 @@ void Entity::effectTimes() myStats->EFFECTS[c] = true; myStats->EFFECTS_TIMERS[c] = vampiricAura_hijacked->channel_duration; + if ( caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - caster->getMP()); + } + // monsters have a chance to un-sustain the spell each MP consume. if ( caster->behavior == &actMonster && local_rng.rand() % 20 == 0 ) { @@ -9328,7 +9358,7 @@ void Entity::attack(int pose, int charge, Entity* target) Item* armor = NULL; int armornum = 0; bool isWeakArmor = false; - + bool shieldIncreased = false; if ( damage > 0 || (damage == 0 && !(hitstats->shield && hitstats->defending)) ) { // choose random piece of equipment to target @@ -9374,15 +9404,26 @@ void Entity::attack(int pose, int charge, Entity* target) if ( hit.entity->behavior == &actPlayer && armornum == 4 ) { - armorDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); - if ( itemCategory(hitstats->shield) == ARMOR ) - { - armorDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); // 2x shield bonus offhand - } if ( skillCapstoneUnlocked(hit.entity->skill[2], PRO_SHIELD) ) { armorDegradeChance = 100; // don't break. } + else + { + if ( itemCategory(hitstats->shield) == ARMOR ) + { + armorDegradeChance += 2 * (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); + armorDegradeChance += 10; + if ( !players[hit.entity->skill[2]]->mechanics.itemDegradeRoll(hitstats->shield) ) + { + armorDegradeChance = 100; // don't break. + } + } + else + { + armorDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); + } + } } // crystal golem special attack increase chance for armor to break if hit. (25-33%) @@ -9442,13 +9483,13 @@ void Entity::attack(int pose, int charge, Entity* target) || (hitstats->defending) ) { int roll = 16; - if ( damage == 0 ) + if ( hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) { - roll /= 2; + roll = 32; } - if ( myStats->type == BAT_SMALL ) + if ( damage == 0 ) { - roll = 64; + roll /= 2; } if ( roll > 0 ) { @@ -9463,6 +9504,11 @@ void Entity::attack(int pose, int charge, Entity* target) { increaseSkill = false; } + else if ( hit.entity->behavior == &actPlayer + && !players[hit.entity->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*this) ) + { + increaseSkill = false; + } else if ( hitstats->EFFECTS[EFF_SHAPESHIFT] ) { increaseSkill = false; @@ -9475,6 +9521,11 @@ void Entity::attack(int pose, int charge, Entity* target) if ( increaseSkill ) { hit.entity->increaseSkill(PRO_SHIELD); // increase shield skill + shieldIncreased = true; + if ( hit.entity->behavior == &actPlayer ) + { + players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; + } } } } @@ -9494,10 +9545,17 @@ void Entity::attack(int pose, int charge, Entity* target) } if ( hit.entity->behavior == &actPlayer ) { - shieldDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); if ( itemCategory(hitstats->shield) == ARMOR ) { - shieldDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); // 2x shield bonus offhand + shieldDegradeChance += 2 * (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); // 2x shield bonus offhand + if ( !players[hit.entity->skill[2]]->mechanics.itemDegradeRoll(hitstats->shield) ) + { + shieldDegradeChance = 100; // don't break. + } + } + else + { + shieldDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); } if ( skillCapstoneUnlocked(hit.entity->skill[2], PRO_SHIELD) ) { @@ -10229,7 +10287,30 @@ void Entity::attack(int pose, int charge, Entity* target) { if ( damage > 0 ) { + bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[player]; updateAchievementRhythmOfTheKnight(player, hit.entity, false); + if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[player] ) + { + //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on atk"); + if ( local_rng.rand() % 10 < 6 ) + { + bool increaseSkill = true; + if ( this->behavior == &actPlayer ) + { + if ( !players[this->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*hit.entity) ) + { + increaseSkill = false; + } + players[this->skill[2]]->mechanics.enemyRaisedBlockingAgainst[hit.entity->getUID()]++; + } + if ( increaseSkill ) + { + this->increaseSkill(PRO_SHIELD); + } + } + achievementStatusRhythmOfTheKnight[player] = false; + achievementRhythmOfTheKnightVec[player].clear(); // reset for the next one + } } else { @@ -10881,7 +10962,34 @@ void Entity::attack(int pose, int charge, Entity* target) { if ( hitstats->defending ) { + bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[playerhit]; updateAchievementRhythmOfTheKnight(playerhit, this, true); + if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[playerhit] ) + { + if ( !shieldIncreased ) + { + //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on hit"); + if ( local_rng.rand() % 10 < 6 ) + { + bool skillIncrease = true; + if ( hit.entity->behavior == &actPlayer ) + { + if ( !players[hit.entity->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*this) ) + { + skillIncrease = false; + } + players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; + } + if ( skillIncrease ) + { + hit.entity->increaseSkill(PRO_SHIELD); + shieldIncreased = true; + } + } + } + achievementStatusRhythmOfTheKnight[playerhit] = false; + achievementRhythmOfTheKnightVec[playerhit].clear(); // reset for the next one + } updateAchievementThankTheTank(playerhit, this, false); } else if ( !achievementStatusRhythmOfTheKnight[playerhit] ) diff --git a/src/game.cpp b/src/game.cpp index cf72afced..7d7814266 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1530,10 +1530,10 @@ void gameLogic(void) steamAchievementClient(c, "BARONY_ACH_WELL_PREPARED"); } - if ( achievementStatusRhythmOfTheKnight[c] ) + /*if ( achievementStatusRhythmOfTheKnight[c] ) { steamAchievementClient(c, "BARONY_ACH_RHYTHM_OF_THE_KNIGHT"); - } + }*/ if ( achievementStatusThankTheTank[c] ) { steamAchievementClient(c, "BARONY_ACH_THANK_THE_TANK"); diff --git a/src/magic/act_HandMagic.cpp b/src/magic/act_HandMagic.cpp index 901feac9b..e60fa6c62 100644 --- a/src/magic/act_HandMagic.cpp +++ b/src/magic/act_HandMagic.cpp @@ -22,6 +22,7 @@ #include "../scores.hpp" #include "../ui/MainMenu.hpp" #include "../prng.hpp" +#include "../mod_tools.hpp" //The spellcasting animation stages: #define CIRCLE 0 //One circle @@ -467,7 +468,23 @@ void actLeftHandMagic(Entity* my) if ( multiplayer == SINGLE && cast_animation[HANDMAGIC_PLAYERNUM].consumeMana ) { int HP = stats[HANDMAGIC_PLAYERNUM]->HP; + int MP = stats[HANDMAGIC_PLAYERNUM]->MP; players[HANDMAGIC_PLAYERNUM]->entity->drainMP(1, false); // don't notify otherwise we'll get spammed each 1 mp + + if ( cast_animation[HANDMAGIC_PLAYERNUM].spell ) + { + bool sustainedSpell = false; + auto findSpellDef = ItemTooltips.spellItems.find(cast_animation[HANDMAGIC_PLAYERNUM].spell->ID); + if ( findSpellDef != ItemTooltips.spellItems.end() ) + { + sustainedSpell = (findSpellDef->second.spellType == ItemTooltips_t::SpellItemTypes::SPELL_TYPE_SELF_SUSTAIN); + } + if ( sustainedSpell ) + { + players[HANDMAGIC_PLAYERNUM]->mechanics.sustainedSpellIncrementMP(MP - stats[HANDMAGIC_PLAYERNUM]->MP); + } + } + if ( (HP > stats[HANDMAGIC_PLAYERNUM]->HP) && !overDrawDamageNotify ) { overDrawDamageNotify = true; diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index 32f63db1a..b3d945052 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -398,6 +398,13 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool int spellBookBonusPercent = 0; int spellBookBeatitude = 0; ItemType spellbookType = WOODEN_SHIELD; + bool sustainedSpell = false; + auto findSpellDef = ItemTooltips.spellItems.find(spell->ID); + if ( findSpellDef != ItemTooltips.spellItems.end() ) + { + sustainedSpell = (findSpellDef->second.spellType == ItemTooltips_t::SpellItemTypes::SPELL_TYPE_SELF_SUSTAIN); + } + int oldMP = caster->getMP(); if ( !using_magicstaff && !trap && stat ) { newbie = isSpellcasterBeginner(player, caster); @@ -438,6 +445,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { magiccost = cast_animation[player].mana_left; } + caster->drainMP(magiccost, false); } else // Calculate the cost of the Spell for Multiplayer @@ -515,6 +523,11 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool caster->drainMP(extramagic); } + if ( caster->behavior == &actPlayer && sustainedSpell ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - stat->MP); + } + bool fizzleSpell = false; chance = local_rng.rand() % 10; if ( chance >= spellcastingAbility / 10 ) @@ -585,6 +598,11 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } } + if ( caster->behavior == &actPlayer && sustainedSpell ) + { + players[caster->skill[2]]->mechanics.sustainedSpellIncrementMP(oldMP - stat->MP); + } + //Check if the bugger is levitating. bool levitating = false; if (!trap) @@ -2704,17 +2722,42 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } } - + bool sustainedChance = players[caster->skill[2]]->mechanics.sustainedSpellLevelChance(); if ( spellCastChance > 0 && (local_rng.rand() % spellCastChance == 0) ) { - caster->increaseSkill(PRO_SPELLCASTING); + if ( sustainedSpell && caster->behavior == &actPlayer ) + { + if ( sustainedChance ) + { + players[caster->skill[2]]->mechanics.sustainedSpellMPUsed = 0; + + caster->increaseSkill(PRO_SPELLCASTING); + } + } + else + { + caster->increaseSkill(PRO_SPELLCASTING); + } } bool magicIncreased = false; if ( magicChance > 0 && (local_rng.rand() % magicChance == 0) ) { - caster->increaseSkill(PRO_MAGIC); // otherwise you will basically never be able to learn all the spells in the game... - magicIncreased = true; + if ( sustainedSpell && caster->behavior == &actPlayer ) + { + if ( sustainedChance ) + { + players[caster->skill[2]]->mechanics.sustainedSpellMPUsed = 0; + + caster->increaseSkill(PRO_MAGIC); // otherwise you will basically never be able to learn all the spells in the game... + magicIncreased = true; + } + } + else + { + caster->increaseSkill(PRO_MAGIC); // otherwise you will basically never be able to learn all the spells in the game... + magicIncreased = true; + } } if ( magicIncreased && usingSpellbook && caster->behavior == &actPlayer ) { @@ -2753,26 +2796,67 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { chance = 1; // cursed books always degrade, or blessed books in succubus/incubus } - if ( local_rng.rand() % chance == 0 && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) + else { - Status oldStatus = stat->shield->status; - caster->degradeArmor(*stat, *(stat->shield), 4); - if ( stat->shield->status < oldStatus ) + if ( caster && caster->behavior == &actPlayer ) { - if ( player >= 0 ) + if ( stat->shield && itemCategory(stat->shield) == SPELLBOOK ) { - Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CAST_DEGRADES, stat->shield->type, 1); + if ( !players[caster->skill[2]]->mechanics.itemDegradeRoll(stat->shield) ) + { + chance = 0; + } } } + } + if ( chance > 0 && local_rng.rand() % chance == 0 && stat->shield && itemCategory(stat->shield) == SPELLBOOK ) + { + Status oldStatus = stat->shield->status; + if ( caster && caster->behavior == &actPlayer ) + { + players[caster->skill[2]]->mechanics.onItemDegrade(stat->shield); + } + if ( oldStatus == DECREPIT && stat->shield->beatitude > 0 && caster && caster->behavior == &actPlayer ) + { + --stat->shield->beatitude; + if ( caster->skill[2] >= 0 ) + { + messagePlayer(caster->skill[2], MESSAGE_EQUIPMENT, Language::get(6308), stat->shield->getName()); + } - if ( stat->shield->status == BROKEN && player >= 0 ) + if ( multiplayer == SERVER && caster->skill[2] > 0 ) + { + strcpy((char*)net_packet->data, "BEAT"); + net_packet->data[4] = caster->skill[2]; + net_packet->data[5] = 5; // shield index + net_packet->data[6] = stat->shield->beatitude + 100; + SDLNet_Write16((Sint16)stat->shield->type, &net_packet->data[7]); + net_packet->address.host = net_clients[caster->skill[2] - 1].host; + net_packet->address.port = net_clients[caster->skill[2] - 1].port; + net_packet->len = 9; + sendPacketSafe(net_sock, -1, net_packet, caster->skill[2] - 1); + } + } + else { - if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->appearance == 0 ) + caster->degradeArmor(*stat, *(stat->shield), 4); + if ( stat->shield->status < oldStatus ) { - steamStatisticUpdateClient(player, STEAM_STAT_DYSLEXIA, STEAM_STAT_INT, 1); + if ( player >= 0 ) + { + Compendium_t::Events_t::eventUpdate(player, Compendium_t::CPDM_SPELLBOOK_CAST_DEGRADES, stat->shield->type, 1); + } + } + + if ( stat->shield->status == BROKEN && player >= 0 ) + { + if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->appearance == 0 ) + { + steamStatisticUpdateClient(player, STEAM_STAT_DYSLEXIA, STEAM_STAT_INT, 1); + } + Item* toBreak = stat->shield; + consumeItem(toBreak, player); } - Item* toBreak = stat->shield; - consumeItem(toBreak, player); } } } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index f9e57d601..31f00f002 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -3805,7 +3805,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType if ( tooltipType.find("tooltip_offhand") != std::string::npos ) { snprintf(buf, sizeof(buf), str.c_str(), - stats[player]->getActiveShieldBonus(false, excludeSkill), + stats[player]->getActiveShieldBonus(false, excludeSkill, &item), getItemProficiencyName(PRO_SHIELD).c_str()); } else @@ -3813,7 +3813,7 @@ void ItemTooltips_t::formatItemDetails(const int player, std::string tooltipType snprintf(buf, sizeof(buf), str.c_str(), stats[player]->getPassiveShieldBonus(false, excludeSkill), getItemProficiencyName(PRO_SHIELD).c_str(), - stats[player]->getActiveShieldBonus(false, excludeSkill), + stats[player]->getActiveShieldBonus(false, excludeSkill, &item), getItemProficiencyName(PRO_SHIELD).c_str()); } } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index ee4a9caa5..d9f5cff9a 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -2753,6 +2753,7 @@ class ItemTooltips_t std::string iconLabelPath = ""; }; +public: enum SpellItemTypes : int { SPELL_TYPE_DEFAULT, @@ -2762,7 +2763,6 @@ class ItemTooltips_t SPELL_TYPE_AREA, SPELL_TYPE_SELF_SUSTAIN }; -public: enum SpellTagTypes : int { SPELL_TAG_DAMAGE, diff --git a/src/net.cpp b/src/net.cpp index cdec121fa..2a83a5f26 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3322,6 +3322,52 @@ static std::unordered_map clientPacketHandlers = { } }}, + // update equip beatitude + {'BEAT', []() { + Item* equipment = nullptr; + //messagePlayer(0, "client: %d, armornum: %d, status %d", player, net_packet->data[5], net_packet->data[6]); + switch ( net_packet->data[5] ) + { + case 0: + equipment = stats[clientnum]->weapon; + break; + case 1: + equipment = stats[clientnum]->helmet; + break; + case 2: + equipment = stats[clientnum]->breastplate; + break; + case 3: + equipment = stats[clientnum]->gloves; + break; + case 4: + equipment = stats[clientnum]->shoes; + break; + case 5: + equipment = stats[clientnum]->shield; + break; + case 6: + equipment = stats[clientnum]->cloak; + break; + case 7: + equipment = stats[clientnum]->mask; + break; + default: + equipment = nullptr; + break; + } + + if ( !equipment ) + { + return; + } + int itemType = SDLNet_Read16(&net_packet->data[7]); + if ( (int)equipment->type == itemType ) // sanity check the item type is what was changed + { + equipment->beatitude = net_packet->data[6] - 100; // we sent the data beatitude + 100 + } + }}, + // update armor quality {'ARMR', [](){ Item* item; diff --git a/src/player.cpp b/src/player.cpp index 9007888ff..029cd880e 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -3061,7 +3061,7 @@ void GameController::stopRumble() haptics.hapticEffectId = -1; } -Player::Player(int in_playernum, bool in_local_host) : +Player::Player(int in_playernum, bool in_local_host) : GUI(*this), inventoryUI(*this), hud(*this), @@ -3078,7 +3078,8 @@ Player::Player(int in_playernum, bool in_local_host) : paperDoll(*this), minimap(*this), shopGUI(*this), - compendiumProgress(*this) + compendiumProgress(*this), + mechanics(*this) { local_host = false; playernum = in_playernum; @@ -3104,6 +3105,8 @@ void Player::init() // for use on new/restart game, UI related minotaurWarning[playernum].deinit(); levelUpAnimation[playernum].lvlUps.clear(); skillUpAnimation[playernum].skillUps.clear(); + mechanics.itemDegradeRng.clear(); + mechanics.sustainedSpellMPUsed = 0; } void Player::cleanUpOnEntityRemoval() @@ -3114,6 +3117,7 @@ void Player::cleanUpOnEntityRemoval() movement.reset(); worldUI.reset(); } + mechanics.enemyRaisedBlockingAgainst.clear(); selectedEntity[playernum] = nullptr; client_selected[playernum] = nullptr; } @@ -6828,3 +6832,139 @@ const char* Player::getAccountName() const { } return unknown; } + +void Player::PlayerMechanics_t::onItemDegrade(Item* item) +{ + if ( !item ) + { + return; + } + if ( item->type < 0 || item->type >= NUMITEMS ) + { + return; + } + if ( itemCategory(item) == SPELLBOOK ) + { + itemDegradeRng[item->type] = 0; + } +} + +bool Player::PlayerMechanics_t::itemDegradeRoll(Item* item, int* checkInterval) +{ + if ( !item ) + { + return true; + } + // assuming just shields/spellbooks for now + if ( item->type < 0 || item->type >= NUMITEMS ) + { + return true; + } + + auto& counter = itemDegradeRng[item->type]; + int interval = 0; + if ( itemCategory(item) == SPELLBOOK ) + { + // 10 max base interval + interval = (1 + item->status) + stats[player.playernum]->getModifiedProficiency(PRO_SPELLCASTING) / 20; + if ( item->beatitude < 0 + && !intro && !shouldInvertEquipmentBeatitude(stats[player.playernum]) ) + { + interval = 0; + } + else if ( item->beatitude > 0 + || (item->beatitude < 0 + && !intro && shouldInvertEquipmentBeatitude(stats[player.playernum])) ) + { + interval += std::min(abs(item->beatitude), 2); + } + } + else + { + switch ( item->type ) + { + case WOODEN_SHIELD: + interval = 5; + break; + case IRON_SHIELD: + interval = 10; + break; + case STEEL_SHIELD: + interval = 15; + break; + case STEEL_SHIELD_RESISTANCE: + interval = 15; + break; + case CRYSTAL_SHIELD: + interval = 10; + break; + default: + break; + } + if ( item->beatitude < 0 + && !intro && !shouldInvertEquipmentBeatitude(stats[player.playernum]) ) + { + interval = 0; + } + else if ( item->beatitude > 0 + || (item->beatitude < 0 + && !intro && shouldInvertEquipmentBeatitude(stats[player.playernum])) ) + { + interval += std::min(abs(item->beatitude), 5); + } + } + + if ( checkInterval ) + { + *checkInterval = interval; + return false; + } + + //messagePlayer(0, MESSAGE_DEBUG, "counter: %d | interval: %d", counter, interval); + + if ( counter >= interval ) + { + if ( itemCategory(item) == SPELLBOOK ) + { + // dont decrement until degraded + } + else + { + counter = 0; + } + return true; + } + ++counter; + return false; +} + +void Player::PlayerMechanics_t::sustainedSpellIncrementMP(int mpChange) +{ + sustainedSpellMPUsed += std::max(0, mpChange); +} + +bool Player::PlayerMechanics_t::sustainedSpellLevelChance() +{ + int threshold = 10; + if ( stats[player.playernum]->getProficiency(PRO_SPELLCASTING) < SKILL_LEVEL_BASIC ) + { + threshold = 5; + } + if ( sustainedSpellMPUsed >= threshold ) + { + return true; + } + else + { + return false; + } +} + +bool Player::PlayerMechanics_t::allowedRaiseBlockingAgainstEntity(Entity& attacker) +{ + if ( attacker.behavior != &actMonster ) + { + return false; + } + return enemyRaisedBlockingAgainst[attacker.getUID()] < 1; +} \ No newline at end of file diff --git a/src/player.hpp b/src/player.hpp index 7d03fd033..560e9a673 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2285,6 +2285,23 @@ class Player void updateFloorEvents(); } compendiumProgress; + class PlayerMechanics_t + { + Player& player; + public: + std::map itemDegradeRng; + bool itemDegradeRoll(Item* item, int* checkInterval = nullptr); + void onItemDegrade(Item* item); + int sustainedSpellMPUsed = 0; + bool sustainedSpellLevelChance(); + void sustainedSpellIncrementMP(int mpChange); + std::map enemyRaisedBlockingAgainst; + bool allowedRaiseBlockingAgainstEntity(Entity& attacker); + PlayerMechanics_t(Player& p) : player(p) + {}; + ~PlayerMechanics_t() {}; + } mechanics; + static void soundMovement(); static void soundActivate(); static void soundCancel(); diff --git a/src/scores.cpp b/src/scores.cpp index 7a02c6373..e86e3724d 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -3988,7 +3988,7 @@ bool anySaveFileExists() void updateAchievementRhythmOfTheKnight(int player, Entity* target, bool playerIsHit) { - if ( achievementStatusRhythmOfTheKnight[player] || multiplayer == CLIENT + if ( multiplayer == CLIENT || player < 0 || player >= MAXPLAYERS ) { return; @@ -6079,6 +6079,11 @@ int SaveGameInfo::populateFromSession(const int playernum) h2.second.numAggressions = h.second.numAggressions; h2.second.numAccessories = h.second.numAccessories; } + for ( auto& pair : ::players[c]->mechanics.itemDegradeRng ) + { + player.itemDegradeRNG.push_back(pair); + } + player.sustainedSpellMPUsed = ::players[c]->mechanics.sustainedSpellMPUsed; for ( auto& pair : ::players[c]->compendiumProgress.itemEvents ) { @@ -6953,6 +6958,18 @@ int loadGame(int player, const SaveGameInfo& info) { } } + // player rng stuff + { + auto& mechanics = players[statsPlayer]->mechanics; + mechanics.itemDegradeRng.clear(); + for ( auto& pair : info.players[player].itemDegradeRNG ) + { + mechanics.itemDegradeRng[pair.first] = pair.second; + } + mechanics.sustainedSpellMPUsed = 0; + mechanics.sustainedSpellMPUsed = info.players[player].sustainedSpellMPUsed; + } + Player::Minimap_t::mapDetails = info.map_messages; if ( !info.hiscore_dummy_loading ) diff --git a/src/scores.hpp b/src/scores.hpp index b995871c5..a96bc55a2 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -423,6 +423,8 @@ struct SaveGameInfo { }; std::vector> shopkeeperHostility; std::vector>> compendium_item_events; + std::vector> itemDegradeRNG; + int sustainedSpellMPUsed = 0; struct stat_t { struct item_t { @@ -606,6 +608,8 @@ struct SaveGameInfo { fp->property("game_statistics", gameStatistics); fp->property("shopkeeper_hostility", shopkeeperHostility); fp->property("compendium_item_events", compendium_item_events); + fp->property("item_degrade_rng", itemDegradeRNG); + fp->property("sustained_mp_used", sustainedSpellMPUsed); return true; } diff --git a/src/stat.cpp b/src/stat.cpp index ae4659782..8d818e265 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -1401,19 +1401,28 @@ void Stat::copyNPCStatsAndInventoryFrom(Stat& src) intro = oldIntro; } -int Stat::getActiveShieldBonus(bool checkShield, bool excludeSkill) const +int Stat::getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem) const { - if ( !checkShield ) + Item* item = shieldItem; + if ( !item ) { - return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); + if ( !checkShield ) + { + return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); + } + item = shield; } - - if ( shield ) + if ( item ) { - if ( itemCategory(shield) == SPELLBOOK || itemTypeIsQuiver(shield->type) ) + if ( itemCategory(item) == SPELLBOOK || itemTypeIsQuiver(item->type) ) { return 0; } + if ( itemCategory(item) != ARMOR ) + { + // non-armor caps out at 40 blocking + return (5 + (excludeSkill ? 0 : std::min(SKILL_LEVEL_SKILLED, getModifiedProficiency(PRO_SHIELD)) / 5)); + } return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); } else diff --git a/src/stat.hpp b/src/stat.hpp index 84378f30f..197152d4f 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -353,7 +353,7 @@ class Stat MONSTER_FORCE_PLAYER_RECRUITABLE }; int getPassiveShieldBonus(bool checkShield, bool excludeSkill) const; - int getActiveShieldBonus(bool checkShield, bool excludeSkill) const; + int getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem = nullptr) const; std::string getAttribute(std::string key) const { if ( attributes.find(key) != attributes.end() ) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 8276c263b..103d6f824 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -32502,7 +32502,7 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } else if ( tag == "BLOCK_DEGRADE_NORMAL_CHANCE" ) { - val = 25 + (stats[playernum]->type == GOBLIN ? 10 : 0); // degrade > 0 dmg taken + val = 25 + (stats[playernum]->type == GOBLIN ? 10 : 0) + 10; // degrade > 0 dmg taken val += 2 * (static_cast(stats[playernum]->getModifiedProficiency(proficiency) / 10)); if ( skillCapstoneUnlocked(playernum, proficiency) ) { From f0474f11f24078961db629be44721ae7ef2fedef Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 18 Oct 2024 04:02:48 +1100 Subject: [PATCH 195/244] * hell map hashes --- src/files.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/files.cpp b/src/files.cpp index 25fbe0120..5e0cbea3c 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -386,7 +386,8 @@ std::unordered_map mapHashes = { { "hell29d.lmp", 144983 }, { "hell29e.lmp", 48684 }, { "hell29f.lmp", 167923 }, - { "hellboss.lmp", 835271 }, + { "hellboss.lmp", 4459727 }, + { "baphoexit.lmp", 3442748 }, { "labyrinth.lmp", 397402 }, { "labyrinth00.lmp", 119759 }, { "labyrinth01.lmp", 32319 }, From d427635cca65d3ba3db7175866016dfe0283e449 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 18 Oct 2024 04:03:13 +1100 Subject: [PATCH 196/244] * /hell_baphoexit cmd to test hell secret exit --- src/maps.cpp | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index bf7b6f42d..7d2399358 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -836,6 +836,8 @@ bool mapTileDiggable(const int x, const int y) return true; } +static ConsoleVariable cvar_hell_baphoexit("/hell_baphoexit", false); + /*------------------------------------------------------------------------------- generateDungeon @@ -1050,6 +1052,14 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { secretlevelexit = 6; } + else if ( currentlevel == 23 ) + { + if ( *cvar_hell_baphoexit ) + { + secretlevelexit = 8; + minotaurlevel = false; + } + } } mapList.first = nullptr; @@ -1696,6 +1706,9 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple strcpy(secretmapname, levelset); strcat(secretmapname, "secret"); break; + case 8: + strcpy(secretmapname, "baphoexit"); + break; default: break; } @@ -1884,15 +1897,22 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { if ( c == 0 ) { - // 7x7, pick random location across all map. - x = getMapPossibleLocationX1() + (1 + map_rng.rand() % 4) * 7; - y = getMapPossibleLocationY1() + (1 + map_rng.rand() % 4) * 7; + if ( secretlevelexit ) + { + x = getMapPossibleLocationX1() + 7; + y = getMapPossibleLocationY1() + 7; + } + else + { + // 7x7, pick random location across all map. + x = getMapPossibleLocationX1() + (1 + map_rng.rand() % 4) * 7; + y = getMapPossibleLocationY1() + (1 + map_rng.rand() % 4) * 7; + } } else if ( secretlevelexit && c == 1 ) { - // 14x14, pick random location minus 1 from both edges. - x = 2 + (map_rng.rand() % 5) * 7; - y = 2 + (map_rng.rand() % 5) * 7; + x = getMapPossibleLocationX1() + (3) * 7; + y = getMapPossibleLocationY1() + (3) * 7; } else if ( c == 2 && shoplevel ) { @@ -3424,6 +3444,11 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( (c == 0 || (minotaurlevel && c < 2)) && (!secretlevel || currentlevel != 7) && (!secretlevel || currentlevel != 20) && std::get(mapParameters) == 0 ) { + if ( !strcmp(map.name, "Hell") && secretlevelexit && *cvar_hell_baphoexit ) + { + continue; // no generate exit + } + // daedalus shrine if ( c == 1 && minotaurlevel && !(secretlevel && (currentlevel == 7 || currentlevel == 20)) ) { @@ -6884,6 +6909,10 @@ void assignActions(map_t* map) { entity->skill[3] = 1; // not secret portal, just aesthetic. } + else if ( !strcmp(map->name, "Hell") && currentlevel == 23 && *cvar_hell_baphoexit ) + { + entity->portalNotSecret = 1; // not secret portal, just aesthetic. + } entity->flags[PASSABLE] = true; break; // secret ladder: From 1984fb1a9be181bd6e124670d8efe6d26f22d31c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Fri, 18 Oct 2024 04:07:25 +1100 Subject: [PATCH 197/244] *new langs --- lang/en.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index 9c250c7fd..809240a5c 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6531,5 +6531,11 @@ Unlock Achievements to earn more!# 6301 Lobby: Invite Only# 6302 gnome thief# 6303 thief leader# +6304 Enchant Weapon# +6305 Enchant Armor# +6306 Select an item to enchant:# +6307 This %s + is unable to be enchanted.# +6308 Your %s loses some of its blessing.# 6350 END# From 81ba7b72bd4904706802a3f5d80a978f79f3f338 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:26:26 +1100 Subject: [PATCH 198/244] * gnome thieves use own sound effect entries to normal for scarony --- src/monster_gnome.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index 3ac255459..1ae0bfacf 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -61,6 +61,7 @@ void initGnome(Entity* my, Stat* myStats) if ( gnome_type.find("gnome2") != std::string::npos ) { MONSTER_IDLESND = 683; + MONSTER_SPOTSND = 693; } else { @@ -158,11 +159,13 @@ void initGnome(Entity* my, Stat* myStats) { myStats->sex = sex_t::FEMALE; MONSTER_IDLESND = 683; + MONSTER_SPOTSND = 693; } else if ( myStats->getAttribute("gnome_type").find("gnome2") != std::string::npos ) { myStats->sex = sex_t::MALE; MONSTER_IDLESND = 683; + MONSTER_SPOTSND = 693; } GnomeVariant gnomeVariant = GNOME_DEFAULT; @@ -309,7 +312,7 @@ void initGnome(Entity* my, Stat* myStats) } } case 2: - if ( rng.rand() % 10 == 0 ) + if ( rng.rand() % 20 == 0 ) { if ( rng.rand() % 2 == 0 ) { @@ -321,7 +324,7 @@ void initGnome(Entity* my, Stat* myStats) } } case 1: - if ( rng.rand() % 3 == 0 ) + if ( rng.rand() % 10 == 0 ) { if ( rng.rand() % 4 == 0 ) { @@ -457,6 +460,10 @@ void initGnome(Entity* my, Stat* myStats) case 8: case 9: myStats->cloak = newItem(CLOAK, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + if ( gnomeVariant == GNOME_THIEF_MELEE || gnomeVariant == GNOME_THIEF_RANGED ) + { + myStats->cloak->isDroppable = (rng.rand() % 4 == 0) ? true : false; + } break; } } @@ -466,7 +473,7 @@ void initGnome(Entity* my, Stat* myStats) if ( gnomeVariant == GNOME_THIEF_MELEE ) { myStats->shoes = newItem(SUEDE_BOOTS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); - myStats->shoes->isDroppable = (rng.rand() % 4 == 0) ? true : false; + myStats->shoes->isDroppable = (rng.rand() % 8 == 0) ? true : false; } } @@ -475,7 +482,7 @@ void initGnome(Entity* my, Stat* myStats) if ( gnomeVariant == GNOME_THIEF_RANGED ) { myStats->gloves = newItem(SUEDE_GLOVES, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); - myStats->gloves->isDroppable = (rng.rand() % 4 == 0) ? true : false; + myStats->gloves->isDroppable = (rng.rand() % 8 == 0) ? true : false; } } @@ -504,7 +511,7 @@ void initGnome(Entity* my, Stat* myStats) if ( rng.rand() % 2 == 0 ) { myStats->helmet = newItem(HAT_HOOD, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 2, false, nullptr); - myStats->helmet->isDroppable = (rng.rand() % 4 == 0) ? true : false; + myStats->helmet->isDroppable = (rng.rand() % 8 == 0) ? true : false; } else { @@ -537,7 +544,7 @@ void initGnome(Entity* my, Stat* myStats) case 8: case 9: myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); - myStats->mask->isDroppable = (rng.rand() % 4 == 0) ? true : false; + myStats->mask->isDroppable = (rng.rand() % 8 == 0) ? true : false; break; default: break; @@ -861,13 +868,17 @@ void gnomeDie(Entity* my) } } } + playSoundEntity(my, 698 + local_rng.rand() % 4, 128); + } + else + { + playSoundEntity(my, 225 + local_rng.rand() % 4, 128); } my->spawnBlood(); my->removeMonsterDeathNodes(); - playSoundEntity(my, 225 + local_rng.rand() % 4, 128); list_RemoveNode(my->mynode); return; } From 8373a3ea26e89b237aa7957e19af212e9a1cf57e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:27:04 +1100 Subject: [PATCH 199/244] * hazard goggles prevent slime secondary effects, hell boss adds no drop items or xp --- src/magic/actmagic.cpp | 194 ++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 81 deletions(-) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 0d6f6a699..f0acc3cda 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -3056,6 +3056,15 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } + bool hasgoggles = false; + if ( hitstats && hitstats->mask && hitstats->mask->type == MASK_HAZARD_GOGGLES ) + { + if ( !(hit.entity->behavior == &actPlayer && hit.entity->effectShapeshift != NOTHING) ) + { + hasgoggles = true; + } + } + if ( mimic ) { playSoundEntity(hit.entity, hitsfx, hitvolume); @@ -3112,8 +3121,19 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. Uint32 color = makeColorRGB(255, 0, 0); messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6237)); } + if ( hasgoggles ) + { + if ( hit.entity->behavior == &actPlayer ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6088)); + } + } + } + + if ( !hasgoggles ) + { + hit.entity->SetEntityOnFire(); } - hit.entity->SetEntityOnFire(); } if ( spell->ID == SPELL_SLIME_TAR ) { @@ -3138,15 +3158,6 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. int duration = 6 * TICKS_PER_SECOND; duration /= (1 + (int)resistance); - bool hasgoggles = false; - if ( hitstats->mask && hitstats->mask->type == MASK_HAZARD_GOGGLES ) - { - if ( !(hit.entity->behavior == &actPlayer && hit.entity->effectShapeshift != NOTHING) ) - { - hasgoggles = true; - } - } - int status = hit.entity->behavior == &actPlayer ? EFF_MESSY : EFF_BLIND; if ( hasgoggles ) @@ -3174,7 +3185,15 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. { int duration = 10 * TICKS_PER_SECOND; duration /= (1 + (int)resistance); - if ( hit.entity->setEffect(EFF_GREASY, true, duration, false) ) + + if ( hasgoggles ) + { + if ( hit.entity->behavior == &actPlayer ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6088)); + } + } + else if ( hit.entity->setEffect(EFF_GREASY, true, duration, false) ) { Uint32 color = makeColorRGB(255, 0, 0); if ( hit.entity->behavior == &actPlayer ) @@ -3202,19 +3221,14 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( spell->ID == SPELL_SLIME_ACID || spell->ID == SPELL_SLIME_METAL ) { bool hasamulet = false; - bool hasgoggles = false; if ( (hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE) || hitstats->type == INSECTOID ) { resistance += 2; hasamulet = true; } - if ( hitstats->mask && hitstats->mask->type == MASK_HAZARD_GOGGLES ) + if ( hasgoggles ) { - if ( !(hit.entity->behavior == &actPlayer && hit.entity->effectShapeshift != NOTHING) ) - { - hasgoggles = true; - resistance += 2; - } + resistance += 2; } int duration = (spell->ID == SPELL_SLIME_METAL ? 10 : 6) * TICKS_PER_SECOND; @@ -3264,7 +3278,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( spell->ID == SPELL_SLIME_METAL ) { - if ( hit.entity->setEffect(EFF_SLOW, true, duration, false) ) + if ( !hasgoggles && hit.entity->setEffect(EFF_SLOW, true, duration, false) ) { if ( particleEmitterHitProps && particleEmitterHitProps->hits == 1 ) { @@ -3303,54 +3317,57 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. } } } - else if ( spell->ID == SPELL_SLIME_WATER && hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) ) + else if ( spell->ID == SPELL_SLIME_WATER ) { - real_t pushbackMultiplier = 0.6; - if ( hit.entity->behavior == &actMonster ) + if ( !hasgoggles && hit.entity->setEffect(EFF_KNOCKBACK, true, 30, false) ) { - if ( !hit.entity->isMobile() ) + real_t pushbackMultiplier = 0.6; + if ( hit.entity->behavior == &actMonster ) { - pushbackMultiplier += 0.3; - } - if ( parent ) - { - real_t tangent = atan2(hit.entity->y - parent->y, hit.entity->x - parent->x); - hit.entity->vel_x = cos(tangent) * pushbackMultiplier; - hit.entity->vel_y = sin(tangent) * pushbackMultiplier; - hit.entity->monsterKnockbackVelocity = 0.01; - hit.entity->monsterKnockbackUID = my->parent; - hit.entity->monsterKnockbackTangentDir = tangent; - } - else - { - real_t tangent = atan2(hit.entity->y - my->y, hit.entity->x - my->x); - hit.entity->vel_x = cos(tangent) * pushbackMultiplier; - hit.entity->vel_y = sin(tangent) * pushbackMultiplier; - hit.entity->monsterKnockbackVelocity = 0.01; - hit.entity->monsterKnockbackTangentDir = tangent; - } - } - else if ( hit.entity->behavior == &actPlayer ) - { - /*if ( parent ) - { - real_t dist = entityDist(parent, hit.entity); - if ( dist < TOUCHRANGE ) + if ( !hit.entity->isMobile() ) { - pushbackMultiplier += 0.5; + pushbackMultiplier += 0.3; + } + if ( parent ) + { + real_t tangent = atan2(hit.entity->y - parent->y, hit.entity->x - parent->x); + hit.entity->vel_x = cos(tangent) * pushbackMultiplier; + hit.entity->vel_y = sin(tangent) * pushbackMultiplier; + hit.entity->monsterKnockbackVelocity = 0.01; + hit.entity->monsterKnockbackUID = my->parent; + hit.entity->monsterKnockbackTangentDir = tangent; + } + else + { + real_t tangent = atan2(hit.entity->y - my->y, hit.entity->x - my->x); + hit.entity->vel_x = cos(tangent) * pushbackMultiplier; + hit.entity->vel_y = sin(tangent) * pushbackMultiplier; + hit.entity->monsterKnockbackVelocity = 0.01; + hit.entity->monsterKnockbackTangentDir = tangent; } - }*/ - if ( !players[hit.entity->skill[2]]->isLocalPlayer() ) - { - hit.entity->monsterKnockbackVelocity = pushbackMultiplier; - hit.entity->monsterKnockbackTangentDir = my->yaw; - serverUpdateEntityFSkill(hit.entity, 11); - serverUpdateEntityFSkill(hit.entity, 9); } - else + else if ( hit.entity->behavior == &actPlayer ) { - hit.entity->monsterKnockbackVelocity = pushbackMultiplier; - hit.entity->monsterKnockbackTangentDir = my->yaw; + /*if ( parent ) + { + real_t dist = entityDist(parent, hit.entity); + if ( dist < TOUCHRANGE ) + { + pushbackMultiplier += 0.5; + } + }*/ + if ( !players[hit.entity->skill[2]]->isLocalPlayer() ) + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = my->yaw; + serverUpdateEntityFSkill(hit.entity, 11); + serverUpdateEntityFSkill(hit.entity, 9); + } + else + { + hit.entity->monsterKnockbackVelocity = pushbackMultiplier; + hit.entity->monsterKnockbackTangentDir = my->yaw; + } } } @@ -3364,11 +3381,22 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. if ( hit.entity->behavior == &actPlayer ) { Uint32 color = makeColorRGB(255, 0, 0); - messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + if ( !hasgoggles ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + } + else + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6309)); + } if ( hitstats->type == VAMPIRE ) { - messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(6235)); + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, color, Language::get(644)); + } + else if ( hasgoggles ) + { + messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT, makeColorRGB(0, 255, 0), Language::get(6088)); } } } @@ -5840,30 +5868,34 @@ void actParticleTimer(Entity* my) Entity* monster = summonMonster(static_cast(my->particleTimerVariable1), my->x, my->y, forceLocation); if ( monster ) { - Stat* monsterStats = monster->getStats(); - if ( my->parent != 0 && uidToEntity(my->parent) ) + if ( Stat* monsterStats = monster->getStats() ) { - if ( uidToEntity(my->parent)->getRace() == LICH_ICE ) + if ( my->parent != 0 && uidToEntity(my->parent) ) { - //monsterStats->leader_uid = my->parent; - switch ( monsterStats->type ) + if ( uidToEntity(my->parent)->getRace() == LICH_ICE ) { - case AUTOMATON: - strcpy(monsterStats->name, "corrupted automaton"); - monsterStats->EFFECTS[EFF_CONFUSED] = true; - monsterStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1; - break; - default: - break; + //monsterStats->leader_uid = my->parent; + switch ( monsterStats->type ) + { + case AUTOMATON: + strcpy(monsterStats->name, "corrupted automaton"); + monsterStats->EFFECTS[EFF_CONFUSED] = true; + monsterStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1; + break; + default: + break; + } } - } - else if ( uidToEntity(my->parent)->getRace() == DEVIL ) - { - monsterStats->LVL = 5; - if ( my->particleTimerVariable2 >= 0 - && players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity ) + else if ( uidToEntity(my->parent)->getRace() == DEVIL ) { - monster->monsterAcquireAttackTarget(*(players[my->particleTimerVariable2]->entity), MONSTER_STATE_ATTACK); + monsterStats->monsterNoDropItems = 1; + monsterStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] = 1; + monsterStats->MISC_FLAGS[STAT_FLAG_NO_DROP_ITEMS] = 1; + if ( my->particleTimerVariable2 >= 0 + && players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity ) + { + monster->monsterAcquireAttackTarget(*(players[my->particleTimerVariable2]->entity), MONSTER_STATE_ATTACK); + } } } } From 702f2a379d42c634f315a0123fa974f5dc4876aa Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:27:43 +1100 Subject: [PATCH 200/244] * setup spells clear allGameSpells when called to prevent dupes --- src/magic/magic.hpp | 1 - src/magic/setupSpells.cpp | 2 ++ src/magic/spell.cpp | 7 ------- src/ui/GameUI.cpp | 18 +++++++++++++++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/magic/magic.hpp b/src/magic/magic.hpp index a7033662b..efd4ba339 100644 --- a/src/magic/magic.hpp +++ b/src/magic/magic.hpp @@ -583,7 +583,6 @@ void createParticleShadowTag(Entity* parent, Uint32 casterUid, int duration); void spawnMagicTower(Entity* parent, real_t x, real_t y, int spellID, Entity* autoHitTarget, bool castedSpell = false); // autoHitTarget is to immediate damage an entity, as all 3 tower magics hitting is unreliable bool magicDig(Entity* parent, Entity* projectile, int numRocks, int randRocks); -spell_t* newSpell(); spell_t* copySpell(spell_t* spell); void spellConstructor(spell_t* spell); void spellDeconstructor(void* data); diff --git a/src/magic/setupSpells.cpp b/src/magic/setupSpells.cpp index fae683d58..e865bd52f 100644 --- a/src/magic/setupSpells.cpp +++ b/src/magic/setupSpells.cpp @@ -18,6 +18,8 @@ std::vector allGameSpells; void setupSpells() ///TODO: Verify this function. { + allGameSpells.clear(); + node_t* node = NULL; spellElement_t* element = NULL; diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index 256c8d0af..a660147fd 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -454,13 +454,6 @@ bool addSpell(int spell, int player, bool ignoreSkill) return true; } -spell_t* newSpell() -{ - spell_t* spell = (spell_t*) malloc(sizeof(spell_t)); - spellConstructor(spell); - return spell; -} - void spellConstructor(spell_t* spell) { spell->ID = -1; diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 103d6f824..e1978a782 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -33492,15 +33492,31 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& skillLVL /= 20; } std::string magics = ""; + std::set inserted; for ( auto it = allGameSpells.begin(); it != allGameSpells.end(); ++it ) { auto spellEntry = *it; - if ( spellEntry->ID == SPELL_WEAKNESS || spellEntry->ID == SPELL_GHOST_BOLT ) + if ( !spellEntry ) + { + continue; + } + if ( spellEntry->ID == SPELL_WEAKNESS + || spellEntry->ID == SPELL_GHOST_BOLT + || spellEntry->ID == SPELL_SLIME_ACID + || spellEntry->ID == SPELL_SLIME_FIRE + || spellEntry->ID == SPELL_SLIME_WATER + || spellEntry->ID == SPELL_SLIME_TAR + || spellEntry->ID == SPELL_SLIME_METAL ) { continue; } if ( spellEntry && spellEntry->difficulty == (skillLVL * 20) ) { + if ( inserted.find(spellEntry->ID) != inserted.end() ) + { + continue; + } + inserted.insert(spellEntry->ID); if ( magics != "" ) { magics += '\n'; From 175a3466e428fb4d7fcfc0ef02bc02c79d0b452e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:27:54 +1100 Subject: [PATCH 201/244] * baphoexit map hash --- src/files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/files.cpp b/src/files.cpp index 5e0cbea3c..8ca5deb2d 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -387,7 +387,7 @@ std::unordered_map mapHashes = { { "hell29e.lmp", 48684 }, { "hell29f.lmp", 167923 }, { "hellboss.lmp", 4459727 }, - { "baphoexit.lmp", 3442748 }, + { "baphoexit.lmp", 4309647 }, { "labyrinth.lmp", 397402 }, { "labyrinth00.lmp", 119759 }, { "labyrinth01.lmp", 32319 }, From bac759d891bf03abfd1440032494acfef6ed21e3 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:28:47 +1100 Subject: [PATCH 202/244] * baphoexit remove console command, default behavior --- src/maps.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/maps.cpp b/src/maps.cpp index 7d2399358..fa3130d9f 100644 --- a/src/maps.cpp +++ b/src/maps.cpp @@ -836,8 +836,6 @@ bool mapTileDiggable(const int x, const int y) return true; } -static ConsoleVariable cvar_hell_baphoexit("/hell_baphoexit", false); - /*------------------------------------------------------------------------------- generateDungeon @@ -1054,11 +1052,8 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple } else if ( currentlevel == 23 ) { - if ( *cvar_hell_baphoexit ) - { - secretlevelexit = 8; - minotaurlevel = false; - } + secretlevelexit = 8; + minotaurlevel = false; } } @@ -1897,7 +1892,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple { if ( c == 0 ) { - if ( secretlevelexit ) + if ( secretlevelexit == 8 ) { x = getMapPossibleLocationX1() + 7; y = getMapPossibleLocationY1() + 7; @@ -1909,7 +1904,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple y = getMapPossibleLocationY1() + (1 + map_rng.rand() % 4) * 7; } } - else if ( secretlevelexit && c == 1 ) + else if ( secretlevelexit == 8 && c == 1 ) { x = getMapPossibleLocationX1() + (3) * 7; y = getMapPossibleLocationY1() + (3) * 7; @@ -2173,6 +2168,10 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple firstroomtile[y0 + x0 * map.height] = true; startRoomInfo.addCoord(x0, y0); } + else if ( c == 1 && secretlevelexit == 8 && !strncmp(map.name, "Hell", 4) ) + { + firstroomtile[y0 + x0 * map.height] = true; + } else if ( c == 2 && shoplevel ) { firstroomtile[y0 + x0 * map.height] = true; @@ -3444,7 +3443,7 @@ int generateDungeon(char* levelset, Uint32 seed, std::tuple if ( (c == 0 || (minotaurlevel && c < 2)) && (!secretlevel || currentlevel != 7) && (!secretlevel || currentlevel != 20) && std::get(mapParameters) == 0 ) { - if ( !strcmp(map.name, "Hell") && secretlevelexit && *cvar_hell_baphoexit ) + if ( !strcmp(map.name, "Hell") && secretlevelexit == 8 ) { continue; // no generate exit } @@ -6909,7 +6908,7 @@ void assignActions(map_t* map) { entity->skill[3] = 1; // not secret portal, just aesthetic. } - else if ( !strcmp(map->name, "Hell") && currentlevel == 23 && *cvar_hell_baphoexit ) + else if ( !strcmp(map->name, "Hell") && currentlevel == 23 ) { entity->portalNotSecret = 1; // not secret portal, just aesthetic. } From 79c81d81d2345b211a9dbbbf3ca3cf298a83a144 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 00:29:07 +1100 Subject: [PATCH 203/244] * add bronze shield to degrade prevention rolls --- src/player.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/player.cpp b/src/player.cpp index 029cd880e..d802e55a5 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -6886,6 +6886,9 @@ bool Player::PlayerMechanics_t::itemDegradeRoll(Item* item, int* checkInterval) case WOODEN_SHIELD: interval = 5; break; + case BRONZE_SHIELD: + interval = 10; + break; case IRON_SHIELD: interval = 10; break; From ca4df094978f98cc8793dcad33f5026d85437050 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 22:26:31 +1100 Subject: [PATCH 204/244] * disable bapho mob miniboss % --- src/magic/actmagic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index f0acc3cda..f48052e1a 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -5891,6 +5891,7 @@ void actParticleTimer(Entity* my) monsterStats->monsterNoDropItems = 1; monsterStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] = 1; monsterStats->MISC_FLAGS[STAT_FLAG_NO_DROP_ITEMS] = 1; + monsterStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; if ( my->particleTimerVariable2 >= 0 && players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity ) { From c645fe377f20c2cdf45b577864e87ea5b5544ac6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 22:35:03 +1100 Subject: [PATCH 205/244] * guild compendium records --- src/actboulder.cpp | 4 +++ src/actmonster.cpp | 9 +++++ src/actthrown.cpp | 20 +++++++++-- src/entity.cpp | 6 ++++ src/game.cpp | 61 ++++++++++++++++++++++++++++++++- src/item_tool.cpp | 5 +++ src/magic/magic.cpp | 8 +++++ src/magic/spell.cpp | 4 +++ src/menu.cpp | 21 ++++++++++++ src/mod_tools.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++-- src/mod_tools.hpp | 14 ++++++++ src/ui/MainMenu.cpp | 79 +++++++++++++++++++++++++++++++++++++++++- 12 files changed, 307 insertions(+), 7 deletions(-) diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 0bd346416..0dc8299e8 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -393,6 +393,10 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit achievementObserver.updateGlobalStat(STEAM_GSTAT_BOULDER_DEATHS); } } + if ( BOULDER_PLAYERPUSHED >= 0 && oldHP > 0 && stats->HP <= 0 ) + { + Compendium_t::Events_t::eventUpdateWorld(BOULDER_PLAYERPUSHED, Compendium_t::CPDM_COMBAT_MASONRY_BOULDERS, "masons guild", 1); + } if ( !lifeSaving ) { diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 0ca2847ae..9a1e423ae 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1961,6 +1961,10 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], } Compendium_t::Events_t::eventUpdateMonster(monsterclicked, Compendium_t::CPDM_RECRUITED, my, 1); + if ( myStats->type == HUMAN && myStats->getAttribute("special_npc") == "merlin" ) + { + Compendium_t::Events_t::eventUpdateWorld(monsterclicked, Compendium_t::CPDM_MERLINS, "magicians guild", 1); + } Compendium_t::Events_t::eventUpdateCodex(monsterclicked, Compendium_t::CPDM_RACE_RECRUITS, "races", 1); if ( (stats[monsterclicked]->type != HUMAN && stats[monsterclicked]->type != AUTOMATON) && myStats->type == HUMAN ) { @@ -12615,6 +12619,11 @@ bool Entity::monsterConsumeFoodEntity(Entity* food, Stat* myStats) // eating sound playSoundEntity(this, 50 + local_rng.rand() % 2, 64); + if ( leader ) + { + Compendium_t::Events_t::eventUpdateWorld(leader->skill[2], Compendium_t::CPDM_ALLIES_FED, "the church", 1); + } + return foodEntityConsumed; } diff --git a/src/actthrown.cpp b/src/actthrown.cpp index 7b6b3b7f5..805d131d8 100644 --- a/src/actthrown.cpp +++ b/src/actthrown.cpp @@ -860,7 +860,7 @@ void actThrown(Entity* my) bool disableAlertBlindStatus = false; bool ignorePotion = false; bool wasPotion = itemCategory(item) == POTION; - bool wasBoomerang = item->type == BOOMERANG; + ItemType itemType = item->type; bool wasConfused = (hitstats && hitstats->EFFECTS[EFF_CONFUSED]); bool healingPotion = false; @@ -938,7 +938,6 @@ void actThrown(Entity* my) { parent->increaseSkill(PRO_ALCHEMY); } - ItemType itemType = item->type; switch ( itemType ) { case POTION_WATER: @@ -968,6 +967,10 @@ void actThrown(Entity* my) { Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_RACE_RECRUITS, "races", 1); + if ( hitstats->type == HUMAN && hitstats->getAttribute("special_npc") == "merlin" ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_MERLINS, "magicians guild", 1); + } } hit.entity->monsterAllyIndex = parent->skill[2]; if ( multiplayer == SERVER ) @@ -1216,7 +1219,7 @@ void actThrown(Entity* my) steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_BOMBARDIER, STEAM_STAT_INT, 1); } } - if ( wasBoomerang ) + if ( itemType == BOOMERANG ) { if ( parent && parent->behavior == &actPlayer && hit.entity->behavior == &actMonster ) { @@ -1364,6 +1367,17 @@ void actThrown(Entity* my) { Compendium_t::Events_t::eventUpdateCodex(parent->skill[2], Compendium_t::CPDM_THROWN_KILLS, "thrown", 1); } + if ( cat == GEM ) + { + if ( itemType == GEM_ROCK || itemType == GEM_LUCK ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_COMBAT_MASONRY_ROCKS, "masons guild", 1); + } + else + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_COMBAT_MASONRY_GEMS, "masons guild", 1); + } + } } } diff --git a/src/entity.cpp b/src/entity.cpp index 93347f742..ce1bc9a09 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1940,6 +1940,10 @@ bool Entity::increaseSkill(int skill, bool notify) { Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_LEGENDS, skillstr, 1); } + if ( myStats->getProficiency(skill) == 20 ) + { + Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_NOVICES, skillstr, 1); + } Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_UPS, skillstr, 1); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_UPS_RUN_MAX, skillstr, 1); Compendium_t::Events_t::eventUpdateCodex(player, Compendium_t::CPDM_CLASS_SKILL_MAX, skillstr, myStats->getProficiency(skill)); @@ -13337,6 +13341,8 @@ void Entity::awardXP(Entity* src, bool share, bool root) } } } + + Compendium_t::Events_t::eventUpdateWorld(leader->skill[2], Compendium_t::CPDM_FOLLOWER_KILLS, "masons guild", 1); } } diff --git a/src/game.cpp b/src/game.cpp index 7d7814266..a07633491 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1825,11 +1825,62 @@ void gameLogic(void) // when this flag is set, it's time to load the next level. loadnextlevel = false; + int totalFloorGold = 0; + int totalFloorItems = 0; + int totalFloorItemValue[MAXPLAYERS]; + int totalFloorMonsters = 0; + int totalFloorEnemies[MAXPLAYERS]; + Item tmpItem; + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + totalFloorItemValue[i] = 0; + totalFloorEnemies[i] = 0; + } + for ( node = map.entities->first; node != nullptr; node = node->next ) { entity = (Entity*)node->element; entity->flags[NOUPDATE] = true; - + if ( entity->behavior == &actGoldBag ) + { + totalFloorGold += entity->goldAmount; + } + else if ( entity->behavior == &actItem ) + { + totalFloorItems++; + tmpItem.type = (entity->skill[10] >= 0 && entity->skill[10] < NUMITEMS) ? (ItemType)entity->skill[10] : ItemType::GEM_ROCK; + tmpItem.status = (int)entity->skill[11] < Status::BROKEN ? + Status::BROKEN : ((int)entity->skill[11] > EXCELLENT ? EXCELLENT : (Status)entity->skill[11]); + tmpItem.beatitude = std::min(std::max((Sint16)-100, (Sint16)entity->skill[12]), (Sint16)100); + tmpItem.count = std::max((Sint16)entity->skill[13], (Sint16)1); + tmpItem.appearance = entity->skill[14]; + tmpItem.identified = entity->skill[15]; + + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + totalFloorItemValue[i] += tmpItem.sellValue(i); + } + } + } + else if ( entity->behavior == &actMonster ) + { + for ( int i = 0; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + if ( players[i]->entity ) + { + if ( !entity->checkFriend(players[i]->entity) ) + { + totalFloorEnemies[i]++; + } + } + } + } + totalFloorMonsters++; + } if ( (entity->behavior == &actThrown || entity->behavior == &actParticleSapCenter) && entity->sprite == 977 ) { // boomerang particle, make sure to return on level change. @@ -2128,6 +2179,13 @@ void gameLogic(void) { playerDied[i] = true; } + if ( i == 0 || !players[i]->isLocalPlayer() ) + { + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_GOLD_LEFT_BEHIND, "merchants guild", totalFloorGold); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::MONSTERS_LEFT_BEHIND, "the church", totalFloorEnemies[i]); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::ITEMS_LEFT_BEHIND, "merchants guild", totalFloorItems); + Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::ITEM_VALUE_LEFT_BEHIND, "merchants guild", totalFloorItemValue[i]); + } } // load map file @@ -2426,6 +2484,7 @@ void gameLogic(void) if ( monsterStats->type == HUMAN && currentlevel == 25 && !strncmp(map.name, "Mages Guild", 11) ) { steamAchievementClient(c, "BARONY_ACH_ESCORT"); + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_HUMANS_SAVED, "the church", 1); } if ( c > 0 && multiplayer == SERVER && !players[c]->isLocalPlayer() && net_packet && net_packet->data ) diff --git a/src/item_tool.cpp b/src/item_tool.cpp index d769827c6..0070e084a 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -628,6 +628,11 @@ void Item::applyOrb(int player, ItemType type, Entity& entity) playSoundEntity(&entity, 166, 128); // invisible.ogg createParticleDropRising(&entity, entity.pedestalOrbType + 605, 1.0); serverSpawnMiscParticles(&entity, PARTICLE_EFFECT_RISING_DROP, entity.pedestalOrbType + 605); + + if ( entity.pedestalLockOrb == 1 ) + { + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_RITUALS_COMPLETED, "magicians guild", 1); + } } entity.pedestalHasOrb = type - ARTIFACT_ORB_BLUE + 1; serverUpdateEntitySkill(&entity, 0); // update orb status. diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index cd4bbbdaf..c24bf51cf 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -158,6 +158,10 @@ bool spellEffectDominate(Entity& my, spellElement_t& element, Entity& caster, En if ( hit.entity->monsterAllyIndex != parent->skill[2] ) { Compendium_t::Events_t::eventUpdateMonster(parent->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); + if ( hitstats->type == HUMAN && hitstats->getAttribute("special_npc") == "merlin" ) + { + Compendium_t::Events_t::eventUpdateWorld(parent->skill[2], Compendium_t::CPDM_MERLINS, "magicians guild", 1); + } } } @@ -1544,6 +1548,10 @@ void spellEffectCharmMonster(Entity& my, spellElement_t& element, Entity* parent whoToFollow->increaseSkill(PRO_LEADERSHIP); messagePlayerMonsterEvent(whoToFollow->skill[2], color, *hitstats, Language::get(3137), Language::get(3138), MSG_COMBAT); Compendium_t::Events_t::eventUpdateMonster(whoToFollow->skill[2], Compendium_t::CPDM_RECRUITED, hit.entity, 1); + if ( hitstats->type == HUMAN && hitstats->getAttribute("special_npc") == "merlin" ) + { + Compendium_t::Events_t::eventUpdateWorld(whoToFollow->skill[2], Compendium_t::CPDM_MERLINS, "magicians guild", 1); + } hit.entity->monsterAllyIndex = whoToFollow->skill[2]; if ( multiplayer == SERVER ) { diff --git a/src/magic/spell.cpp b/src/magic/spell.cpp index a660147fd..0f5091ead 100644 --- a/src/magic/spell.cpp +++ b/src/magic/spell.cpp @@ -451,6 +451,10 @@ bool addSpell(int spell, int player, bool ignoreSkill) } free(item); + if ( !ignoreSkill || (ignoreSkill && !intro) ) + { + Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_SPELLS_LEARNED, "magicians guild", 1); + } return true; } diff --git a/src/menu.cpp b/src/menu.cpp index 36aff1d3d..e1e730b77 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9633,6 +9633,27 @@ void doEndgame(bool saveHighscore, bool onServerDisconnect) { } achievementObserver.updateGlobalStat(STEAM_GSTAT_GAMES_WON); + for ( int c = 0; c < MAXPLAYERS; ++c ) + { + if ( players[c]->isLocalPlayer() ) + { + for ( node_t* node = stats[c]->FOLLOWERS.first; node != nullptr; node = node->next ) + { + Entity* follower = nullptr; + if ( (Uint32*)node->element ) + { + follower = uidToEntity(*((Uint32*)node->element)); + } + if ( follower ) + { + if ( follower->getMonsterTypeFromSprite() == HUMAN ) + { + Compendium_t::Events_t::eventUpdateWorld(c, Compendium_t::CPDM_HUMANS_SAVED, "the church", 1); + } + } + } + } + } } } diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 31f00f002..0c45a4621 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -12430,6 +12430,22 @@ std::string Compendium_t::Events_t::formatEventRecordText(Sint32 value, const ch } output += buf; } + else if ( resultsFormatting[c + 1] == '$' ) + { + int gold = value; + // gold amounts + char buf[32]; + if ( gold >= 1000.f ) + { + float k = gold / 1000.f; + snprintf(buf, sizeof(buf), "%.3fk ", k); + } + else + { + snprintf(buf, sizeof(buf), "%d", gold); + } + output += buf; + } else if ( resultsFormatting[c + 1] == 't' ) { if ( langMap.find("format_time") != langMap.end() ) @@ -12646,6 +12662,10 @@ std::vector> Compendium_t::Events_t::getCustomEve { formatType = d["value"]["format"].GetString(); } + if ( d["value"].HasMember("compendium_section") ) + { + compendiumSection = d["value"]["compendium_section"].GetString(); + } if ( d["value"].HasMember("tags") ) { for ( auto itr = d["value"]["tags"].Begin(); itr != d["value"]["tags"].End(); ++itr ) @@ -12931,7 +12951,7 @@ std::vector> Compendium_t::Events_t::getCustomEve std::string output = ""; int numFormats = 0; - if ( valueType == "sum_category_max" || valueType == "sum_category_min" ) + if ( valueType == "sum_category_max" || valueType == "sum_category_min" || valueType == "sum_category_cycle" ) { int categoryValue = foundId; if ( formatType == "skills" ) @@ -13056,6 +13076,16 @@ std::vector> Compendium_t::Events_t::getCustomEve } return results; } + else if ( valueType == "sum_category_cycle" ) + { + results.clear(); + for ( auto& pair : mapValueTotals ) + { + std::string output = formatEventRecordText(pair.second, formatType.c_str(), pair.first, eventCustomLangEntries[key]); + results.push_back(std::make_pair(output, pair.second)); + } + return results; + } else if ( valueType == "sum_category_min" ) { results.clear(); @@ -15023,7 +15053,18 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks[find->second]; if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) { - unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + if ( find->second == "merchants guild" + || find->second == "magicians guild" + || find->second == "hunters guild" + || find->second == "the church" + || find->second == "masons guild" ) + { + // dont reveal these, revealed @ hamlet + } + else + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } } if ( find->second == "shop" ) { @@ -15050,6 +15091,44 @@ void Compendium_t::Events_t::eventUpdateWorld(int playernum, const EventTags tag } } } + else if ( find->second == "hamlet" ) + { + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["merchants guild"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["magicians guild"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["hunters guild"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["the church"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + { + auto& unlockStatus = Compendium_t::CompendiumWorld_t::unlocks["masons guild"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::LOCKED_UNKNOWN ) + { + unlockStatus = Compendium_t::CompendiumUnlockStatus::LOCKED_REVEALED_UNVISITED; + } + } + } else if ( find->second == "molten throne" ) { auto find = monsterIDToString.find(Compendium_t::Events_t::kEventMonsterOffset + DEVIL); diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index d9f5cff9a..4b3a628c0 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -3939,6 +3939,20 @@ struct Compendium_t CPDM_BELL_LOOT_GOLD, CPDM_BELL_LOOT_BATS, CPDM_BELL_CLAPPER_BROKEN, + CPDM_CLASS_SKILL_NOVICES, + CPDM_FOLLOWER_KILLS, + CPDM_COMBAT_MASONRY_BOULDERS, + CPDM_MERLINS, + CPDM_RITUALS_COMPLETED, + CPDM_HUMANS_SAVED, + CPDM_SPELLS_LEARNED, + CPDM_ALLIES_FED, + CPDM_COMBAT_MASONRY_GEMS, + CPDM_COMBAT_MASONRY_ROCKS, + CPDM_GOLD_LEFT_BEHIND, + MONSTERS_LEFT_BEHIND, + ITEMS_LEFT_BEHIND, + ITEM_VALUE_LEFT_BEHIND, CPDM_EVENT_TAGS_MAX }; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 3a74a6175..aeba24143 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -33728,7 +33728,84 @@ namespace MainMenu { if ( val ) { val->setDisabled(false); - if ( customTagKey != "" ) + if ( customTagKey == "CUSTOM_UNDEAD_KILLED" + || customTagKey == "CUSTOM_BEASTS_KILLED" + || customTagKey == "CUSTOM_DEMONS_KILLED" ) + { + val->setText("-"); + std::vector> results; + { + auto findTag = Compendium_t::Events_t::playerEvents.find(Compendium_t::EventTags::CPDM_KILLED_SOLO); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + for ( auto& pair : findTag->second ) + { + if ( pair.first >= Compendium_t::Events_t::kEventMonsterOffset + && pair.first < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) + { + auto find = Compendium_t::Events_t::monsterIDToString.find(pair.first); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + results.push_back(std::make_pair(pair.second.value, find->second)); + } + } + } + } + } + { + auto findTag = Compendium_t::Events_t::playerEvents.find(Compendium_t::CPDM_KILLED_MULTIPLAYER); + if ( findTag != Compendium_t::Events_t::playerEvents.end() ) + { + for ( auto& pair : findTag->second ) + { + if ( pair.first >= Compendium_t::Events_t::kEventMonsterOffset + && pair.first < Compendium_t::Events_t::kEventMonsterOffset + 1000 ) + { + auto find = Compendium_t::Events_t::monsterIDToString.find(pair.first); + if ( find != Compendium_t::Events_t::monsterIDToString.end() ) + { + results.push_back(std::make_pair(pair.second.value, find->second)); + } + } + } + } + } + { + int value = 0; + int matchingType = -1; + if ( customTagKey == "CUSTOM_UNDEAD_KILLED" ) { matchingType = Compendium_t::CompendiumMonsters_t::MonsterSpecies::SPECIES_UNDEAD; } + else if ( customTagKey == "CUSTOM_BEASTS_KILLED" ) { matchingType = Compendium_t::CompendiumMonsters_t::MonsterSpecies::SPECIES_BEAST; } + else if ( customTagKey == "CUSTOM_DEMONS_KILLED" ) { matchingType = Compendium_t::CompendiumMonsters_t::MonsterSpecies::SPECIES_DEMONOID; } + for ( auto& result : results ) + { + auto find = CompendiumEntries.monsters.find(result.second); + if ( find != CompendiumEntries.monsters.end() ) + { + if ( find->second.species == matchingType ) + { + value += result.first; + } + } + } + + results.clear(); + std::string output = Compendium_t::Events_t::formatEventRecordText(value, nullptr, 0, + Compendium_t::Events_t::eventCustomLangEntries[customTagKey] + ); + results.push_back(std::make_pair(value, output)); + } + if ( results.size() > 0 ) + { + compendiumRecordsSectionLoadedValues[index].clear(); + for ( auto& pair : results ) + { + compendiumRecordsSectionLoadedValues[index].push_back(pair.second); + } + val->setText(compendiumRecordsSectionLoadedValues[index][compendiumRecordsSectionRandSequence + % compendiumRecordsSectionLoadedValues[index].size()].c_str()); + } + } + else if ( customTagKey != "" ) { auto results = Compendium_t::Events_t::getCustomEventValue(customTagKey, compendium_current, compendium_contents_current[compendium_current], specificClass); From 385fea2dcebbf1aca355e2ed2b6b1b6c0ed757e6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 22:44:06 +1100 Subject: [PATCH 206/244] * fix chest lids opened compendium record --- src/actchest.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/actchest.cpp b/src/actchest.cpp index 8e64acfe5..8bc1d071b 100644 --- a/src/actchest.cpp +++ b/src/actchest.cpp @@ -662,8 +662,6 @@ void Entity::actChest() return; } - int i; - if (!chestInit) { auto& rng = entity_rng ? *entity_rng : local_rng; @@ -811,7 +809,7 @@ void Entity::actChest() //Using the chest (TODO: Monsters using it?). int chestclicked = -1; - for (i = 0; i < MAXPLAYERS; ++i) + for (int i = 0; i < MAXPLAYERS; ++i) { if ( selectedEntity[i] == this || client_selected[i] == this ) { @@ -835,7 +833,7 @@ void Entity::actChest() messagePlayer(chestclicked, MESSAGE_INTERACTION, Language::get(459)); openedChest[chestclicked] = this; - Compendium_t::Events_t::eventUpdateWorld(i, Compendium_t::CPDM_CHESTS_OPENED, "chest", 1); + Compendium_t::Events_t::eventUpdateWorld(chestclicked, Compendium_t::CPDM_CHESTS_OPENED, "chest", 1); chestOpener = chestclicked; if ( !players[chestclicked]->isLocalPlayer() && multiplayer == SERVER) From 24cf7b054eed08418e9a08ad969b340573ddbdb1 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sat, 19 Oct 2024 23:06:46 +1100 Subject: [PATCH 207/244] * blocking skill page show new offhand calc --- src/stat.cpp | 11 +++++++++-- src/stat.hpp | 2 +- src/ui/GameUI.cpp | 9 +++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/stat.cpp b/src/stat.cpp index 8d818e265..34264bcd1 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -1401,14 +1401,21 @@ void Stat::copyNPCStatsAndInventoryFrom(Stat& src) intro = oldIntro; } -int Stat::getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem) const +int Stat::getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem, bool checkNonShieldBonus) const { Item* item = shieldItem; if ( !item ) { if ( !checkShield ) { - return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); + if ( checkNonShieldBonus ) + { + return (5 + (excludeSkill ? 0 : std::min(SKILL_LEVEL_SKILLED, getModifiedProficiency(PRO_SHIELD)) / 5)); + } + else + { + return (5 + (excludeSkill ? 0 : (getModifiedProficiency(PRO_SHIELD) / 5))); + } } item = shield; } diff --git a/src/stat.hpp b/src/stat.hpp index 197152d4f..16ca4f9a9 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -353,7 +353,7 @@ class Stat MONSTER_FORCE_PLAYER_RECRUITABLE }; int getPassiveShieldBonus(bool checkShield, bool excludeSkill) const; - int getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem = nullptr) const; + int getActiveShieldBonus(bool checkShield, bool excludeSkill, Item* shieldItem = nullptr, bool checkNonShieldBonus = false) const; std::string getAttribute(std::string key) const { if ( attributes.find(key) != attributes.end() ) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index e1978a782..89fec2a92 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -17036,6 +17036,10 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element } snprintf(buf, sizeof(buf), getHoverTextString("attributes_ac_defending").c_str(), skillName.c_str(), skillLVL); std::string tag = "BLOCK_AC_INCREASE"; + if ( stats[player.playernum]->shield && itemCategory(stats[player.playernum]->shield) != ARMOR ) + { + tag = "BLOCK_AC_INCREASE_OFFHAND"; + } std::string blockBonus = formatSkillSheetEffects(player.playernum, PRO_SHIELD, tag, getHoverTextString("attributes_ac_bonus_format")); snprintf(valueBuf, sizeof(valueBuf), "%s", blockBonus.c_str()); } @@ -32495,6 +32499,11 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& val = stats[playernum]->getActiveShieldBonus(false, false); snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); } + else if ( tag == "BLOCK_AC_INCREASE_OFFHAND" ) + { + val = stats[playernum]->getActiveShieldBonus(false, false, nullptr, true); + snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val); + } else if ( tag == "PASSIVE_AC_INCREASE" ) { val = stats[playernum]->getPassiveShieldBonus(false, false); From d2bc136cc0d12ca9c01cf205ccb8c3ae706ae5b7 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 20 Oct 2024 01:30:26 +1100 Subject: [PATCH 208/244] * refactor stats->appearance to stat_appearance avoid item->appearance overlap --- src/actboulder.cpp | 2 +- src/actfountain.cpp | 4 +- src/actgeneral.cpp | 2 +- src/acthudweapon.cpp | 4 +- src/actladder.cpp | 8 ++-- src/actmonster.cpp | 8 ++-- src/actplayer.cpp | 18 ++++---- src/charclass.cpp | 20 ++++----- src/entity.cpp | 34 +++++++------- src/game.cpp | 12 ++--- src/init_game.cpp | 2 +- src/interface/consolecommand.cpp | 14 +++--- src/interface/drawstatus.cpp | 8 ++-- src/interface/playerinventory.cpp | 4 +- src/item_tool.cpp | 4 +- src/item_usage_funcs.cpp | 40 ++++++++--------- src/items.cpp | 2 +- src/magic/act_HandMagic.cpp | 4 +- src/magic/actmagic.cpp | 2 +- src/magic/castSpell.cpp | 18 ++++---- src/magic/magic.cpp | 4 +- src/menu.cpp | 16 +++---- src/mod_tools.cpp | 4 +- src/mod_tools.hpp | 4 +- src/monster_human.cpp | 46 +++++++++---------- src/net.cpp | 12 ++--- src/playfab.cpp | 4 +- src/scores.cpp | 44 +++++++++--------- src/scores.hpp | 4 +- src/stat.cpp | 6 +-- src/stat.hpp | 2 +- src/stat_editor.cpp | 2 +- src/stat_shared.cpp | 66 +++++++++++++-------------- src/ui/GameUI.cpp | 32 ++++++------- src/ui/MainMenu.cpp | 74 +++++++++++++++---------------- 35 files changed, 265 insertions(+), 265 deletions(-) diff --git a/src/actboulder.cpp b/src/actboulder.cpp index 0dc8299e8..e497f584e 100644 --- a/src/actboulder.cpp +++ b/src/actboulder.cpp @@ -401,7 +401,7 @@ int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntit if ( !lifeSaving ) { if ( stats->HP <= 0 && entity->behavior == &actPlayer - && ((stats->playerRace == RACE_SKELETON && stats->appearance == 0) || stats->type == SKELETON) ) + && ((stats->playerRace == RACE_SKELETON && stats->stat_appearance == 0) || stats->type == SKELETON) ) { if ( stats->MP >= 75 ) { diff --git a/src/actfountain.cpp b/src/actfountain.cpp index 789043532..da5644b89 100644 --- a/src/actfountain.cpp +++ b/src/actfountain.cpp @@ -202,7 +202,7 @@ void actFountain(Entity* my) steamAchievementClient(i, "BARONY_ACH_HOT_SHOWER"); } int potionDropQuantity = 0; - if ( stats[i] && (stats[i]->type == GOATMAN || (stats[i]->playerRace == RACE_GOATMAN && stats[i]->appearance == 0)) ) + if ( stats[i] && (stats[i]->type == GOATMAN || (stats[i]->playerRace == RACE_GOATMAN && stats[i]->stat_appearance == 0)) ) { // drop some random potions. switch ( rng.rand() % 10 ) @@ -232,7 +232,7 @@ void actFountain(Entity* my) if ( potionDropQuantity > 0 ) { - if ( stats[i]->playerRace == RACE_GOATMAN && stats[i]->appearance == 0 ) + if ( stats[i]->playerRace == RACE_GOATMAN && stats[i]->stat_appearance == 0 ) { steamStatisticUpdateClient(i, STEAM_STAT_BOTTLE_NOSED, STEAM_STAT_INT, 1); } diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index 7e4d261ac..d30666dab 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -4833,7 +4833,7 @@ void actBell(Entity* my) entity->modMP(amount); if ( svFlags & SV_FLAG_HUNGER ) { - if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { Sint32 hungerPointPerMana = entity->playerInsectoidHungerValueOfManaPoint(*stats); stats->HUNGER += amount * hungerPointPerMana; diff --git a/src/acthudweapon.cpp b/src/acthudweapon.cpp index ce7a5dbf7..fe4c71d43 100644 --- a/src/acthudweapon.cpp +++ b/src/acthudweapon.cpp @@ -74,7 +74,7 @@ void actHudArm(Entity* my) my->z = parent->z - 2.5; Monster playerRace = players[HUDARM_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HUDARM_PLAYERNUM]->playerRace); - int playerAppearance = stats[HUDARM_PLAYERNUM]->appearance; + int playerAppearance = stats[HUDARM_PLAYERNUM]->stat_appearance; if ( players[HUDARM_PLAYERNUM]->entity->effectShapeshift != NOTHING ) { playerRace = static_cast(players[HUDARM_PLAYERNUM]->entity->effectShapeshift); @@ -441,7 +441,7 @@ void actHudWeapon(Entity* my) } Monster playerRace = players[HUDWEAPON_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HUDWEAPON_PLAYERNUM]->playerRace); - int playerAppearance = stats[HUDWEAPON_PLAYERNUM]->appearance; + int playerAppearance = stats[HUDWEAPON_PLAYERNUM]->stat_appearance; if ( players[HUDWEAPON_PLAYERNUM]->entity->effectShapeshift != NOTHING ) { playerRace = static_cast(players[HUDWEAPON_PLAYERNUM]->entity->effectShapeshift); diff --git a/src/actladder.cpp b/src/actladder.cpp index a625a1d4a..4e773a75b 100644 --- a/src/actladder.cpp +++ b/src/actladder.cpp @@ -606,7 +606,7 @@ void actWinningPortal(Entity* my) if (cutscene == 1) { // classic herx ending int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } @@ -634,7 +634,7 @@ void actWinningPortal(Entity* my) } else if (cutscene == 2) { // classic baphomet ending int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } @@ -835,7 +835,7 @@ void Entity::actExpansionEndGamePortal() } int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } @@ -1044,7 +1044,7 @@ void Entity::actMidGamePortal() } int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 9a1e423ae..3fedeb612 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -1991,7 +1991,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], steamAchievementClient(monsterclicked, "BARONY_ACH_SSSMOKIN"); } } - if ( myStats->type == HUMAN && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->appearance == 0 + if ( myStats->type == HUMAN && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->stat_appearance == 0 && stats[monsterclicked]->playerRace == RACE_AUTOMATON ) { achievementObserver.updatePlayerAchievement(monsterclicked, AchievementObserver::Achievement::BARONY_ACH_REAL_BOY, @@ -2001,7 +2001,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], { serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_FORUM_TROLL, AchievementObserver::FORUM_TROLL_RECRUIT_TROLL); } - if ( stats[monsterclicked]->appearance == 0 + if ( stats[monsterclicked]->stat_appearance == 0 && (stats[monsterclicked]->playerRace == RACE_INCUBUS || stats[monsterclicked]->playerRace == RACE_SUCCUBUS) ) { if ( myStats->type == HUMAN ) @@ -2016,7 +2016,7 @@ bool makeFollower(int monsterclicked, bool ringconflict, char namesays[64], serverUpdatePlayerGameplayStats(monsterclicked, STATISTICS_PIMPING_AINT_EASY, 1); } } - if ( stats[monsterclicked]->appearance == 0 + if ( stats[monsterclicked]->stat_appearance == 0 && (stats[monsterclicked]->playerRace == RACE_GOBLIN) ) { if ( myStats->type == GOBLIN ) @@ -4267,7 +4267,7 @@ void actMonster(Entity* my) { // shopkeepers start trading startTradingServer(my, monsterclicked); - if ( stats[monsterclicked] && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->appearance == 0 + if ( stats[monsterclicked] && stats[monsterclicked]->type == HUMAN && stats[monsterclicked]->stat_appearance == 0 && stats[monsterclicked]->playerRace == RACE_AUTOMATON ) { achievementObserver.updatePlayerAchievement(monsterclicked, AchievementObserver::Achievement::BARONY_ACH_REAL_BOY, diff --git a/src/actplayer.cpp b/src/actplayer.cpp index cad9ec716..ab3ac7921 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -4013,17 +4013,17 @@ void doStatueEditor(int player) } if ( playerEntity->getMonsterFromPlayerRace(stats->playerRace) != HUMAN ) { - stats->appearance = 0; + stats->stat_appearance = 0; } } if ( keystatus[SDLK_F2] ) { keystatus[SDLK_F2] = 0; - ++stats->appearance; - if ( stats->appearance >= NUMAPPEARANCES ) + ++stats->stat_appearance; + if ( stats->stat_appearance >= NUMAPPEARANCES ) { - stats->appearance = 0; + stats->stat_appearance = 0; } } if ( keystatus[SDLK_F3] ) @@ -4405,7 +4405,7 @@ void actPlayer(Entity* my) int spriteLegLeft = 108 + 12 * stats[PLAYER_NUM]->sex; int spriteArmRight = 109 + 12 * stats[PLAYER_NUM]->sex; int spriteArmLeft = 110 + 12 * stats[PLAYER_NUM]->sex; - int playerAppearance = stats[PLAYER_NUM]->appearance; + int playerAppearance = stats[PLAYER_NUM]->stat_appearance; if ( my->effectShapeshift != NOTHING ) { @@ -4427,7 +4427,7 @@ void actPlayer(Entity* my) playerRace = static_cast(my->effectPolymorph); } } - if ( stats[PLAYER_NUM]->appearance == 0 || my->effectPolymorph != NOTHING ) + if ( stats[PLAYER_NUM]->stat_appearance == 0 || my->effectPolymorph != NOTHING ) { stats[PLAYER_NUM]->type = playerRace; } @@ -5275,7 +5275,7 @@ void actPlayer(Entity* my) serverSpawnMiscParticles(my, PARTICLE_EFFECT_VAMPIRIC_AURA, 600); } } - if ( currentlevel == 0 && stats[PLAYER_NUM]->playerRace == RACE_GOATMAN && stats[PLAYER_NUM]->appearance == 0 ) + if ( currentlevel == 0 && stats[PLAYER_NUM]->playerRace == RACE_GOATMAN && stats[PLAYER_NUM]->stat_appearance == 0 ) { if ( PLAYER_ALIVETIME == 1 ) { @@ -10469,7 +10469,7 @@ void Entity::setDefaultPlayerModel(int playernum, Monster playerRace, int limbTy return; } - int playerAppearance = stats[playernum]->appearance; + int playerAppearance = stats[playernum]->stat_appearance; if ( players[playernum] && players[playernum]->entity && players[playernum]->entity->effectPolymorph > NUMMONSTERS ) { playerAppearance = players[playernum]->entity->effectPolymorph - 100; @@ -10860,7 +10860,7 @@ bool playerRequiresBloodToSustain(int player) { return true; } - if ( stats[player]->playerRace == RACE_VAMPIRE && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_VAMPIRE && stats[player]->stat_appearance == 0 ) { return true; } diff --git a/src/charclass.cpp b/src/charclass.cpp index 7cc27c626..65a02dea9 100644 --- a/src/charclass.cpp +++ b/src/charclass.cpp @@ -577,7 +577,7 @@ void initClass(const int player) bool curseItems = false; if ( (stats[player]->playerRace == RACE_SUCCUBUS || stats[player]->playerRace == RACE_INCUBUS) - && stats[player]->appearance == 0 ) + && stats[player]->stat_appearance == 0 ) { curseItems = true; } @@ -2762,7 +2762,7 @@ void initClass(const int player) stats[player]->OLDHP = stats[player]->HP; - if ( stats[player]->appearance == 0 && stats[player]->playerRace == RACE_GOATMAN ) + if ( stats[player]->stat_appearance == 0 && stats[player]->playerRace == RACE_GOATMAN ) { stats[player]->EFFECTS[EFF_ASLEEP] = true; stats[player]->EFFECTS_TIMERS[EFF_ASLEEP] = -1; @@ -2780,12 +2780,12 @@ void initClass(const int player) stats[player]->EFFECTS_TIMERS[EFF_ASLEEP] = 0; } - if ( stats[player]->appearance == 0 && stats[player]->playerRace == RACE_AUTOMATON ) + if ( stats[player]->stat_appearance == 0 && stats[player]->playerRace == RACE_AUTOMATON ) { //stats[player]->HUNGER = 150; } - if ( stats[player]->appearance == 0 + if ( stats[player]->stat_appearance == 0 && client_classes[player] <= CLASS_MONK && stats[player]->playerRace != RACE_HUMAN ) { @@ -2797,7 +2797,7 @@ void initClass(const int player) free(item); } } - if ( stats[player]->appearance == 0 + if ( stats[player]->stat_appearance == 0 && client_classes[player] >= CLASS_CONJURER && client_classes[player] <= CLASS_HUNTER && stats[player]->playerRace != RACE_HUMAN ) @@ -2841,28 +2841,28 @@ void initClass(const int player) } if ( isLocalPlayer ) { - if ( stats[player]->playerRace == RACE_VAMPIRE && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_VAMPIRE && stats[player]->stat_appearance == 0 ) { addSpell(SPELL_LEVITATION, player, true); addSpell(SPELL_BLEED, player, true); } - else if ( stats[player]->playerRace == RACE_SUCCUBUS && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_SUCCUBUS && stats[player]->stat_appearance == 0 ) { addSpell(SPELL_TELEPORTATION, player, true); addSpell(SPELL_SELF_POLYMORPH, player, true); } - else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { addSpell(SPELL_FLUTTER, player, true); addSpell(SPELL_DASH, player, true); addSpell(SPELL_ACID_SPRAY, player, true); } - else if ( stats[player]->playerRace == RACE_INCUBUS && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_INCUBUS && stats[player]->stat_appearance == 0 ) { addSpell(SPELL_TELEPORTATION, player, true); addSpell(SPELL_SHADOW_TAG, player, true); } - else if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { addSpell(SPELL_SALVAGE, player, true); } diff --git a/src/entity.cpp b/src/entity.cpp index ce1bc9a09..103c0c285 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -1876,7 +1876,7 @@ bool Entity::increaseSkill(int skill, bool notify) } } - if ( player >= 0 && stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0 + if ( player >= 0 && stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0 && myStats->getProficiency(skill) == 100 ) { switch ( skill ) @@ -2655,7 +2655,7 @@ void Entity::drainMP(int amount, bool notifyOverexpend) } } - if ( player >= 0 && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 ) + if ( player >= 0 && entitystats->playerRace == RACE_INSECTOID && entitystats->stat_appearance == 0 ) { if ( svFlags & SV_FLAG_HUNGER ) { @@ -2779,7 +2779,7 @@ bool Entity::safeConsumeMP(int amount) } else { - if ( behavior == &actPlayer && stat->playerRace == RACE_INSECTOID && stat->appearance == 0 ) + if ( behavior == &actPlayer && stat->playerRace == RACE_INSECTOID && stat->stat_appearance == 0 ) { if ( svFlags & SV_FLAG_HUNGER ) { @@ -3078,7 +3078,7 @@ void Entity::handleEffects(Stat* myStats) { myStats->MP += MP_MOD; myStats->MAXMP += MP_MOD; - if ( behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 ) + if ( behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0 ) { myStats->MAXMP = std::min(50, myStats->MAXMP); if ( svFlags & SV_FLAG_HUNGER ) @@ -3804,7 +3804,7 @@ void Entity::handleEffects(Stat* myStats) if ( !(behavior == &actPlayer && effectShapeshift != NOTHING) && ((getHealthRegenInterval(this, *myStats, behavior == &actPlayer) == -1) || myStats->type == INSECTOID - || (behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0)) ) + || (behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0)) ) { if ( ticks % (HEAL_TIME) == 0 ) { @@ -3967,7 +3967,7 @@ void Entity::handleEffects(Stat* myStats) if ( myStats->mask && myStats->mask->type == MASK_PIPE ) { if ( !(behavior == &actPlayer && effectShapeshift != NOTHING) - && !(behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0) ) + && !(behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0) ) { if ( myStats->mask->beatitude >= 0 || shouldInvertEquipmentBeatitude(myStats) ) { @@ -4139,7 +4139,7 @@ void Entity::handleEffects(Stat* myStats) } } } - else if ( this->behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 ) + else if ( this->behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0 ) { if ( (svFlags & SV_FLAG_HUNGER) && !MFLAG_DISABLEHUNGER ) { @@ -4861,7 +4861,7 @@ void Entity::handleEffects(Stat* myStats) myStats->burningInflictedBy = 0; } - if ( player >= 0 && (stats[player]->type == SKELETON || (stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0)) ) + if ( player >= 0 && (stats[player]->type == SKELETON || (stats[player]->playerRace == RACE_SKELETON && stats[player]->stat_appearance == 0)) ) { // life saving if ( myStats->HP <= 0 ) @@ -5008,7 +5008,7 @@ void Entity::handleEffects(Stat* myStats) int amount = 2 + local_rng.rand() % 2; int oldMP = myStats->MP; this->modMP(amount); - if ( player >= 0 && stats[player]->appearance == 0 ) + if ( player >= 0 && stats[player]->stat_appearance == 0 ) { if ( stats[player]->playerRace == RACE_INCUBUS || stats[player]->playerRace == RACE_SUCCUBUS ) { @@ -5942,7 +5942,7 @@ Sint32 statGetDEX(Stat* entitystats, Entity* my) if ( !(svFlags & SV_FLAG_HUNGER) ) { - if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 ) + if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->stat_appearance == 0 ) { int dexDebuff = 0; if ( entitystats->MP < (entitystats->MAXMP) / 5 ) @@ -6327,7 +6327,7 @@ Sint32 statGetPER(Stat* entitystats, Entity* my) if ( !(svFlags & SV_FLAG_HUNGER) ) { - if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->appearance == 0 ) + if ( my && my->behavior == &actPlayer && entitystats->playerRace == RACE_INSECTOID && entitystats->stat_appearance == 0 ) { int perDebuff = 0; if ( entitystats->MP < (entitystats->MAXMP) / 5 ) @@ -13088,7 +13088,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) if ( root ) { achievementObserver.awardAchievementIfActive(player, src, AchievementObserver::BARONY_ACH_TELEFRAG); - if ( stats[player]->playerRace == RACE_INCUBUS && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_INCUBUS && stats[player]->stat_appearance == 0 ) { achievementObserver.playerAchievements[player].checkTraditionKill(this, src); } @@ -13312,7 +13312,7 @@ void Entity::awardXP(Entity* src, bool share, bool root) } if ( destStats->type == INSECTOID ) { - if ( leader->getStats()->playerRace == RACE_INSECTOID && leader->getStats()->appearance == 0 ) + if ( leader->getStats()->playerRace == RACE_INSECTOID && leader->getStats()->stat_appearance == 0 ) { steamStatisticUpdateClient(leader->skill[2], STEAM_STAT_MONARCH, STEAM_STAT_INT, 1); } @@ -19388,7 +19388,7 @@ int Entity::getManaRegenInterval(Entity* my, Stat& myStats, bool isPlayer) return floatRegenTime; } } - else if ( isPlayer && myStats.playerRace == RACE_INSECTOID && myStats.appearance == 0 ) + else if ( isPlayer && myStats.playerRace == RACE_INSECTOID && myStats.stat_appearance == 0 ) { if ( !(svFlags & SV_FLAG_HUNGER) ) { @@ -22606,7 +22606,7 @@ int getEntityHungerInterval(int player, Entity* my, Stat* myStats, EntityHungerI { isAutomatonPlayer = true; } - else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { isInsectoidPlayer = true; } @@ -22617,7 +22617,7 @@ int getEntityHungerInterval(int player, Entity* my, Stat* myStats, EntityHungerI { isAutomatonPlayer = true; } - else if ( myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 ) + else if ( myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0 ) { isInsectoidPlayer = true; } @@ -22632,7 +22632,7 @@ int getEntityHungerInterval(int player, Entity* my, Stat* myStats, EntityHungerI { isAutomatonPlayer = true; } - else if ( myStats->playerRace == RACE_INSECTOID && myStats->appearance == 0 ) + else if ( myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0 ) { isInsectoidPlayer = true; } diff --git a/src/game.cpp b/src/game.cpp index a07633491..7276de213 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -815,7 +815,7 @@ static void demo_record(const char* filename) { // write player stats demo_file->write(&stats[clientnum]->playerRace, sizeof(Stat::playerRace), 1); demo_file->write(&stats[clientnum]->sex, sizeof(Stat::sex), 1); - demo_file->write(&stats[clientnum]->appearance, sizeof(Stat::appearance), 1); + demo_file->write(&stats[clientnum]->stat_appearance, sizeof(Stat::stat_appearance), 1); demo_file->write(&client_classes[clientnum], sizeof(client_classes[clientnum]), 1); // write player name @@ -864,7 +864,7 @@ static void demo_play(const char* filename) { // read player stats demo_file->read(&stats[clientnum]->playerRace, sizeof(Stat::playerRace), 1); demo_file->read(&stats[clientnum]->sex, sizeof(Stat::sex), 1); - demo_file->read(&stats[clientnum]->appearance, sizeof(Stat::appearance), 1); + demo_file->read(&stats[clientnum]->stat_appearance, sizeof(Stat::stat_appearance), 1); demo_file->read(&client_classes[clientnum], sizeof(client_classes[clientnum]), 1); // read player name @@ -2870,7 +2870,7 @@ void gameLogic(void) } } - if ( item->type == FOOD_BLOOD && stats[player]->playerRace == RACE_VAMPIRE && stats[player]->appearance == 0 ) + if ( item->type == FOOD_BLOOD && stats[player]->playerRace == RACE_VAMPIRE && stats[player]->stat_appearance == 0 ) { bloodCount += item->count; if ( bloodCount >= 20 ) @@ -3544,7 +3544,7 @@ void gameLogic(void) } } - if ( item->type == FOOD_BLOOD && stats[clientnum]->playerRace == RACE_VAMPIRE && stats[clientnum]->appearance == 0 ) + if ( item->type == FOOD_BLOOD && stats[clientnum]->playerRace == RACE_VAMPIRE && stats[clientnum]->stat_appearance == 0 ) { bloodCount += item->count; if ( bloodCount >= 20 ) @@ -7183,12 +7183,12 @@ int main(int argc, char** argv) // set class loadout strcpy(stats[0]->name, "Avatar"); stats[0]->sex = static_cast(local_rng.rand() % 2); - stats[0]->appearance = local_rng.rand() % NUMAPPEARANCES; + stats[0]->stat_appearance = local_rng.rand() % NUMAPPEARANCES; stats[0]->clearStats(); initClass(0); if ( stats[0]->playerRace != RACE_HUMAN ) { - stats[0]->appearance = 0; + stats[0]->stat_appearance = 0; } // generate unique game key diff --git a/src/init_game.cpp b/src/init_game.cpp index 214b2b653..8d59166fe 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -303,7 +303,7 @@ int initGame() } players[c]->entity = nullptr; stats[c]->sex = static_cast(0); - stats[c]->appearance = 0; + stats[c]->stat_appearance = 0; strcpy(stats[c]->name, ""); stats[c]->type = HUMAN; stats[c]->playerRace = RACE_HUMAN; diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 06050adb6..4fb864097 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -1974,7 +1974,7 @@ namespace ConsoleCommands { if (i > 0) { stats[i]->sex = static_cast(local_rng.rand() % 2); - stats[i]->appearance = local_rng.rand() % 18; + stats[i]->stat_appearance = local_rng.rand() % 18; stats[i]->clearStats(); client_classes[i] = local_rng.rand() % (CLASS_MONK + 1);//NUMCLASSES; stats[i]->playerRace = RACE_HUMAN; @@ -2025,7 +2025,7 @@ namespace ConsoleCommands { client_classes[i] = local_rng.rand() % (NUMCLASSES); } } - stats[i]->appearance = local_rng.rand() % 18; + stats[i]->stat_appearance = local_rng.rand() % 18; } else { @@ -2034,13 +2034,13 @@ namespace ConsoleCommands { { client_classes[i] = CLASS_MONK + stats[i]->playerRace; // monster specific classes. } - stats[i]->appearance = 0; + stats[i]->stat_appearance = 0; } } else { stats[i]->playerRace = RACE_HUMAN; - stats[i]->appearance = local_rng.rand() % 18; + stats[i]->stat_appearance = local_rng.rand() % 18; } strcpy(stats[i]->name, randomPlayerNamesFemale[local_rng.rand() % randomPlayerNamesFemale.size()].c_str()); bool oldIntro = intro; @@ -3962,10 +3962,10 @@ namespace ConsoleCommands { messagePlayer(clientnum, MESSAGE_MISC, Language::get(277)); return; } - ++stats[clientnum]->appearance; - if (stats[clientnum]->appearance >= NUMAPPEARANCES) + ++stats[clientnum]->stat_appearance; + if (stats[clientnum]->stat_appearance >= NUMAPPEARANCES) { - stats[clientnum]->appearance = 0; + stats[clientnum]->stat_appearance = 0; } }); diff --git a/src/interface/drawstatus.cpp b/src/interface/drawstatus.cpp index 6a68bed43..fbd2e9242 100644 --- a/src/interface/drawstatus.cpp +++ b/src/interface/drawstatus.cpp @@ -441,7 +441,7 @@ void drawHPMPBars(int player) } Uint32 mpColorBG = makeColorRGB(0, 0, 48); Uint32 mpColorFG = makeColorRGB(0, 24, 128); - if ( stats[player] && stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + if ( stats[player] && stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { ttfPrintText(ttf12, pos.x + (playerStatusBarWidth / 2 - 10), pos.y + 6, Language::get(3768)); mpColorBG = makeColorRGB(32, 48, 0); @@ -1068,7 +1068,7 @@ void drawStatus(int player) if ( itemCategory(item) == SPELLBOOK && stats[player] && (stats[player]->type == GOBLIN - || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0)) ) + || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0)) ) { learnedSpell = true; // goblinos can't learn spells but always equip books. } @@ -2017,7 +2017,7 @@ void drawStatus(int player) if ( itemCategory(item) == SPELLBOOK && stats[player] ) { if ( stats[player]->type == GOBLIN || stats[player]->type == CREATURE_IMP - || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0) ) + || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0) ) { learnedSpell = true; // goblinos can't learn spells but always equip books. } @@ -3408,7 +3408,7 @@ void drawStatusNew(const int player) if ( itemCategory(item) == SPELLBOOK && stats[player] ) { if ( stats[player]->type == GOBLIN || stats[player]->type == CREATURE_IMP - || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0) ) + || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0) ) { learnedSpell = true; // goblinos can't learn spells but always equip books. } diff --git a/src/interface/playerinventory.cpp b/src/interface/playerinventory.cpp index 4418a07d1..7fe23af63 100644 --- a/src/interface/playerinventory.cpp +++ b/src/interface/playerinventory.cpp @@ -4891,7 +4891,7 @@ void Player::HUD_t::updateFrameTooltip(Item* item, const int x, const int y, int int skillLVL = std::min(100, stats[player]->getModifiedProficiency(PRO_MAGIC) + statGetINT(stats[player], players[player]->entity)); bool isGoblin = (stats[player] && (stats[player]->type == GOBLIN - || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0))); + || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0))); if ( icon.conditionalAttribute == "SPELLBOOK_CAST_BONUS" ) { if ( !items[item->type].hasAttribute(icon.conditionalAttribute) ) @@ -10431,7 +10431,7 @@ std::vector getContextMenuOptionsForItem(const int playe } else if ( stats[player] && (stats[player]->type == GOBLIN - || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0)) ) + || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0)) ) { // goblinos can't learn spells but always equip books. learnedSpell = true; diff --git a/src/item_tool.cpp b/src/item_tool.cpp index 0070e084a..78e0fdb7b 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -966,7 +966,7 @@ void Item::applyEmptyPotion(int player, Entity& entity) } if ( stats[player] && (stats[player]->type == GOATMAN - || (stats[player]->playerRace == RACE_GOATMAN && stats[player]->appearance == 0)) ) + || (stats[player]->playerRace == RACE_GOATMAN && stats[player]->stat_appearance == 0)) ) { int potionDropQuantity = 0; // drop some random potions. @@ -997,7 +997,7 @@ void Item::applyEmptyPotion(int player, Entity& entity) if ( potionDropQuantity > 0 ) { - if ( stats[player]->playerRace == RACE_GOATMAN && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_GOATMAN && stats[player]->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_BOTTLE_NOSED, STEAM_STAT_INT, 1); } diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index f4194aece..1a7dd4aa0 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -516,7 +516,7 @@ bool item_PotionBooze(Item*& item, Entity* entity, Entity* usedBy, bool shouldCo stats->HUNGER = std::min(1499, stats->HUNGER + 100); } } - if ( stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { stats->HUNGER += 250; } @@ -525,7 +525,7 @@ bool item_PotionBooze(Item*& item, Entity* entity, Entity* usedBy, bool shouldCo else { // hunger off. - if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { entity->modMP(5 * (1 + item->beatitude)); } @@ -672,7 +672,7 @@ bool item_PotionJuice(Item*& item, Entity* entity, Entity* usedBy) stats->HUNGER = std::min(1499, stats->HUNGER + 50); } } - if ( stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { stats->HUNGER += 200; } @@ -681,7 +681,7 @@ bool item_PotionJuice(Item*& item, Entity* entity, Entity* usedBy) else { // hunger off. - if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { entity->modMP(5); } @@ -705,7 +705,7 @@ bool item_PotionJuice(Item*& item, Entity* entity, Entity* usedBy) stats->HUNGER = std::min(1499, stats->HUNGER + 50); } } - if ( stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { stats->HUNGER += 200; } @@ -714,7 +714,7 @@ bool item_PotionJuice(Item*& item, Entity* entity, Entity* usedBy) else { // hunger off. - if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( entity->behavior == &actPlayer && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { entity->modMP(5 * (1 + item->beatitude)); } @@ -2263,7 +2263,7 @@ bool item_PotionRestoreMagic(Item*& item, Entity* entity, Entity* usedBy) if ( svFlags & SV_FLAG_HUNGER ) { - if ( player >= 0 && stats->playerRace == RACE_INSECTOID && stats->appearance == 0 ) + if ( player >= 0 && stats->playerRace == RACE_INSECTOID && stats->stat_appearance == 0 ) { Sint32 hungerPointPerMana = entity->playerInsectoidHungerValueOfManaPoint(*stats); stats->HUNGER += amount * hungerPointPerMana; @@ -4435,7 +4435,7 @@ void item_Food(Item*& item, int player) { conductVegetarian = false; } - if ( stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0 + if ( stats[player]->playerRace == RACE_SKELETON && stats[player]->stat_appearance == 0 && players[player] && players[player]->entity->effectPolymorph > NUMMONSTERS ) { steamAchievement("BARONY_ACH_MUSCLE_MEMORY"); @@ -4626,7 +4626,7 @@ void item_Food(Item*& item, int player) messagePlayer(player, MESSAGE_WORLD, Language::get(911)); - if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { real_t manaRegenPercent = 0.f; switch ( item->type ) @@ -4745,7 +4745,7 @@ void item_FoodTin(Item*& item, int player) { conductFoodless = false; conductVegetarian = false; - if ( stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0 + if ( stats[player]->playerRace == RACE_SKELETON && stats[player]->stat_appearance == 0 && players[player] && players[player]->entity->effectPolymorph > NUMMONSTERS ) { steamAchievement("BARONY_ACH_MUSCLE_MEMORY"); @@ -4911,7 +4911,7 @@ void item_FoodTin(Item*& item, int player) { players[player]->entity->modHP(std::max(1, (int)(5 * foodMult))); messagePlayer(player, MESSAGE_WORLD, Language::get(911)); - if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { real_t manaRegenPercent = 0.6 * foodMult; int manaAmount = stats[player]->MAXMP * manaRegenPercent; @@ -5011,13 +5011,13 @@ void item_AmuletSexChange(Item* item, int player) // find out what creature we are... if ( stats[player]->sex == FEMALE && stats[player]->playerRace == RACE_INCUBUS - && stats[player]->appearance == 0 ) + && stats[player]->stat_appearance == 0 ) { messagePlayer(player, MESSAGE_HINT, Language::get(4048)); // don't feel like yourself } else if ( stats[player]->sex == MALE && stats[player]->playerRace == RACE_SUCCUBUS - && stats[player]->appearance == 0 ) + && stats[player]->stat_appearance == 0 ) { messagePlayer(player, MESSAGE_HINT, Language::get(4048)); // don't feel like yourself } @@ -5066,7 +5066,7 @@ void item_Spellbook(Item*& item, int player) playSoundPlayer(player, 90, 64); return; } - else if ( stats[player] && (stats[player]->type == GOBLIN || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->appearance == 0)) ) + else if ( stats[player] && (stats[player]->type == GOBLIN || (stats[player]->playerRace == RACE_GOBLIN && stats[player]->stat_appearance == 0)) ) { messagePlayer(player, MESSAGE_HINT, Language::get(3444)); playSoundPlayer(player, 90, 64); @@ -5397,7 +5397,7 @@ void item_Spellbook(Item*& item, int player) consumeItem(item, player); } - if ( stats[player] && stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + if ( stats[player] && stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { steamStatisticUpdate(STEAM_STAT_BOOKWORM, STEAM_STAT_INT, 1); } @@ -5522,7 +5522,7 @@ void item_FoodAutomaton(Item*& item, int player) break; case READABLE_BOOK: stats[player]->HUNGER += 400; - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_FASCIST, STEAM_STAT_INT, 1); } @@ -5555,7 +5555,7 @@ void item_FoodAutomaton(Item*& item, int player) stats[player]->HUNGER += 1500; players[player]->entity->modMP(stats[player]->MAXMP); messagePlayerColor(player, MESSAGE_STATUS, color, Language::get(3699)); // superheats - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_SPICY, STEAM_STAT_INT, 1); } @@ -5576,7 +5576,7 @@ void item_FoodAutomaton(Item*& item, int player) break; } case TOOL_METAL_SCRAP: - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { achievementObserver.playerAchievements[player].trashCompactor += 1; } @@ -5593,7 +5593,7 @@ void item_FoodAutomaton(Item*& item, int player) } break; case TOOL_MAGIC_SCRAP: - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { achievementObserver.playerAchievements[player].trashCompactor += 1; } @@ -5621,7 +5621,7 @@ void item_FoodAutomaton(Item*& item, int player) if ( itemCategory(item) == SCROLL ) { - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_FASCIST, STEAM_STAT_INT, 1); } diff --git a/src/items.cpp b/src/items.cpp index 2e358fdd7..e68af7ad4 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -2535,7 +2535,7 @@ void useItem(Item* item, const int player, Entity* usedBy, bool unequipForDroppi const Uint32 color = makeColorRGB(255, 128, 0); messagePlayerColor(player, MESSAGE_STATUS, color, Language::get(3699)); // superheats serverUpdateHunger(player); - if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->appearance == 0 ) + if ( stats[player]->playerRace == RACE_AUTOMATON && stats[player]->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_SPICY, STEAM_STAT_INT, 1); steamStatisticUpdateClient(player, STEAM_STAT_FASCIST, STEAM_STAT_INT, 1); diff --git a/src/magic/act_HandMagic.cpp b/src/magic/act_HandMagic.cpp index e60fa6c62..27af30a3c 100644 --- a/src/magic/act_HandMagic.cpp +++ b/src/magic/act_HandMagic.cpp @@ -239,7 +239,7 @@ void actLeftHandMagic(Entity* my) //Sprite Monster playerRace = players[HANDMAGIC_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HANDMAGIC_PLAYERNUM]->playerRace); - int playerAppearance = stats[HANDMAGIC_PLAYERNUM]->appearance; + int playerAppearance = stats[HANDMAGIC_PLAYERNUM]->stat_appearance; if ( players[HANDMAGIC_PLAYERNUM]->entity->effectShapeshift != NOTHING ) { playerRace = static_cast(players[HANDMAGIC_PLAYERNUM]->entity->effectShapeshift); @@ -608,7 +608,7 @@ void actRightHandMagic(Entity* my) //Sprite Monster playerRace = players[HANDMAGIC_PLAYERNUM]->entity->getMonsterFromPlayerRace(stats[HANDMAGIC_PLAYERNUM]->playerRace); - int playerAppearance = stats[HANDMAGIC_PLAYERNUM]->appearance; + int playerAppearance = stats[HANDMAGIC_PLAYERNUM]->stat_appearance; if ( players[HANDMAGIC_PLAYERNUM]->entity->effectShapeshift != NOTHING ) { playerRace = static_cast(players[HANDMAGIC_PLAYERNUM]->entity->effectShapeshift); diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index f48052e1a..817c43eb4 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -4470,7 +4470,7 @@ void actMagicMissile(Entity* my) //TODO: Verify this function. // killed a monster with it's own spell with mirror reflection. steamAchievementEntity(parent, "BARONY_ACH_NARCISSIST"); } - if ( stats[parent->skill[2]] && stats[parent->skill[2]]->playerRace == RACE_INSECTOID && stats[parent->skill[2]]->appearance == 0 ) + if ( stats[parent->skill[2]] && stats[parent->skill[2]]->playerRace == RACE_INSECTOID && stats[parent->skill[2]]->stat_appearance == 0 ) { if ( !achievementObserver.playerAchievements[parent->skill[2]].gastricBypass ) { diff --git a/src/magic/castSpell.cpp b/src/magic/castSpell.cpp index b3d945052..51c0cc885 100644 --- a/src/magic/castSpell.cpp +++ b/src/magic/castSpell.cpp @@ -485,7 +485,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool if ( multiplayer != CLIENT ) { - if ( caster->behavior == &actPlayer && stat->playerRace == RACE_INSECTOID && stat->appearance == 0 ) + if ( caster->behavior == &actPlayer && stat->playerRace == RACE_INSECTOID && stat->stat_appearance == 0 ) { if ( !achievementObserver.playerAchievements[caster->skill[2]].gastricBypass ) { @@ -2761,7 +2761,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool } if ( magicIncreased && usingSpellbook && caster->behavior == &actPlayer ) { - if ( stats[caster->skill[2]] && stats[caster->skill[2]]->playerRace == RACE_INSECTOID && stats[caster->skill[2]]->appearance == 0 ) + if ( stats[caster->skill[2]] && stats[caster->skill[2]]->playerRace == RACE_INSECTOID && stats[caster->skill[2]]->stat_appearance == 0 ) { steamStatisticUpdateClient(caster->skill[2], STEAM_STAT_BOOKWORM, STEAM_STAT_INT, 1); } @@ -2777,7 +2777,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool { chance = 16; - if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->appearance == 0 ) + if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->stat_appearance == 0 ) { if ( spell->ID >= 30 && spell->ID < 60 ) { @@ -2850,7 +2850,7 @@ Entity* castSpell(Uint32 caster_uid, spell_t* spell, bool using_magicstaff, bool if ( stat->shield->status == BROKEN && player >= 0 ) { - if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->appearance == 0 ) + if ( caster && caster->behavior == &actPlayer && stat->playerRace == RACE_GOBLIN && stat->stat_appearance == 0 ) { steamStatisticUpdateClient(player, STEAM_STAT_DYSLEXIA, STEAM_STAT_INT, 1); } @@ -2957,23 +2957,23 @@ bool spellIsNaturallyLearnedByRaceOrClass(Entity& caster, Stat& stat, int spellI } // player races: - if ( stat.playerRace == RACE_INSECTOID && stat.appearance == 0 && (spellID == SPELL_DASH || spellID == SPELL_FLUTTER || spellID == SPELL_ACID_SPRAY) ) + if ( stat.playerRace == RACE_INSECTOID && stat.stat_appearance == 0 && (spellID == SPELL_DASH || spellID == SPELL_FLUTTER || spellID == SPELL_ACID_SPRAY) ) { return true; } - else if ( stat.playerRace == RACE_VAMPIRE && stat.appearance == 0 && (spellID == SPELL_LEVITATION || spellID == SPELL_BLEED) ) + else if ( stat.playerRace == RACE_VAMPIRE && stat.stat_appearance == 0 && (spellID == SPELL_LEVITATION || spellID == SPELL_BLEED) ) { return true; } - else if ( stat.playerRace == RACE_SUCCUBUS && stat.appearance == 0 && (spellID == SPELL_TELEPORTATION || spellID == SPELL_SELF_POLYMORPH) ) + else if ( stat.playerRace == RACE_SUCCUBUS && stat.stat_appearance == 0 && (spellID == SPELL_TELEPORTATION || spellID == SPELL_SELF_POLYMORPH) ) { return true; } - else if ( stat.playerRace == RACE_INCUBUS && stat.appearance == 0 && (spellID == SPELL_TELEPORTATION || spellID == SPELL_SHADOW_TAG) ) + else if ( stat.playerRace == RACE_INCUBUS && stat.stat_appearance == 0 && (spellID == SPELL_TELEPORTATION || spellID == SPELL_SHADOW_TAG) ) { return true; } - else if ( stat.playerRace == RACE_AUTOMATON && stat.appearance == 0 && (spellID == SPELL_SALVAGE) ) + else if ( stat.playerRace == RACE_AUTOMATON && stat.stat_appearance == 0 && (spellID == SPELL_SALVAGE) ) { return true; } diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index c24bf51cf..680f5e7ea 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -2303,7 +2303,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell createParticleDropRising(target, 593, 1.f); serverSpawnMiscParticles(target, PARTICLE_EFFECT_RISING_DROP, 593); - if ( targetStats->playerRace == RACE_HUMAN || (targetStats->playerRace != RACE_HUMAN && targetStats->appearance != 0) ) + if ( targetStats->playerRace == RACE_HUMAN || (targetStats->playerRace != RACE_HUMAN && targetStats->stat_appearance != 0) ) { int roll = (RACE_HUMAN + 1) + local_rng.rand() % 8; if ( target->effectPolymorph == 0 ) @@ -2319,7 +2319,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell target->effectPolymorph = target->getMonsterFromPlayerRace(roll); } } - else if ( (targetStats->playerRace != RACE_HUMAN && targetStats->appearance == 0) ) + else if ( (targetStats->playerRace != RACE_HUMAN && targetStats->stat_appearance == 0) ) { target->effectPolymorph = 100 + local_rng.rand() % NUMAPPEARANCES; } diff --git a/src/menu.cpp b/src/menu.cpp index e1e730b77..82f98f516 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -513,7 +513,7 @@ int isCharacterValidFromDLC(Stat& myStats, int characterClass) { return VALID_OK_CHARACTER; } - else if ( myStats.playerRace > RACE_HUMAN && myStats.appearance == 1 ) + else if ( myStats.playerRace > RACE_HUMAN && myStats.stat_appearance == 1 ) { return VALID_OK_CHARACTER; // aesthetic only option. } @@ -9901,7 +9901,7 @@ void doEndgame(bool saveHighscore, bool onServerDisconnect) { steamAchievement("BARONY_ACH_LIKE_CLOCKWORK"); } - if ( stats[i] && stats[i]->appearance == 0 ) + if ( stats[i] && stats[i]->stat_appearance == 0 ) { switch ( stats[i]->playerRace ) { @@ -10057,7 +10057,7 @@ void doEndgame(bool saveHighscore, bool onServerDisconnect) { players[c]->entity = nullptr; //TODO: PLAYERSWAP VERIFY. Need to do anything else? players[c]->cleanUpOnEntityRemoval(); stats[c]->sex = static_cast(0); - stats[c]->appearance = 0; + stats[c]->stat_appearance = 0; strcpy(stats[c]->name, ""); stats[c]->type = HUMAN; stats[c]->playerRace = RACE_HUMAN; @@ -11356,7 +11356,7 @@ void buttonOpenCharacterCreationWindow(button_t* my) // reset class loadout clientnum = 0; stats[0]->sex = static_cast(0 + local_rng.rand() % 2); - stats[0]->appearance = 0 + local_rng.rand() % NUMAPPEARANCES; + stats[0]->stat_appearance = 0 + local_rng.rand() % NUMAPPEARANCES; stats[0]->playerRace = RACE_HUMAN; strcpy(stats[0]->name, ""); stats[0]->type = HUMAN; @@ -11529,7 +11529,7 @@ void buttonRandomCharacter(button_t* my) client_classes[0] = local_rng.rand() % (NUMCLASSES); } } - stats[0]->appearance = local_rng.rand() % NUMAPPEARANCES; + stats[0]->stat_appearance = local_rng.rand() % NUMAPPEARANCES; } else { @@ -11538,13 +11538,13 @@ void buttonRandomCharacter(button_t* my) { client_classes[0] = CLASS_MONK + stats[0]->playerRace; // monster specific classes. } - stats[0]->appearance = 0; + stats[0]->stat_appearance = 0; } } else { stats[0]->playerRace = RACE_HUMAN; - stats[0]->appearance = local_rng.rand() % NUMAPPEARANCES; + stats[0]->stat_appearance = local_rng.rand() % NUMAPPEARANCES; } initClass(0); } @@ -11591,7 +11591,7 @@ bool replayLastCharacter(const int index, int multiplayer) { stats[index]->sex = static_cast(std::min(lastSex, (int)sex_t::FEMALE)); stats[index]->playerRace = std::min(std::max(static_cast(RACE_HUMAN), lastRace), static_cast(NUMPLAYABLERACES)); - stats[index]->appearance = lastAppearance; + stats[index]->stat_appearance = lastAppearance; client_classes[index] = std::min(std::max(0, lastClass), static_cast(CLASS_HUNTER)); switch ( isCharacterValidFromDLC(*stats[index], lastClass) ) diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 0c45a4621..daeaa01b9 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -88,7 +88,7 @@ void GameModeManager_t::Tutorial_t::startTutorial(std::string mapToSet) strcpy(stats[0]->name, "Player"); stats[0]->sex = static_cast(local_rng.rand() % 2); stats[0]->playerRace = RACE_HUMAN; - stats[0]->appearance = local_rng.rand() % NUMAPPEARANCES; + stats[0]->stat_appearance = local_rng.rand() % NUMAPPEARANCES; client_classes[0] = CLASS_WARRIOR; initClass(0); } @@ -15340,7 +15340,7 @@ void Compendium_t::Events_t::eventUpdateCodex(int playernum, const EventTags tag if ( findRaceTag != eventClassIds.end() ) { int race = RACE_HUMAN; - if ( stats[playernum]->playerRace > 0 && stats[playernum]->appearance == 0 ) + if ( stats[playernum]->playerRace > 0 && stats[playernum]->stat_appearance == 0 ) { race = stats[playernum]->playerRace; } diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 4b3a628c0..8c2522e05 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -416,7 +416,7 @@ class MonsterStatCustomManager strcpy(name, myStats->name); type = myStats->type; sex = myStats->sex; - appearance = myStats->appearance; + appearance = myStats->stat_appearance; HP = myStats->HP; MAXHP = myStats->MAXHP; OLDHP = HP; @@ -460,7 +460,7 @@ class MonsterStatCustomManager strcpy(myStats->name, name); myStats->type = static_cast(type); myStats->sex = static_cast(sex); - myStats->appearance = appearance; + myStats->stat_appearance = appearance; myStats->HP = HP; myStats->MAXHP = MAXHP; myStats->OLDHP = myStats->HP; diff --git a/src/monster_human.cpp b/src/monster_human.cpp index 95fe65579..4d1312be7 100644 --- a/src/monster_human.cpp +++ b/src/monster_human.cpp @@ -86,7 +86,7 @@ void initHuman(Entity* my, Stat* myStats) // red riding hood myStats->setAttribute("special_npc", "red riding hood"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 2; + myStats->stat_appearance = 2; myStats->sex = FEMALE; myStats->LVL = 1; myStats->HP = 10; @@ -111,7 +111,7 @@ void initHuman(Entity* my, Stat* myStats) // king arthur myStats->setAttribute("special_npc", "king arthur"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 0; + myStats->stat_appearance = 0; myStats->sex = MALE; myStats->LVL = 10; myStats->HP = 100; @@ -138,7 +138,7 @@ void initHuman(Entity* my, Stat* myStats) // merlin myStats->setAttribute("special_npc", "merlin"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 5; + myStats->stat_appearance = 5; myStats->sex = MALE; myStats->LVL = 10; myStats->HP = 60; @@ -162,7 +162,7 @@ void initHuman(Entity* my, Stat* myStats) // robin hood myStats->setAttribute("special_npc", "robin hood"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 1; + myStats->stat_appearance = 1; myStats->sex = MALE; myStats->LVL = 5; myStats->HP = 70; @@ -185,7 +185,7 @@ void initHuman(Entity* my, Stat* myStats) // conan myStats->setAttribute("special_npc", "conan the barbarian"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 7; + myStats->stat_appearance = 7; myStats->sex = MALE; myStats->LVL = 10; myStats->HP = 100; @@ -206,7 +206,7 @@ void initHuman(Entity* my, Stat* myStats) // othello myStats->setAttribute("special_npc", "othello"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 14; + myStats->stat_appearance = 14; myStats->sex = MALE; myStats->LVL = 10; myStats->HP = 50; @@ -230,7 +230,7 @@ void initHuman(Entity* my, Stat* myStats) // anansi myStats->setAttribute("special_npc", "anansi"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 15; + myStats->stat_appearance = 15; myStats->sex = MALE; myStats->LVL = 20; myStats->HP = 100; @@ -268,7 +268,7 @@ void initHuman(Entity* my, Stat* myStats) // oya myStats->setAttribute("special_npc", "oya"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 13; + myStats->stat_appearance = 13; myStats->sex = FEMALE; myStats->LVL = 20; myStats->HP = 100; @@ -289,7 +289,7 @@ void initHuman(Entity* my, Stat* myStats) // vishpala myStats->setAttribute("special_npc", "vishpala"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 17; + myStats->stat_appearance = 17; myStats->sex = FEMALE; myStats->LVL = 10; myStats->HP = 70; @@ -314,7 +314,7 @@ void initHuman(Entity* my, Stat* myStats) // kali myStats->setAttribute("special_npc", "kali"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 15; + myStats->stat_appearance = 15; myStats->sex = FEMALE; myStats->LVL = 20; myStats->HP = 200; @@ -343,7 +343,7 @@ void initHuman(Entity* my, Stat* myStats) // zap brigadier myStats->setAttribute("special_npc", "zap brigadier"); strcpy(myStats->name, MonsterData_t::getSpecialNPCName(*myStats).c_str()); - myStats->appearance = 1; + myStats->stat_appearance = 1; myStats->sex = static_cast(rng.rand() % 2); myStats->LVL = 10; myStats->HP = 100; @@ -920,21 +920,21 @@ void initHuman(Entity* my, Stat* myStats) } // set head model - if ( myStats->appearance < 5 ) + if ( myStats->stat_appearance < 5 ) { - my->sprite = 113 + 12 * myStats->sex + myStats->appearance; + my->sprite = 113 + 12 * myStats->sex + myStats->stat_appearance; } - else if ( myStats->appearance == 5 ) + else if ( myStats->stat_appearance == 5 ) { my->sprite = 332 + myStats->sex; } - else if ( myStats->appearance >= 6 && myStats->appearance < 12 ) + else if ( myStats->stat_appearance >= 6 && myStats->stat_appearance < 12 ) { - my->sprite = 341 + myStats->sex * 13 + myStats->appearance - 6; + my->sprite = 341 + myStats->sex * 13 + myStats->stat_appearance - 6; } - else if ( myStats->appearance >= 12 ) + else if ( myStats->stat_appearance >= 12 ) { - my->sprite = 367 + myStats->sex * 13 + myStats->appearance - 12; + my->sprite = 367 + myStats->sex * 13 + myStats->stat_appearance - 12; } else { @@ -1134,7 +1134,7 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( myStats->breastplate == nullptr ) { - switch ( myStats->appearance / 6 ) + switch ( myStats->stat_appearance / 6 ) { case 1: entity->sprite = 334 + 13 * myStats->sex; @@ -1210,7 +1210,7 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( myStats->shoes == nullptr ) { - switch ( myStats->appearance / 6 ) + switch ( myStats->stat_appearance / 6 ) { case 1: entity->sprite = 335 + 13 * myStats->sex; @@ -1291,7 +1291,7 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( myStats->shoes == nullptr ) { - switch ( myStats->appearance / 6 ) + switch ( myStats->stat_appearance / 6 ) { case 1: entity->sprite = 336 + 13 * myStats->sex; @@ -1373,7 +1373,7 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( myStats->gloves == nullptr ) { - switch ( myStats->appearance / 6 ) + switch ( myStats->stat_appearance / 6 ) { case 1: entity->sprite = 337 + 13 * myStats->sex; @@ -1481,7 +1481,7 @@ void humanMoveBodyparts(Entity* my, Stat* myStats, double dist) { if ( myStats->gloves == nullptr ) { - switch ( myStats->appearance / 6 ) + switch ( myStats->stat_appearance / 6 ) { case 1: entity->sprite = 338 + 13 * myStats->sex; diff --git a/src/net.cpp b/src/net.cpp index 2a83a5f26..59b729487 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1530,7 +1530,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool loc client_classes[c] = (int)SDLNet_Read32(&net_packet->data[36]); stats[c]->sex = static_cast((int)SDLNet_Read32(&net_packet->data[40])); Uint32 raceAndAppearance = (Uint32)SDLNet_Read32(&net_packet->data[44]); - stats[c]->appearance = (raceAndAppearance & 0xFF00) >> 8; + stats[c]->stat_appearance = (raceAndAppearance & 0xFF00) >> 8; stats[c]->playerRace = (raceAndAppearance & 0xFF); net_clients[c - 1].host = net_packet->address.host; net_clients[c - 1].port = net_packet->address.port; @@ -1549,7 +1549,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool loc net_packet->data[4] = c; // clientnum net_packet->data[5] = client_classes[c]; // class net_packet->data[6] = stats[c]->sex; // sex - net_packet->data[7] = (Uint8)stats[c]->appearance; // appearance + net_packet->data[7] = (Uint8)stats[c]->stat_appearance; // appearance net_packet->data[8] = (Uint8)stats[c]->playerRace; // player race stringCopy((char*)net_packet->data + 9, stats[c]->name, 32, sizeof(Stat::name)); // name net_packet->address.host = net_clients[x - 1].host; @@ -1573,7 +1573,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool loc net_packet->data[8 + x * chunk_size + 1] = lockedSlots[x]; // locked state net_packet->data[8 + x * chunk_size + 2] = client_classes[x]; // class net_packet->data[8 + x * chunk_size + 3] = stats[x]->sex; // sex - net_packet->data[8 + x * chunk_size + 4] = (Uint8)stats[x]->appearance; // appearance + net_packet->data[8 + x * chunk_size + 4] = (Uint8)stats[x]->stat_appearance; // appearance net_packet->data[8 + x * chunk_size + 5] = (Uint8)stats[x]->playerRace; // player race char shortname[32]; @@ -1614,7 +1614,7 @@ NetworkingLobbyJoinRequestResult lobbyPlayerJoinRequest(int& outResult, bool loc net_packet->data[8 + x * chunk_size + 1] = lockedSlots[x]; // locked state net_packet->data[8 + x * chunk_size + 2] = client_classes[x]; // class net_packet->data[8 + x * chunk_size + 3] = stats[x]->sex; // sex - net_packet->data[8 + x * chunk_size + 4] = (Uint8)stats[x]->appearance; // appearance + net_packet->data[8 + x * chunk_size + 4] = (Uint8)stats[x]->stat_appearance; // appearance net_packet->data[8 + x * chunk_size + 5] = (Uint8)stats[x]->playerRace; // player race char shortname[32]; @@ -4898,7 +4898,7 @@ static std::unordered_map clientPacketHandlers = { int victoryType; int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } @@ -5013,7 +5013,7 @@ static std::unordered_map clientPacketHandlers = { // mid game cutscene {'MIDG', [](){ int race = RACE_HUMAN; - if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->appearance == 0 ) + if ( stats[clientnum]->playerRace != RACE_HUMAN && stats[clientnum]->stat_appearance == 0 ) { race = stats[clientnum]->playerRace; } diff --git a/src/playfab.cpp b/src/playfab.cpp index a7a97d0f3..b678586e3 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -364,7 +364,7 @@ int parseOnlineHiscore(SaveGameInfo& info, Json::Value score) } else if ( s == "appearance" ) { - jsonValueToUint(score[m], s, player.stats.appearance); + jsonValueToUint(score[m], s, player.stats.statscore_appearance); } else if ( s == "race" ) { @@ -499,7 +499,7 @@ int parseOnlineHiscore(SaveGameInfo& info, Json::Value score) } } } - if ( player.race > 0 && player.stats.appearance != 0 ) + if ( player.race > 0 && player.stats.statscore_appearance != 0 ) { player.race = RACE_HUMAN; // set to human appearance for aesthetic scores } diff --git a/src/scores.cpp b/src/scores.cpp index e86e3724d..8a7af8235 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -95,7 +95,7 @@ score_t* scoreConstructor(int player) } score->stats->type = stats[player]->type; score->stats->sex = stats[player]->sex; - score->stats->appearance = stats[player]->appearance; + score->stats->stat_appearance = stats[player]->stat_appearance; score->stats->playerRace = stats[player]->playerRace; //score->stats->appearance |= stats[player]->playerRace << 8; strcpy(score->stats->name, stats[player]->name); @@ -444,7 +444,7 @@ void loadScore(score_t* score) } stats[0]->type = score->stats->type; stats[0]->sex = score->stats->sex; - stats[0]->appearance = score->stats->appearance; + stats[0]->stat_appearance = score->stats->stat_appearance; stats[0]->playerRace = score->stats->playerRace; //((stats[0]->appearance & 0xFF00) >> 8); //stats[0]->appearance = (stats[0]->appearance & 0xFF); @@ -688,7 +688,7 @@ void saveAllScores(const std::string& scoresfilename) fp->write(&score->stats->sex, sizeof(sex_t), 1); Uint32 raceAndAppearance = 0; raceAndAppearance |= (score->stats->playerRace << 8); - raceAndAppearance |= (score->stats->appearance); + raceAndAppearance |= (score->stats->stat_appearance); fp->write(&raceAndAppearance, sizeof(Uint32), 1); fp->write(score->stats->name, sizeof(char), 32); if ( versionNumber >= 412 ) @@ -1101,11 +1101,11 @@ void loadAllScores(const std::string& scoresfilename) fp->read(&score->conductIlliterate, sizeof(bool), 1); fp->read(&score->stats->type, sizeof(Monster), 1); fp->read(&score->stats->sex, sizeof(sex_t), 1); - fp->read(&score->stats->appearance, sizeof(Uint32), 1); + fp->read(&score->stats->stat_appearance, sizeof(Uint32), 1); if ( versionNumber >= 323 ) { - score->stats->playerRace = ((score->stats->appearance & 0xFF00) >> 8); - score->stats->appearance = (score->stats->appearance & 0xFF); + score->stats->playerRace = ((score->stats->stat_appearance & 0xFF00) >> 8); + score->stats->stat_appearance = (score->stats->stat_appearance & 0xFF); } fp->read(&score->stats->name, sizeof(char), 32); if ( versionNumber >= 412 ) @@ -1608,7 +1608,7 @@ int saveGameOld(int saveIndex) fp->write(&stats[player]->sex, sizeof(sex_t), 1); Uint32 raceAndAppearance = 0; raceAndAppearance |= (stats[player]->playerRace << 8); - raceAndAppearance |= (stats[player]->appearance); + raceAndAppearance |= (stats[player]->stat_appearance); fp->write(&raceAndAppearance, sizeof(Uint32), 1); fp->write(stats[player]->name, sizeof(char), 32); fp->write(&stats[player]->HP, sizeof(Sint32), 1); @@ -1967,7 +1967,7 @@ int saveGameOld(int saveIndex) // record follower stats fp->write(&followerStats->type, sizeof(Monster), 1); fp->write(&followerStats->sex, sizeof(sex_t), 1); - fp->write(&followerStats->appearance, sizeof(Uint32), 1); + fp->write(&followerStats->stat_appearance, sizeof(Uint32), 1); fp->write(followerStats->name, sizeof(char), 32); fp->write(&followerStats->HP, sizeof(Sint32), 1); fp->write(&followerStats->MAXHP, sizeof(Sint32), 1); @@ -2615,11 +2615,11 @@ int loadGameOld(int player, int saveIndex) } fp->read(&stats[player]->type, sizeof(Monster), 1); fp->read(&stats[player]->sex, sizeof(sex_t), 1); - fp->read(&stats[player]->appearance, sizeof(Uint32), 1); + fp->read(&stats[player]->stat_appearance, sizeof(Uint32), 1); if ( versionNumber >= 323 ) { - stats[player]->playerRace = ((stats[player]->appearance & 0xFF00) >> 8); - stats[player]->appearance = (stats[player]->appearance & 0xFF); + stats[player]->playerRace = ((stats[player]->stat_appearance & 0xFF00) >> 8); + stats[player]->stat_appearance = (stats[player]->stat_appearance & 0xFF); } fp->read(&stats[player]->name, sizeof(char), 32); fp->read(&stats[player]->HP, sizeof(Sint32), 1); @@ -3036,7 +3036,7 @@ list_t* loadGameFollowersOld(int saveIndex) // read follower attributes fp->read(&followerStats->type, sizeof(Monster), 1); fp->read(&followerStats->sex, sizeof(sex_t), 1); - fp->read(&followerStats->appearance, sizeof(Uint32), 1); + fp->read(&followerStats->stat_appearance, sizeof(Uint32), 1); fp->read(&followerStats->name, sizeof(char), 32); fp->read(&followerStats->HP, sizeof(Sint32), 1); fp->read(&followerStats->MAXHP, sizeof(Sint32), 1); @@ -3738,7 +3738,7 @@ void updateGameplayStatisticsInMainLoop() std::unordered_set bowList; std::unordered_set utilityBeltList; int badAndBeautiful = -1; - if ( stats[clientnum]->appearance == 0 && (stats[clientnum]->type == INCUBUS || stats[clientnum]->type == SUCCUBUS) ) + if ( stats[clientnum]->stat_appearance == 0 && (stats[clientnum]->type == INCUBUS || stats[clientnum]->type == SUCCUBUS) ) { if ( stats[clientnum]->playerRace == RACE_INCUBUS || stats[clientnum]->playerRace == RACE_SUCCUBUS ) { @@ -4075,7 +4075,7 @@ void updateAchievementBaitAndSwitch(int player, bool isTeleporting) return; } - if ( stats[player]->playerRace == RACE_SUCCUBUS && stats[player]->appearance != 0 ) + if ( stats[player]->playerRace == RACE_SUCCUBUS && stats[player]->stat_appearance != 0 ) { return; } @@ -5299,7 +5299,7 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev { if ( !client_disconnected[i] ) { - if ( stats[i] && stats[i]->playerRace != RACE_HUMAN && stats[i]->appearance == 0 ) + if ( stats[i] && stats[i]->playerRace != RACE_HUMAN && stats[i]->stat_appearance == 0 ) { races.insert(stats[i]->playerRace); } @@ -5773,7 +5773,7 @@ void SaveGameInfo::computeHash(const int playernum, Uint32& hash) { hash += (Uint32)((Uint32)stats->type << (shift % 32)); ++shift; hash += (Uint32)((Uint32)stats->sex << (shift % 32)); ++shift; - hash += (Uint32)((Uint32)stats->appearance << (shift % 32)); ++shift; + hash += (Uint32)((Uint32)stats->statscore_appearance << (shift % 32)); ++shift; hash += (Uint32)((Uint32)stats->HP << (shift % 32)); ++shift; hash += (Uint32)((Uint32)stats->maxHP << (shift % 32)); ++shift; @@ -6034,7 +6034,7 @@ int SaveGameInfo::populateFromSession(const int playernum) player.stats.name = stats[c]->name; player.stats.type = stats[c]->type; player.stats.sex = stats[c]->sex; - player.stats.appearance = stats[c]->appearance; + player.stats.statscore_appearance = stats[c]->stat_appearance; player.stats.HP = stats[c]->HP; player.stats.maxHP = stats[c]->MAXHP; player.stats.MP = stats[c]->MP; @@ -6189,7 +6189,7 @@ int SaveGameInfo::populateFromSession(const int playernum) stats.name = follower->name; stats.type = follower->type; stats.sex = follower->sex; - stats.appearance = follower->appearance; + stats.statscore_appearance = follower->stat_appearance; stats.HP = follower->HP; stats.maxHP = follower->MAXHP; stats.MP = follower->MP; @@ -6466,7 +6466,7 @@ std::string SaveGameInfo::serializeToOnlineHiscore(const int playernum, const in statsObj.AddMember("LVL", myStats.LVL, d.GetAllocator()); statsObj.AddMember("EXP", myStats.EXP, d.GetAllocator()); statsObj.AddMember("race", player.race, d.GetAllocator()); - statsObj.AddMember("appearance", myStats.appearance, d.GetAllocator()); + statsObj.AddMember("appearance", myStats.statscore_appearance, d.GetAllocator()); statsObj.AddMember("sex", myStats.sex, d.GetAllocator()); statsObj.AddMember("class", player.char_class, d.GetAllocator()); @@ -6721,7 +6721,7 @@ int loadGame(int player, const SaveGameInfo& info) { auto& p = info.players[player].stats; stringCopy(stats[statsPlayer]->name, p.name.c_str(), sizeof(Stat::name), p.name.size()); stats[statsPlayer]->sex = static_cast(p.sex); - stats[statsPlayer]->appearance = p.appearance; + stats[statsPlayer]->stat_appearance = p.statscore_appearance; stats[statsPlayer]->HP = p.HP; stats[statsPlayer]->MAXHP = p.maxHP; stats[statsPlayer]->MP = p.MP; @@ -7030,7 +7030,7 @@ list_t* loadGameFollowers(const SaveGameInfo& info) { sizeof(Stat::name), follower.name.size()); stats->type = (Monster)follower.type; stats->sex = (sex_t)follower.sex; - stats->appearance = follower.appearance; + stats->stat_appearance = follower.statscore_appearance; stats->HP = follower.HP; stats->MAXHP = follower.maxHP; stats->MP = follower.MP; @@ -7178,7 +7178,7 @@ int SaveGameInfo::Player::isCharacterValidFromDLC() { return VALID_OK_CHARACTER; } - else if ( this->race > RACE_HUMAN && this->stats.appearance == 1 ) + else if ( this->race > RACE_HUMAN && this->stats.statscore_appearance == 1 ) { return VALID_OK_CHARACTER; // aesthetic only option. } diff --git a/src/scores.hpp b/src/scores.hpp index a96bc55a2..67f89ab1c 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -509,7 +509,7 @@ struct SaveGameInfo { std::string name; Uint32 type = Monster::HUMAN; Uint32 sex = 0; - Uint32 appearance = 0; + Uint32 statscore_appearance = 0; int HP = 0; int maxHP = 0; int MP = 0; @@ -538,7 +538,7 @@ struct SaveGameInfo { fp->property("name", name); fp->property("type", type); fp->property("sex", sex); - fp->property("appearance", appearance); + fp->property("appearance", statscore_appearance); fp->property("HP", HP); fp->property("maxHP", maxHP); fp->property("MP", MP); diff --git a/src/stat.cpp b/src/stat.cpp index 34264bcd1..d807d55f0 100644 --- a/src/stat.cpp +++ b/src/stat.cpp @@ -561,7 +561,7 @@ Stat* Stat::copyStats() newStat->type = this->type; newStat->sex = this->sex; - newStat->appearance = this->appearance; + newStat->stat_appearance = this->stat_appearance; strcpy(newStat->name, this->name); strcpy(newStat->obituary, this->obituary); @@ -817,7 +817,7 @@ void Stat::printStats() { printlog("type = %d\n", this->type); printlog("sex = %d\n", this->sex); - printlog("appearance = %d\n", this->appearance); + printlog("appearance = %d\n", this->stat_appearance); printlog("name = \"%s\"\n", this->name); printlog("HP = %d\n", this->HP); printlog("MAXHP = %d\n", this->MAXHP); @@ -1490,7 +1490,7 @@ bool Stat::statusEffectRemovedByCureAilment(const int effect, Entity* my) case EFF_DRUNK: if ( this->type == GOATMAN || (my && my->behavior == &actPlayer - && playerRace == RACE_GOATMAN && appearance == 0) ) + && playerRace == RACE_GOATMAN && stat_appearance == 0) ) { return false; } diff --git a/src/stat.hpp b/src/stat.hpp index 16ca4f9a9..f051900b0 100644 --- a/src/stat.hpp +++ b/src/stat.hpp @@ -220,7 +220,7 @@ class Stat public: Monster type; sex_t sex; - Uint32 appearance; + Uint32 stat_appearance = 0; char name[128]; // uid of the entity which killed me via burning/poison (for rewarding XP to them) diff --git a/src/stat_editor.cpp b/src/stat_editor.cpp index 8f0232016..f72e09bed 100644 --- a/src/stat_editor.cpp +++ b/src/stat_editor.cpp @@ -38,7 +38,7 @@ Stat* Stat::copyStats() newStat->type = this->type; newStat->sex = this->sex; - newStat->appearance = this->appearance; + newStat->stat_appearance = this->stat_appearance; strcpy(newStat->name, this->name); strcpy(newStat->obituary, this->obituary); diff --git a/src/stat_shared.cpp b/src/stat_shared.cpp index a88ecfcf7..74414c110 100644 --- a/src/stat_shared.cpp +++ b/src/stat_shared.cpp @@ -53,7 +53,7 @@ Stat::Stat(Sint32 sprite) : this->killer_item = WOODEN_SHIELD; this->killer_name = ""; this->sex = static_cast(local_rng.rand() % 2); - this->appearance = 0; + this->stat_appearance = 0; this->HP = 10; this->MAXHP = 10; this->OLDHP = this->HP; @@ -165,7 +165,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + GNOME): stats->type = GNOME; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = 0; + stats->stat_appearance = 0; stats->HP = 50; stats->MAXHP = 50; stats->MP = 50; @@ -212,7 +212,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + DEVIL): stats->type = DEVIL; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); strcpy(stats->name, "Baphomet"); stats->inventory.first = nullptr; stats->inventory.last = nullptr; @@ -242,7 +242,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + LICH): stats->type = LICH; stats->sex = MALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); strcpy(stats->name, "Baron Herx"); stats->inventory.first = NULL; stats->inventory.last = NULL; @@ -268,7 +268,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SPIDER): stats->type = SPIDER; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 50; @@ -292,7 +292,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + GOBLIN): stats->type = GOBLIN; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 60; @@ -339,7 +339,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SHOPKEEPER): stats->type = SHOPKEEPER; stats->sex = MALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 300; @@ -374,7 +374,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + TROLL): stats->type = TROLL; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 100; @@ -404,7 +404,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + HUMAN): stats->type = HUMAN; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand() % 18; //NUMAPPEARANCES = 18 + stats->stat_appearance = local_rng.rand() % 18; //NUMAPPEARANCES = 18 stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 30; @@ -462,7 +462,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + KOBOLD): stats->type = KOBOLD; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = 0; + stats->stat_appearance = 0; stats->HP = 100; stats->MAXHP = stats->HP; @@ -510,7 +510,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SCARAB): stats->type = SCARAB; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 60; @@ -540,7 +540,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + CRYSTALGOLEM): stats->type = CRYSTALGOLEM; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = 0; + stats->stat_appearance = 0; stats->HP = 200; stats->MAXHP = stats->HP; @@ -579,7 +579,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + INCUBUS): stats->type = INCUBUS; stats->sex = sex_t::MALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->MAXHP = 280; @@ -631,7 +631,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + VAMPIRE): stats->type = VAMPIRE; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->HP = 400; @@ -688,7 +688,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) stats->type = SHADOW; stats->RANDOM_MAXHP = stats->RANDOM_HP; stats->RANDOM_MAXMP = stats->RANDOM_MP; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->sex = static_cast(local_rng.rand() % 2); @@ -730,7 +730,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + COCKATRICE): stats->type = COCKATRICE; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = 0; + stats->stat_appearance = 0; stats->HP = 500; stats->MAXHP = stats->HP; @@ -771,7 +771,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + INSECTOID): stats->type = INSECTOID; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->MAXHP = 130; @@ -826,7 +826,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + GOATMAN): stats->type = GOATMAN; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->MAXHP = 220; @@ -882,7 +882,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + AUTOMATON): stats->type = AUTOMATON; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = nullptr; stats->inventory.last = nullptr; stats->MAXHP = 115; @@ -915,7 +915,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + LICH_ICE): stats->type = LICH_ICE; stats->sex = FEMALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); strcpy(stats->name, "Erudyce"); stats->inventory.first = nullptr; stats->inventory.last = nullptr; @@ -943,7 +943,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + LICH_FIRE): stats->type = LICH_FIRE; stats->sex = MALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); strcpy(stats->name, "Orpheus"); stats->inventory.first = nullptr; stats->inventory.last = nullptr; @@ -971,7 +971,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SKELETON): stats->type = SKELETON; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->HP = 40; stats->MAXHP = 40; stats->MP = 30; @@ -1005,7 +1005,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + DEMON): stats->type = DEMON; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 120; @@ -1031,7 +1031,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + CREATURE_IMP): stats->type = CREATURE_IMP; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 80; @@ -1069,7 +1069,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + MINOTAUR): stats->type = MINOTAUR; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 400; @@ -1095,7 +1095,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SCORPION): stats->type = SCORPION; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 70; @@ -1123,7 +1123,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SLIME): stats->type = SLIME; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; //if ( stats->LVL >= 7 ) // blue slime @@ -1156,7 +1156,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + SUCCUBUS): stats->type = SUCCUBUS; stats->sex = FEMALE; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->HP = 60; stats->MAXHP = 60; stats->MP = 40; @@ -1183,7 +1183,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + RAT): stats->type = RAT; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 30; @@ -1209,7 +1209,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + GHOUL): stats->type = GHOUL; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 90; @@ -1295,7 +1295,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) break; case (1000 + MIMIC): stats->type = MIMIC; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->MAXHP = 90; @@ -1316,7 +1316,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case 188: case (1000 + BAT_SMALL): stats->type = BAT_SMALL; - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->MAXHP = 10; @@ -1337,7 +1337,7 @@ void setDefaultMonsterStats(Stat* stats, int sprite) case (1000 + BUGBEAR): stats->type = BUGBEAR; stats->sex = static_cast(local_rng.rand() % 2); - stats->appearance = local_rng.rand(); + stats->stat_appearance = local_rng.rand(); stats->inventory.first = NULL; stats->inventory.last = NULL; stats->HP = 130; diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 89fec2a92..2b924dab2 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -515,7 +515,7 @@ struct MPBarPaths_t }*/ return automatonSTBars; } - else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 ) + else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { return insectoidENBars; } @@ -7273,7 +7273,7 @@ void StatusEffectQueue_t::updateAllQueuedEffects() miscEffects[kEffectWaterWalking] = true; } if ( (stats[player]->amulet && stats[player]->amulet->type == AMULET_LIFESAVING) - || (((stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0) + || (((stats[player]->playerRace == RACE_SKELETON && stats[player]->stat_appearance == 0) || stats[player]->type == SKELETON) && stats[player]->MP >= 75) ) { miscEffects[kEffectLifesaving] = true; @@ -15363,7 +15363,7 @@ real_t getDisplayedMPRegen(Entity* my, Stat& myStats, Uint32* outColor, char buf } } } - else if ( myStats.playerRace == RACE_INSECTOID && myStats.appearance == 0 ) + else if ( myStats.playerRace == RACE_INSECTOID && myStats.stat_appearance == 0 ) { isInsectoid = true; if ( !(svFlags & SV_FLAG_HUNGER) ) @@ -16486,7 +16486,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element //#endif // !NDEBUG bool isAutomatonHTRegen = stats[player.playernum]->type == AUTOMATON; - bool isInsectoidENRegen = (stats[player.playernum]->playerRace == RACE_INSECTOID && stats[player.playernum]->appearance == 0); + bool isInsectoidENRegen = (stats[player.playernum]->playerRace == RACE_INSECTOID && stats[player.playernum]->stat_appearance == 0); auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str()); tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png"; @@ -17289,7 +17289,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; appearance = Language::get(4068); @@ -17314,7 +17314,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace); @@ -17391,7 +17391,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace); @@ -17567,7 +17567,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace); @@ -17645,7 +17645,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace); @@ -17864,7 +17864,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace); @@ -18576,7 +18576,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 }; Monster race = HUMAN; - if ( stats[player.playernum]->appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN ) + if ( stats[player.playernum]->stat_appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN ) { race = getMonsterFromPlayerRace(stats[player.playernum]->playerRace); } @@ -18733,7 +18733,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 }; Monster race = HUMAN; - if ( stats[player.playernum]->appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN ) + if ( stats[player.playernum]->stat_appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN ) { race = getMonsterFromPlayerRace(stats[player.playernum]->playerRace); } @@ -19056,7 +19056,7 @@ void Player::CharacterSheet_t::updateCharacterInfo() { if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN ) { - if ( stats[player.playernum]->appearance != 0 ) + if ( stats[player.playernum]->stat_appearance != 0 ) { aestheticOnly = true; appearance = Language::get(4068); @@ -19068,7 +19068,7 @@ void Player::CharacterSheet_t::updateCharacterInfo() capitalizeString(race); if ( type == HUMAN ) { - appearance = Language::get(20 + stats[player.playernum]->appearance % NUMAPPEARANCES); + appearance = Language::get(20 + stats[player.playernum]->stat_appearance % NUMAPPEARANCES); capitalizeString(appearance); } bool centerIconAndText = false; @@ -33325,7 +33325,7 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& { if ( tag == "CASTING_MP_REGEN" ) { - if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->appearance == 0 ) + if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->stat_appearance == 0 ) { return Language::get(4066); } @@ -33341,7 +33341,7 @@ std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& } else if ( tag == "CASTING_MP_REGEN_SKILL_MULTIPLIER" ) { - if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->appearance == 0 ) + if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->stat_appearance == 0 ) { return Language::get(4066); } diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index aeba24143..d348e0a06 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -10717,7 +10717,7 @@ namespace MainMenu { { if ( directConnect ) { - LastCreatedCharacterSettings.characterAppearance[LastCreatedCharacter::LASTCHAR_LAN_PERSONA_INDEX] = stats[clientnum]->appearance; + LastCreatedCharacterSettings.characterAppearance[LastCreatedCharacter::LASTCHAR_LAN_PERSONA_INDEX] = stats[clientnum]->stat_appearance; LastCreatedCharacterSettings.characterSex[LastCreatedCharacter::LASTCHAR_LAN_PERSONA_INDEX] = stats[clientnum]->sex; LastCreatedCharacterSettings.characterRace[LastCreatedCharacter::LASTCHAR_LAN_PERSONA_INDEX] = stats[clientnum]->playerRace; LastCreatedCharacterSettings.characterClass[LastCreatedCharacter::LASTCHAR_LAN_PERSONA_INDEX] = client_classes[clientnum]; @@ -10725,7 +10725,7 @@ namespace MainMenu { } else { - LastCreatedCharacterSettings.characterAppearance[LastCreatedCharacter::LASTCHAR_ONLINE_PERSONA_INDEX] = stats[clientnum]->appearance; + LastCreatedCharacterSettings.characterAppearance[LastCreatedCharacter::LASTCHAR_ONLINE_PERSONA_INDEX] = stats[clientnum]->stat_appearance; LastCreatedCharacterSettings.characterSex[LastCreatedCharacter::LASTCHAR_ONLINE_PERSONA_INDEX] = stats[clientnum]->sex; LastCreatedCharacterSettings.characterRace[LastCreatedCharacter::LASTCHAR_ONLINE_PERSONA_INDEX] = stats[clientnum]->playerRace; LastCreatedCharacterSettings.characterClass[LastCreatedCharacter::LASTCHAR_ONLINE_PERSONA_INDEX] = client_classes[clientnum]; @@ -10735,7 +10735,7 @@ namespace MainMenu { } else { - LastCreatedCharacterSettings.characterAppearance[index] = stats[index]->appearance; + LastCreatedCharacterSettings.characterAppearance[index] = stats[index]->stat_appearance; LastCreatedCharacterSettings.characterSex[index] = stats[index]->sex; LastCreatedCharacterSettings.characterRace[index] = stats[index]->playerRace; LastCreatedCharacterSettings.characterClass[index] = client_classes[index]; @@ -10763,7 +10763,7 @@ namespace MainMenu { SDLNet_Write32((Uint32)client_classes[player], &net_packet->data[37]); SDLNet_Write32((Uint32)stats[player]->sex, &net_packet->data[41]); Uint32 raceAndAppearance = - ((stats[player]->appearance & 0xff) << 8) | + ((stats[player]->stat_appearance & 0xff) << 8) | (stats[player]->playerRace & 0xff); SDLNet_Write32(raceAndAppearance, &net_packet->data[45]); @@ -11198,7 +11198,7 @@ namespace MainMenu { client_classes[player] = (int)SDLNet_Read32(&net_packet->data[37]); stats[player]->sex = static_cast((int)SDLNet_Read32(&net_packet->data[41])); Uint32 raceAndAppearance = SDLNet_Read32(&net_packet->data[45]); - stats[player]->appearance = (raceAndAppearance & 0xFF00) >> 8; + stats[player]->stat_appearance = (raceAndAppearance & 0xFF00) >> 8; stats[player]->playerRace = (raceAndAppearance & 0xFF); if (!loadingsavegame) { @@ -11561,7 +11561,7 @@ namespace MainMenu { client_disconnected[player] = false; client_classes[player] = net_packet->data[5]; stats[player]->sex = static_cast(net_packet->data[6]); - stats[player]->appearance = net_packet->data[7]; + stats[player]->stat_appearance = net_packet->data[7]; stats[player]->playerRace = net_packet->data[8]; stringCopy(stats[player]->name, (char*)(&net_packet->data[9]), sizeof(Stat::name), 32); @@ -11601,7 +11601,7 @@ namespace MainMenu { client_classes[player] = (int)SDLNet_Read32(&net_packet->data[37]); stats[player]->sex = static_cast((int)SDLNet_Read32(&net_packet->data[41])); Uint32 raceAndAppearance = SDLNet_Read32(&net_packet->data[45]); - stats[player]->appearance = (raceAndAppearance & 0xFF00) >> 8; + stats[player]->stat_appearance = (raceAndAppearance & 0xFF00) >> 8; stats[player]->playerRace = (raceAndAppearance & 0xFF); if (!loadingsavegame) { initClass(player); @@ -11730,7 +11730,7 @@ namespace MainMenu { stats[clientnum]->playerRace = gameModeManager.currentSession.challengeRun.race; if ( stats[clientnum]->playerRace != RACE_HUMAN ) { - stats[clientnum]->appearance = 0; + stats[clientnum]->stat_appearance = 0; } if ( stats[clientnum]->playerRace == RACE_INCUBUS ) { @@ -11934,7 +11934,7 @@ namespace MainMenu { printlog("connected to server.\n"); client_disconnected[clientnum] = false; if (!loadingsavegame) { - stats[clientnum]->appearance = stats[0]->appearance; + stats[clientnum]->stat_appearance = stats[0]->stat_appearance; } // now set up everybody else @@ -11948,7 +11948,7 @@ namespace MainMenu { playerSlotsLocked[c] = net_packet->data[8 + c * chunk_size + 1]; // locked state client_classes[c] = net_packet->data[8 + c * chunk_size + 2]; // class stats[c]->sex = static_cast(net_packet->data[8 + c * chunk_size + 3]); // sex - stats[c]->appearance = net_packet->data[8 + c * chunk_size + 4]; // appearance + stats[c]->stat_appearance = net_packet->data[8 + c * chunk_size + 4]; // appearance stats[c]->playerRace = net_packet->data[8 + c * chunk_size + 5]; // player race stringCopy(stats[c]->name, (char*)(net_packet->data + 8 + c * chunk_size + 6), sizeof(Stat::name), 32); // name @@ -12215,7 +12215,7 @@ namespace MainMenu { stringCopy((char*)net_packet->data + 4, stats[index]->name, 32, sizeof(Stat::name)); SDLNet_Write32((Uint32)client_classes[index], &net_packet->data[36]); SDLNet_Write32((Uint32)stats[index]->sex, &net_packet->data[40]); - Uint32 appearanceAndRace = ((Uint8)stats[index]->appearance << 8); // store in bits 8 - 15 + Uint32 appearanceAndRace = ((Uint8)stats[index]->stat_appearance << 8); // store in bits 8 - 15 appearanceAndRace |= (Uint8)stats[index]->playerRace; // store in bits 0 - 7 SDLNet_Write32(appearanceAndRace, &net_packet->data[44]); stringCopy((char*)net_packet->data + 48, VERSION, 8, sizeof(VERSION)); @@ -13092,7 +13092,7 @@ namespace MainMenu { void RaceDescriptions::update_details_text(Frame& card, void* stats) { Monster race = HUMAN; - if ( static_cast(stats)->appearance == 0 && static_cast(stats)->playerRace != RACE_HUMAN ) + if ( static_cast(stats)->stat_appearance == 0 && static_cast(stats)->playerRace != RACE_HUMAN ) { race = getMonsterFromPlayerRace(static_cast(stats)->playerRace); } @@ -13535,7 +13535,7 @@ namespace MainMenu { } if (stats[index]->playerRace == RACE_SUCCUBUS) { if (wasHuman) { - stats[index]->appearance = 0; + stats[index]->stat_appearance = 0; } stats[index]->sex = FEMALE; auto card = static_cast(frame->getParent()); assert(card); @@ -13551,7 +13551,7 @@ namespace MainMenu { } else if (stats[index]->playerRace == RACE_INCUBUS) { if (wasHuman) { - stats[index]->appearance = 0; + stats[index]->stat_appearance = 0; } stats[index]->sex = MALE; auto card = static_cast(frame->getParent()); assert(card); @@ -13567,15 +13567,15 @@ namespace MainMenu { } else if (stats[index]->playerRace == RACE_HUMAN) { auto appearances = frame->findFrame("appearances"); assert(appearances); - stats[index]->appearance = std::max(0, appearances->getSelection()); + stats[index]->stat_appearance = std::max(0, appearances->getSelection()); if (appearances) { - appearances->setSelection(stats[index]->appearance); + appearances->setSelection(stats[index]->stat_appearance); appearances->scrollToSelection(); } } else { if (wasHuman) { - stats[index]->appearance = 0; + stats[index]->stat_appearance = 0; } } if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { @@ -13662,7 +13662,7 @@ namespace MainMenu { if (incubus) { incubus->setPressed(true); } - if (client_classes[index] == CLASS_MESMER && stats[index]->appearance == 0) { + if (client_classes[index] == CLASS_MESMER && stats[index]->stat_appearance == 0) { if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { client_classes[index] = CLASS_PUNISHER; auto class_button = card->findButton("class"); @@ -13718,7 +13718,7 @@ namespace MainMenu { if (succubus) { succubus->setPressed(true); } - if (client_classes[index] == CLASS_PUNISHER && stats[index]->appearance == 0) { + if (client_classes[index] == CLASS_PUNISHER && stats[index]->stat_appearance == 0) { if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { client_classes[index] = CLASS_MESMER; auto class_button = card->findButton("class"); @@ -15337,7 +15337,7 @@ namespace MainMenu { static void (*back_fn)(int) = [](int index){ if (inputs.hasController(index)) { client_classes[index] = old_classes[index]; - stats[index]->appearance = old_appearances[index]; + stats[index]->stat_appearance = old_appearances[index]; stats[index]->playerRace = old_races[index]; stats[index]->sex = old_sexes[index]; stats[index]->clearStats(); @@ -15682,7 +15682,7 @@ namespace MainMenu { appearance_uparrow->setCallback([](Button& button){ auto card = static_cast(button.getParent()); auto appearances = card->findFrame("appearances"); assert(appearances); - int selection = (int)stats[button.getOwner()]->appearance - 1; + int selection = (int)stats[button.getOwner()]->stat_appearance - 1; if (selection < 0) { selection = num_appearances - 1; } @@ -15724,7 +15724,7 @@ namespace MainMenu { appearance_downarrow->setCallback([](Button& button){ auto card = static_cast(button.getParent()); auto appearances = card->findFrame("appearances"); assert(appearances); - int selection = (int)stats[button.getOwner()]->appearance + 1; + int selection = (int)stats[button.getOwner()]->stat_appearance + 1; if (selection >= num_appearances) { selection = 0; } @@ -15757,7 +15757,7 @@ namespace MainMenu { if (stats[index]->playerRace != RACE_HUMAN) { return; } - stats[index]->appearance = std::stoi(entry.name); + stats[index]->stat_appearance = std::stoi(entry.name); }; for (int c = 0; c < num_appearances; ++c) { @@ -15774,7 +15774,7 @@ namespace MainMenu { } }; //entry->selected = entry->click; - if (stats[index]->appearance == c && stats[index]->playerRace == RACE_HUMAN) { + if (stats[index]->stat_appearance == c && stats[index]->playerRace == RACE_HUMAN) { appearances->setSelection(c); appearances->scrollToSelection(); } @@ -15833,7 +15833,7 @@ namespace MainMenu { disable_abilities->setWidgetDown("show_race_info"); disable_abilities->setWidgetUp(Language::get(5369 + num_races - 1)); if (stats[index]->playerRace != RACE_HUMAN) { - disable_abilities->setPressed(stats[index]->appearance != 0); + disable_abilities->setPressed(stats[index]->stat_appearance != 0); } static auto disable_abilities_fn = [](Button& button, int index){ if ( gameModeManager.currentSession.challengeRun.isActive() @@ -15848,7 +15848,7 @@ namespace MainMenu { if (stats[index]->playerRace == RACE_HUMAN) { soundError(); } else { - stats[index]->appearance = button.isPressed() ? 1 : 0; + stats[index]->stat_appearance = button.isPressed() ? 1 : 0; auto check = isCharacterValidFromDLC(*stats[index], client_classes[index]); if (check != VALID_OK_CHARACTER) { // player tried to play a class they haven't unlocked for this race @@ -16756,7 +16756,7 @@ namespace MainMenu { if (isCharacterValidFromDLC(*stats[index], client_classes[index]) != VALID_OK_CHARACTER) { stats[index]->playerRace = RACE_HUMAN; stats[index]->sex = static_cast(RNG.getU8() % 2); - stats[index]->appearance = RNG.uniform(0, NUMAPPEARANCES - 1); + stats[index]->stat_appearance = RNG.uniform(0, NUMAPPEARANCES - 1); client_classes[index] = 0; bool challengeRunModified = false; @@ -16774,7 +16774,7 @@ namespace MainMenu { stats[index]->playerRace = gameModeManager.currentSession.challengeRun.race; if ( stats[index]->playerRace != RACE_HUMAN ) { - stats[index]->appearance = 0; + stats[index]->stat_appearance = 0; } if ( stats[index]->playerRace == RACE_INCUBUS ) { @@ -17093,7 +17093,7 @@ namespace MainMenu { soundActivate(); const int index = button.getOwner(); old_classes[index] = client_classes[index]; - old_appearances[index] = stats[index]->appearance; + old_appearances[index] = stats[index]->stat_appearance; old_races[index] = stats[index]->playerRace; old_sexes[index] = stats[index]->sex; characterCardRaceMenu(button.getOwner(), false, -1); @@ -17127,8 +17127,8 @@ namespace MainMenu { std::vector chances; chances.resize(RACE_INSECTOID + 1); auto oldRace = stats[index]->playerRace; - Uint32 oldAppearance = stats[index]->appearance; - stats[index]->appearance = 0; + Uint32 oldAppearance = stats[index]->stat_appearance; + stats[index]->stat_appearance = 0; bool chanceFound = false; for ( int race = RACE_HUMAN; race <= RACE_INSECTOID; ++race ) @@ -17141,7 +17141,7 @@ namespace MainMenu { chanceFound = true; } } - stats[index]->appearance = oldAppearance; + stats[index]->stat_appearance = oldAppearance; if ( !chanceFound ) { stats[index]->playerRace = RACE_HUMAN; @@ -17176,9 +17176,9 @@ namespace MainMenu { // choose a random appearance const int appearance_choice = RNG.uniform(0, NUMAPPEARANCES - 1); if (stats[index]->playerRace == RACE_HUMAN) { - stats[index]->appearance = appearance_choice; + stats[index]->stat_appearance = appearance_choice; } else { - stats[index]->appearance = 0; + stats[index]->stat_appearance = 0; } // select a random sex (unless you're a succubus or an incubus) @@ -18316,7 +18316,7 @@ namespace MainMenu { { stats[c]->playerRace = RACE_HUMAN; stats[c]->sex = static_cast(RNG.getU8() % 2); - stats[c]->appearance = RNG.uniform(0, NUMAPPEARANCES - 1); + stats[c]->stat_appearance = RNG.uniform(0, NUMAPPEARANCES - 1); client_classes[c] = 0; } @@ -18335,7 +18335,7 @@ namespace MainMenu { stats[c]->playerRace = gameModeManager.currentSession.challengeRun.race; if ( stats[c]->playerRace != RACE_HUMAN ) { - stats[c]->appearance = 0; + stats[c]->stat_appearance = 0; } if ( stats[c]->playerRace == RACE_INCUBUS ) { @@ -23179,7 +23179,7 @@ namespace MainMenu { monsterData.getAllyIconFromSprite(playerHeadSprite( (Monster)getMonsterFromPlayerRace(info.players[player].race), (sex_t)info.players[player].stats.sex, - (int)info.players[player].stats.appearance)); + (int)info.players[player].stats.statscore_appearance)); auto portrait = subframe->addImage( SDL_Rect{32, 24, 32, 32}, 0xffffffff, From ef3f9df64942c25ba339f3674f8f4d7d1bed1273 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Sun, 20 Oct 2024 01:46:03 +1100 Subject: [PATCH 209/244] * fix duplicate lang warning and username not being set on load for nx --- src/game.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game.cpp b/src/game.cpp index 7276de213..d9fb25ea9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -6853,7 +6853,6 @@ int main(int argc, char** argv) // load game config Input::defaultBindings(); - MainMenu::randomizeUsername(); MainMenu::settingsReset(); MainMenu::settingsApply(); bool load_successful = MainMenu::settingsLoad(); @@ -6912,6 +6911,8 @@ int main(int argc, char** argv) exit(c); } + MainMenu::randomizeUsername(); + // init message printlog("Barony version: %s\n", VERSION); char buffer[32]; From e04c1a49d4410f64eb550fa9b5efe7b23eb793ab Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 02:54:50 +1100 Subject: [PATCH 210/244] * bell dmg 80, new achievements --- src/actgeneral.cpp | 25 ++++++++++++++++++++++--- src/actmagictrap.cpp | 3 +++ src/entity.cpp | 16 ++++++++++++++++ src/entity.hpp | 1 + src/interface/interface.cpp | 7 +++++++ src/magic/magic.cpp | 14 +++++++++++++- src/main.cpp | 5 ++++- src/main.hpp | 2 +- src/mod_tools.cpp | 6 ++++++ src/scores.cpp | 24 ++++++++++++++++++++++++ src/scores.hpp | 18 ++++++++++++++---- src/steam.cpp | 37 +++++++++++++++++++++++++++++++++---- src/ui/MainMenu.cpp | 15 +++++++++++++++ 13 files changed, 159 insertions(+), 14 deletions(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index d30666dab..de1d90c00 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -661,6 +661,17 @@ bool Entity::isColliderWall() const return false; } +bool Entity::isColliderBreakableContainer() const +{ + if ( !isDamageableCollider() ) { return false; } + auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; + if ( colliderData.damageCalculationType.find("breakable") != std::string::npos ) + { + return true; + } + return false; +} + void Entity::colliderOnDestroy() { if ( multiplayer == CLIENT ) { return; } @@ -672,8 +683,7 @@ void Entity::colliderOnDestroy() killer = uidToEntity(colliderKillerUid); if ( killer ) { - auto& colliderData = EditorEntityData_t::colliderData[colliderDamageTypes]; - if ( colliderData.damageCalculationType.find("breakable") != std::string::npos ) + if ( isColliderBreakableContainer() ) { Compendium_t::Events_t::eventUpdateWorld(killer->skill[2], Compendium_t::CPDM_CONTAINER_BROKEN, "containers", 1); } @@ -3703,7 +3713,7 @@ int getBellDmgOnEntity(Entity* entity) return 0; } - int damage = 50; + int damage = 80; int trapResist = entity->getFollowerBonusTrapResist(); if ( trapResist != 0 ) { @@ -4233,6 +4243,7 @@ void actBell(Entity* my) if ( puller->behavior == &actPlayer ) { Compendium_t::Events_t::eventUpdateWorld(puller->skill[2], Compendium_t::CPDM_BELL_BROKEN, "bell", 1); + steamStatisticUpdateClient(puller->skill[2], STEAM_STAT_RUNG_OUT, STEAM_STAT_INT, 1); } } } @@ -4437,6 +4448,10 @@ void actBell(Entity* my) if ( puller->behavior == &actPlayer ) { messagePlayerMonsterEvent(puller->skill[2], makeColorRGB(0, 255, 0), *stats, Language::get(692), Language::get(697), MSG_COMBAT); + if ( stats->type == GNOME ) + { + steamAchievementClient(puller->skill[2], "BARONY_ACH_JUBBAITED"); + } } } } @@ -4875,6 +4890,7 @@ void actBell(Entity* my) messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); bellBreakBulb(my, false); + steamStatisticUpdateClient(BELL_LAST_TOUCHED_PLAYER, STEAM_STAT_RUNG_OUT, STEAM_STAT_INT, 1); } } else if ( BELL_CURRENT_EVENT == BELL_CLAPPER_BREAK ) @@ -4887,6 +4903,7 @@ void actBell(Entity* my) messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6279)); bellAttractMonsters(my); Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_CLAPPER_BROKEN, "bell", 1); + steamStatisticUpdateClient(BELL_LAST_TOUCHED_PLAYER, STEAM_STAT_RUNG_OUT, STEAM_STAT_INT, 1); } } else if ( BELL_CURRENT_EVENT == BELL_MONSTER ) @@ -4934,6 +4951,7 @@ void actBell(Entity* my) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); + steamStatisticUpdateClient(BELL_LAST_TOUCHED_PLAYER, STEAM_STAT_RUNG_OUT, STEAM_STAT_INT, 1); } bellBreakBulb(my, false); } @@ -5019,6 +5037,7 @@ void actBell(Entity* my) { messagePlayer(BELL_LAST_TOUCHED_PLAYER, MESSAGE_INTERACTION, Language::get(6275)); Compendium_t::Events_t::eventUpdateWorld(BELL_LAST_TOUCHED_PLAYER, Compendium_t::CPDM_BELL_BROKEN, "bell", 1); + steamStatisticUpdateClient(BELL_LAST_TOUCHED_PLAYER, STEAM_STAT_RUNG_OUT, STEAM_STAT_INT, 1); } bellBreakBulb(my, false); } diff --git a/src/actmagictrap.cpp b/src/actmagictrap.cpp index ca0a695ab..b139a23f5 100644 --- a/src/actmagictrap.cpp +++ b/src/actmagictrap.cpp @@ -616,6 +616,8 @@ void daedalusShrineInteract(Entity* my, Entity* touched) if ( touched->behavior == &actPlayer ) { messagePlayerColor(touched->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(768)); + steamAchievementClient(touched->skill[2], "BARONY_ACH_BULL_RUSH"); + Compendium_t::Events_t::eventUpdateWorld(touched->skill[2], Compendium_t::CPDM_DAED_SPEED_BUFFS, "daedalus", 1); } } } @@ -751,6 +753,7 @@ void Entity::actDaedalusShrine() if ( touched->behavior == &actPlayer ) { messagePlayerColor(touched->skill[2], MESSAGE_STATUS, makeColorRGB(0, 255, 0), Language::get(768)); + steamAchievementClient(touched->skill[2], "BARONY_ACH_BULL_RUSH"); Compendium_t::Events_t::eventUpdateWorld(touched->skill[2], Compendium_t::CPDM_DAED_SPEED_BUFFS, "daedalus", 1); } } diff --git a/src/entity.cpp b/src/entity.cpp index 103c0c285..b2618cb3b 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7917,6 +7917,10 @@ void Entity::attack(int pose, int charge, Entity* target) { spawnDamageGib(hit.entity, 0, DamageGib::DMG_MISS, DamageGibDisplayType::DMG_GIB_MISS, true); } + if ( player >= 0 && bat ) + { + steamStatisticUpdateClient(player, STEAM_STAT_PITCH_PERFECT, STEAM_STAT_INT, 1); + } if ( hit.entity->behavior == &actPlayer ) { @@ -8531,6 +8535,13 @@ void Entity::attack(int pose, int charge, Entity* target) { Compendium_t::Events_t::eventUpdateWorld(player, Compendium_t::CPDM_BARRIER_DESTROYED, "breakable barriers", 1); } + if ( behavior == &actPlayer ) + { + if ( hit.entity->isColliderBreakableContainer() ) + { + steamStatisticUpdateClient(player, STEAM_STAT_SMASH_MELEE, STEAM_STAT_INT, 1); + } + } } else if ( hit.entity->behavior == &::actFurniture ) { @@ -9586,6 +9597,11 @@ void Entity::attack(int pose, int charge, Entity* target) if ( player >= 0 && hit.entity->behavior == &actMonster ) { steamStatisticUpdateClient(player, STEAM_STAT_UNSTOPPABLE_FORCE, STEAM_STAT_INT, 1); + if ( armornum == 4 && hitstats->type == BUGBEAR + && (hitstats->defending || hit.entity->monsterAttack == MONSTER_POSE_BUGBEAR_SHIELD) ) + { + steamAchievementClient(player, "BARONY_ACH_BEAR_WITH_ME"); + } } } } diff --git a/src/entity.hpp b/src/entity.hpp index 9cafd9716..7d47ffaaf 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -1095,6 +1095,7 @@ class Entity bool isColliderDamageableByMagic() const; bool isColliderAttachableToBombs() const; bool isColliderWall() const; + bool isColliderBreakableContainer() const; void colliderOnDestroy(); int getColliderOnHitLangEntry() const; int getColliderOnBreakLangEntry() const; diff --git a/src/interface/interface.cpp b/src/interface/interface.cpp index e5e0c532f..e4071c65a 100644 --- a/src/interface/interface.cpp +++ b/src/interface/interface.cpp @@ -8146,6 +8146,13 @@ void GenericGUIMenu::alchemyCombinePotions() else { Compendium_t::Events_t::eventUpdate(gui_player, Compendium_t::CPDM_ALEMBIC_BREWED, TOOL_ALEMBIC, 1); + + if ( result != POTION_SICKNESS && result != POTION_WATER ) + { + achievementObserver.updatePlayerAchievement(gui_player, // clientnum intentional for to include splitscreen + AchievementObserver::BARONY_ACH_BY_THE_BOOK, + AchievementObserver::BY_THE_BOOK_BREW); + } } } diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index 680f5e7ea..c5dd02e1b 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -387,7 +387,7 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re { armornum = hitstats->pickRandomEquippedItem(&armor, true, false, true, true); } - else if ( !hitstats->defending && (local_rng.rand() % (4 + resistance) == 0) ) // 1 in 4 to corrode armor + if ( armornum != -1 && !hitstats->defending && (local_rng.rand() % (4 + resistance) == 0) ) // 1 in 4 to corrode armor { armornum = hitstats->pickRandomEquippedItem(&armor, true, false, false, false); } @@ -396,6 +396,18 @@ void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int re { hit.entity->degradeArmor(*hitstats, *armor, armornum); //messagePlayerColor(player, color, "Armor piece: %s", armor->getName()); + + if ( armor->status == BROKEN ) + { + if ( parent && parent->behavior == &actPlayer ) + { + if ( armornum == 4 && hitstats->type == BUGBEAR + && (hitstats->defending || hit.entity->monsterAttack == MONSTER_POSE_BUGBEAR_SHIELD) ) + { + steamAchievementClient(parent->skill[2], "BARONY_ACH_BEAR_WITH_ME"); + } + } + } } } } diff --git a/src/main.cpp b/src/main.cpp index 3bf7b3c22..a2f62a885 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -295,7 +295,10 @@ SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS] = { 52, STEAM_STAT_INT, "STAT_DAPPER_2"}, { 53, STEAM_STAT_INT, "STAT_DAPPER_3"}, { 54, STEAM_STAT_INT, "STAT_DAPPER"}, - { 55, STEAM_STAT_INT, "STAT_DUNGEONSEED" } + { 55, STEAM_STAT_INT, "STAT_DUNGEONSEED" }, + { 56, STEAM_STAT_INT, "STAT_PITCH_PERFECT" }, + { 57, STEAM_STAT_INT, "STAT_RUNG_OUT" }, + { 58, STEAM_STAT_INT, "STAT_SMASH_MELEE" } }; SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS] = diff --git a/src/main.hpp b/src/main.hpp index c5ccbc857..3c9b77431 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -890,7 +890,7 @@ extern bool initialized; //So that messagePlayer doesn't explode before the game void GO_SwapBuffers(SDL_Window* screen); -static const int NUM_STEAM_STATISTICS = 55; +static const int NUM_STEAM_STATISTICS = 58; extern SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS]; static const int NUM_GLOBAL_STEAM_STATISTICS = 66; extern SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS]; diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index daeaa01b9..46110018a 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -16012,6 +16012,7 @@ void Compendium_t::updateLorePointCounts() completed = 0; total = 0; + int seenEntries = 0; for ( auto& pair : CompendiumWorld_t::contents["default"] ) { if ( pair.first != "-" ) @@ -16033,11 +16034,16 @@ void Compendium_t::updateLorePointCounts() } } ++completed; + ++seenEntries; } } } total = std::max(1, total); CompendiumWorld_t::completionPercent = 100.0 * (completed / (real_t)total); + if ( seenEntries >= (total / 2) ) + { + steamAchievement("BARONY_ACH_ISEENTIT"); + } completed = 0; total = 0; diff --git a/src/scores.cpp b/src/scores.cpp index 8a7af8235..0f07fc92a 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -5101,6 +5101,26 @@ void AchievementObserver::updatePlayerAchievement(int player, Achievement achiev { switch ( achievement ) { + case BARONY_ACH_BY_THE_BOOK: + if ( player == clientnum ) + { + if ( achEvent == BY_THE_BOOK_COMPENDIUM_PAGE ) + { + playerAchievements[player].ticksByTheBookViewed = ticks; + } + else if ( achEvent == BY_THE_BOOK_BREW ) + { + if ( playerAchievements[player].ticksByTheBookViewed != 0 + && ticks > playerAchievements[player].ticksByTheBookViewed ) + { + if ( (ticks - playerAchievements[player].ticksByTheBookViewed) < TICKS_PER_SECOND * 10 ) + { + awardAchievement(player, achievement); + } + } + } + } + break; case BARONY_ACH_BACK_TO_BASICS: if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) { @@ -5414,6 +5434,7 @@ void AchievementObserver::clearPlayerAchievementData() playerAchievements[i].updatedBountyTargets = false; playerAchievements[i].wearingBountyHat = false; playerAchievements[i].totalKillsTickUpdate = false; + playerAchievements[i].ticksByTheBookViewed = 0; } } @@ -5485,6 +5506,9 @@ void AchievementObserver::awardAchievement(int player, int achievement) case BARONY_ACH_SPROUTS: steamAchievementClient(player, "BARONY_ACH_SPROUTS"); break; + case BARONY_ACH_BY_THE_BOOK: + steamAchievementClient(player, "BARONY_ACH_BY_THE_BOOK"); + break; default: messagePlayer(player, MESSAGE_DEBUG, "[WARNING]: Unhandled achievement: %d", achievement); break; diff --git a/src/scores.hpp b/src/scores.hpp index 67f89ab1c..990816650 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -112,7 +112,10 @@ enum SteamStatIndexes : int STEAM_STAT_DAPPER_2, STEAM_STAT_DAPPER_3, STEAM_STAT_DAPPER, - STEAM_STAT_DUNGEONSEED + STEAM_STAT_DUNGEONSEED, + STEAM_STAT_PITCH_PERFECT, + STEAM_STAT_RUNG_OUT, + STEAM_STAT_SMASH_MELEE }; enum SteamGlobalStatIndexes : int @@ -244,7 +247,10 @@ static const std::pair steamStatAchStringsAndMaxVals[] = std::make_pair("BARONY_ACH_NONE", 0xFFFFFFFF), // STEAM_STAT_DAPPER_2 std::make_pair("BARONY_ACH_NONE", 0xFFFFFFFF), // STEAM_STAT_DAPPER_3 std::make_pair("BARONY_ACH_DAPPER", 30), // STEAM_STAT_DAPPER - std::make_pair("BARONY_ACH_DUNGEONSEED", 12) // STEAM_STAT_DUNGEONSEED + std::make_pair("BARONY_ACH_DUNGEONSEED", 12), // STEAM_STAT_DUNGEONSEED + std::make_pair("BARONY_ACH_BAT1000", 81), // STEAM_STAT_PITCH_PERFECT + std::make_pair("BARONY_ACH_RUNG_OUT", 20), // STEAM_STAT_RUNG_OUT + std::make_pair("BARONY_ACH_SMASH_MELEE", 500) // STEAM_STAT_SMASH_MELEE }; typedef struct score_t @@ -719,7 +725,8 @@ class AchievementObserver BARONY_ACH_FAST_LEARNER, BARONY_ACH_MASTER, BARONY_ACH_DAPPER, - BARONY_ACH_SPROUTS + BARONY_ACH_SPROUTS, + BARONY_ACH_BY_THE_BOOK }; enum AchievementEvent : int { @@ -733,7 +740,9 @@ class AchievementObserver DIPLOMA_LEVEL_COMPLETE, BACK_TO_BASICS_LEVEL_COMPLETE, FAST_LEARNER_TIME_UPDATE, - DAPPER_EQUIPMENT_CHECK + DAPPER_EQUIPMENT_CHECK, + BY_THE_BOOK_COMPENDIUM_PAGE, + BY_THE_BOOK_BREW }; void updatePlayerAchievement(int player, Achievement achievement, AchievementEvent achEvent); bool bIsAchievementAllowedDuringTutorial(std::string achievementStr) @@ -806,6 +815,7 @@ class AchievementObserver int rollTheBones = 0; int trashCompactor = 0; bool totalKillsTickUpdate = false; + Uint32 ticksByTheBookViewed = 0; static bool allPlayersDeadEvent; std::pair realBoy; diff --git a/src/steam.cpp b/src/steam.cpp index ee2796878..713845f56 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -804,7 +804,11 @@ bool achievementUnlocked(const char* achName) void steamAchievement(const char* achName) { #ifdef DEBUG_ACHIEVEMENTS - //messagePlayer(clientnum, MESSAGE_DEBUG, "%s", achName); + static ConsoleVariable cvar_achievements_debug("/achievements_debug", false); + if ( *cvar_achievements_debug ) + { + messagePlayer(clientnum, MESSAGE_DEBUG, "%s", achName); + } #endif if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL ) @@ -843,7 +847,6 @@ void steamAchievement(const char* achName) { conductGameChallenges[CONDUCT_BOOTS_SPEED] = 1; // to cover bases when lich or devil dies as we can't remotely update this for clients. } - //messagePlayer(clientnum, "%s", achName); if ( !achievementUnlocked(achName) ) { @@ -1035,8 +1038,18 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) case STEAM_STAT_FASCIST: case STEAM_STAT_I_NEEDED_THAT: case STEAM_STAT_DUNGEONSEED: + case STEAM_STAT_RUNG_OUT: + g_SteamStats[statisticNum].m_iValue = + std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); + break; + case STEAM_STAT_PITCH_PERFECT: + indicateProgress = false; g_SteamStats[statisticNum].m_iValue = std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); + if ( g_SteamStats[statisticNum].m_iValue == steamStatAchStringsAndMaxVals[statisticNum].second ) + { + indicateProgress = true; + } break; case STEAM_STAT_ALTER_EGO: indicateProgress = false; @@ -1081,6 +1094,7 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) } break; case STEAM_STAT_SUPER_SHREDDER: + case STEAM_STAT_SMASH_MELEE: indicateProgress = false; g_SteamStats[statisticNum].m_iValue = std::min(g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); @@ -1227,6 +1241,14 @@ void steamStatisticUpdate(int statisticNum, ESteamStatTypes type, int value) { steamIndicateStatisticProgress(statisticNum, type); } +#ifdef DEBUG_ACHIEVEMENTS + static ConsoleVariable cvar_statistics_debug("/statistics_debug", false); + if ( *cvar_statistics_debug ) + { + messagePlayer(clientnum, MESSAGE_DEBUG, "%s: %d, %d", steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), + g_SteamStats[statisticNum].m_iValue, steamStatAchStringsAndMaxVals[statisticNum].second); + } +#endif } void steamStatisticUpdateClient(int player, int statisticNum, ESteamStatTypes type, int value) @@ -1371,6 +1393,7 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) case STEAM_STAT_MONARCH: case STEAM_STAT_RAGE_AGAINST: case STEAM_STAT_GUERILLA_RADIO: + case STEAM_STAT_RUNG_OUT: if ( !achievementUnlocked(steamStatAchStringsAndMaxVals[statisticNum].first.c_str()) ) { if ( iVal == 1 || (iVal > 0 && iVal % 4 == 0) ) @@ -1393,6 +1416,8 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) case STEAM_STAT_TRASH_COMPACTOR: case STEAM_STAT_TORCHERER: case STEAM_STAT_FIXER_UPPER: + case STEAM_STAT_SMASH_MELEE: + case STEAM_STAT_PITCH_PERFECT: // below are 1000 max value case STEAM_STAT_SUPER_SHREDDER: if ( !achievementUnlocked(steamStatAchStringsAndMaxVals[statisticNum].first.c_str()) ) @@ -1456,8 +1481,12 @@ void steamIndicateStatisticProgress(int statisticNum, ESteamStatTypes type) break; } #ifdef DEBUG_ACHIEVEMENTS - /*messagePlayer(clientnum, MESSAGE_DEBUG, "%s: %d, %d", steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), - iVal, steamStatAchStringsAndMaxVals[statisticNum].second);*/ + static ConsoleVariable cvar_statistics_indicate_debug("/statistics_indicate_debug", false); + if ( *cvar_statistics_indicate_debug ) + { + messagePlayer(clientnum, MESSAGE_DEBUG, "%s: %d, %d", steamStatAchStringsAndMaxVals[statisticNum].first.c_str(), + iVal, steamStatAchStringsAndMaxVals[statisticNum].second); + } #endif } #endif // !STEAMWORKS diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index d348e0a06..3073e182a 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -39070,6 +39070,9 @@ namespace MainMenu { { success = true; playSound(632 + local_rng.rand() % 2, 92); + + steamAchievement("BARONY_ACH_CURIOSITY"); + to_unlock->setText(""); auto* unlockStatus = compendium_current == "monsters" ? &Compendium_t::CompendiumMonsters_t::unlocks : (compendium_current == "world" ? &Compendium_t::CompendiumWorld_t::unlocks @@ -40228,6 +40231,18 @@ namespace MainMenu { widget.removeWidgetAction("MenuStart"); widget.addWidgetAction("MenuAlt1", "page_right_unlock_btn"); } + + if ( compendium_current == "codex" && compendium_contents_current[compendium_current] == "alchemy skill" ) + { + auto& unlockStatus = Compendium_t::CompendiumCodex_t::unlocks["alchemy skill"]; + if ( unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_VISITED + || unlockStatus == Compendium_t::CompendiumUnlockStatus::UNLOCKED_UNVISITED ) + { + achievementObserver.updatePlayerAchievement(clientnum, + AchievementObserver::BARONY_ACH_BY_THE_BOOK, + AchievementObserver::BY_THE_BOOK_COMPENDIUM_PAGE); + } + } }); tab_title = window->addField(tab->getName(), 32); From d60181b5832e7e014293dbff409100d641560055 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:17:07 +1100 Subject: [PATCH 211/244] * add limit to baphomet ally xp drops --- src/entity_editor.cpp | 1 + src/magic/actmagic.cpp | 49 ++++++++++++++++++++++++------------------ src/monster_devil.cpp | 2 ++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/entity_editor.cpp b/src/entity_editor.cpp index f33395882..53d9df963 100644 --- a/src/entity_editor.cpp +++ b/src/entity_editor.cpp @@ -92,6 +92,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli monsterPathBoundaryXEnd(skill[16]), monsterPathBoundaryYEnd(skill[17]), monsterStoreType(skill[18]), + monsterDevilNumSummons(skill[18]), monsterStrafeDirection(skill[39]), monsterPathCount(skill[38]), monsterAllyIndex(skill[42]), diff --git a/src/magic/actmagic.cpp b/src/magic/actmagic.cpp index 817c43eb4..12d549c98 100644 --- a/src/magic/actmagic.cpp +++ b/src/magic/actmagic.cpp @@ -5870,32 +5870,39 @@ void actParticleTimer(Entity* my) { if ( Stat* monsterStats = monster->getStats() ) { - if ( my->parent != 0 && uidToEntity(my->parent) ) + if ( my->parent != 0 ) { - if ( uidToEntity(my->parent)->getRace() == LICH_ICE ) + Entity* parent = uidToEntity(my->parent); + if ( parent ) { - //monsterStats->leader_uid = my->parent; - switch ( monsterStats->type ) + if ( parent->getRace() == LICH_ICE ) { - case AUTOMATON: - strcpy(monsterStats->name, "corrupted automaton"); - monsterStats->EFFECTS[EFF_CONFUSED] = true; - monsterStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1; - break; - default: - break; + //monsterStats->leader_uid = my->parent; + switch ( monsterStats->type ) + { + case AUTOMATON: + strcpy(monsterStats->name, "corrupted automaton"); + monsterStats->EFFECTS[EFF_CONFUSED] = true; + monsterStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1; + break; + default: + break; + } } - } - else if ( uidToEntity(my->parent)->getRace() == DEVIL ) - { - monsterStats->monsterNoDropItems = 1; - monsterStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] = 1; - monsterStats->MISC_FLAGS[STAT_FLAG_NO_DROP_ITEMS] = 1; - monsterStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; - if ( my->particleTimerVariable2 >= 0 - && players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity ) + else if ( parent->getRace() == DEVIL ) { - monster->monsterAcquireAttackTarget(*(players[my->particleTimerVariable2]->entity), MONSTER_STATE_ATTACK); + monsterStats->monsterNoDropItems = 1; + monsterStats->MISC_FLAGS[STAT_FLAG_DISABLE_MINIBOSS] = 1; + monsterStats->LVL = 5; + if ( parent->monsterDevilNumSummons <= 25 ) + { + monsterStats->MISC_FLAGS[STAT_FLAG_XP_PERCENT_AWARD] = 1; + } + if ( my->particleTimerVariable2 >= 0 + && players[my->particleTimerVariable2] && players[my->particleTimerVariable2]->entity ) + { + monster->monsterAcquireAttackTarget(*(players[my->particleTimerVariable2]->entity), MONSTER_STATE_ATTACK); + } } } } diff --git a/src/monster_devil.cpp b/src/monster_devil.cpp index 96ecff3ca..614d1eb32 100644 --- a/src/monster_devil.cpp +++ b/src/monster_devil.cpp @@ -693,6 +693,8 @@ bool Entity::devilSummonMonster(Entity* summonOnEntity, Monster creature, int ra timer->particleTimerVariable1 = creature; timer->particleTimerVariable2 = playerToTarget; serverSpawnMiscParticlesAtLocation(static_cast(chosen.first), static_cast(chosen.second), 0, PARTICLE_EFFECT_DEVIL_SUMMON_MONSTER, 174); + + monsterDevilNumSummons++; return true; } return false; From f984611fe9fc8cff10525e1e0a487db357fbd81c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:39:03 +1100 Subject: [PATCH 212/244] * mace slightly more effective breaking shields while defending --- src/entity.cpp | 7 ++++++- src/entity.hpp | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index b2618cb3b..471f05d2b 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -126,6 +126,7 @@ Entity::Entity(Sint32 in_sprite, Uint32 pos, list_t* entlist, list_t* creatureli monsterPathBoundaryXEnd(skill[16]), monsterPathBoundaryYEnd(skill[17]), monsterStoreType(skill[18]), + monsterDevilNumSummons(skill[18]), monsterStrafeDirection(skill[39]), monsterPathCount(skill[38]), monsterAllyIndex(skill[42]), @@ -8541,7 +8542,7 @@ void Entity::attack(int pose, int charge, Entity* target) { steamStatisticUpdateClient(player, STEAM_STAT_SMASH_MELEE, STEAM_STAT_INT, 1); } - } + } } else if ( hit.entity->behavior == &::actFurniture ) { @@ -9558,6 +9559,10 @@ void Entity::attack(int pose, int charge, Entity* target) { shieldDegradeChance += 10; } + if ( weaponskill == PRO_MACE ) + { + shieldDegradeChance *= 0.75; + } if ( hit.entity->behavior == &actPlayer ) { if ( itemCategory(hitstats->shield) == ARMOR ) diff --git a/src/entity.hpp b/src/entity.hpp index 7d47ffaaf..56a48ec15 100644 --- a/src/entity.hpp +++ b/src/entity.hpp @@ -234,6 +234,7 @@ class Entity Sint32& monsterPathBoundaryXEnd; //skill[16] Sint32& monsterPathBoundaryYEnd; //skill[17] Sint32& monsterStoreType; //skill[18] + Sint32& monsterDevilNumSummons; //skill[18] Sint32& monsterStrafeDirection; //skill[39] Sint32& monsterPathCount; //skill[38] real_t& monsterLookDir; //fskill[4] From 5ba162a9bcb24915fa24989dc35063df2d0f942c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:39:50 +1100 Subject: [PATCH 213/244] * kobold reduce idle noise freq, dont overlap --- src/actmonster.cpp | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/actmonster.cpp b/src/actmonster.cpp index 3fedeb612..4abf59668 100644 --- a/src/actmonster.cpp +++ b/src/actmonster.cpp @@ -5191,7 +5191,37 @@ void actMonster(Entity* my) if ( (local_rng.rand() % 3 == 0 && !my->monsterAllyGetPlayerLeader()) || (my->monsterAllyGetPlayerLeader() && local_rng.rand() % 8 == 0) || myStats->type == DUMMYBOT ) { // idle sounds. if player follower, reduce noise frequency by 66%. - MONSTER_SOUND = playSoundEntity(my, MONSTER_IDLESND + (local_rng.rand() % MONSTER_IDLEVAR), 128); + bool doIdleSound = true; +#ifdef USE_FMOD + if ( myStats->type == KOBOLD ) + { + doIdleSound = local_rng.rand() % 2 == 0; + for ( node_t* node = map.creatures->first; node && doIdleSound; node = node->next ) + { + Entity* entity = (Entity*)node->element; + if ( entity->behavior == &actMonster && entity->skill[19] == MONSTER_IDLESND ) // skill 19 is monster idle snd + { + if ( Stat* stats = entity->getStats() ) + { + if ( stats->monster_sound ) + { + bool playing; + stats->monster_sound->isPlaying(&playing); + if ( playing ) + { + doIdleSound = false; + break; + } + } + } + } + } + } +#endif + if ( doIdleSound ) + { + MONSTER_SOUND = playSoundEntity(my, MONSTER_IDLESND + (local_rng.rand() % MONSTER_IDLEVAR), 128); + } } } else From ace724a1d5a3ab545b8d0e18556e1da307271b82 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:40:09 +1100 Subject: [PATCH 214/244] * XP text in top bar can support more than 2 characters for XP text --- src/ui/GameUI.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index 2b924dab2..e5af3167d 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -3802,7 +3802,7 @@ void createXPBar(const int player) auto endCapRight = hud_t.xpFrame->addImage(endCapPos, 0xFFFFFFFF, "*#images/ui/HUD/xpbar/HUD_Bars_ExpCap2_00.png", "xp img endcap right"); endCapRight->ontop = true; - const int textWidth = 72; + const int textWidth = 120; auto font = "fonts/pixel_maz.ttf#32#2"; auto textStatic = hud_t.xpFrame->addField("xp text static", 16); textStatic->setText(Language::get(6106)); @@ -3810,7 +3810,7 @@ void createXPBar(const int player) textStatic->setSize(SDL_Rect{ pos.w / 2 - 4, 0, textWidth, pos.h }); // x - 4 to center the slash textStatic->setFont(font); textStatic->setVJustify(Field::justify_t::CENTER); - textStatic->setHJustify(Field::justify_t::LEFT); + textStatic->setHJustify(Field::justify_t::RIGHT); textStatic->setColor(makeColor( 255, 255, 255, 255)); auto text = hud_t.xpFrame->addField("xp text current", 16); @@ -28524,7 +28524,7 @@ void Player::HUD_t::updateXPBar() auto xpTextStatic = xpFrame->findField("xp text static"); SDL_Rect xpTextStaticPos = xpTextStatic->getSize(); - int offsetx = pos.w / 2 - xpTextStaticPos.w - 24; + int offsetx = pos.w / 2 - xpTextStaticPos.w - 24 - 4; if ( bCompactWidth ) { xpTextStatic->setDisabled(true); @@ -28547,7 +28547,12 @@ void Player::HUD_t::updateXPBar() } else { - xpTextPos.x = pos.w / 2 - (4 * 2) - xpTextPos.w + offsetx; + xpTextPos.x = xpTextStaticPos.x + xpTextStaticPos.w - xpTextPos.w; + if ( auto textGet = xpTextStatic->getTextObject() ) + { + xpTextPos.x -= (textGet->getWidth()); + xpTextPos.x -= 4; + } } xpText->setSize(xpTextPos); From 4f77249f4efc0779ac0aadf6236fc0f442edabf6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:40:35 +1100 Subject: [PATCH 215/244] * lang update --- lang/en.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en.txt b/lang/en.txt index 809240a5c..41da3db19 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6537,5 +6537,6 @@ Unlock Achievements to earn more!# 6307 This %s is unable to be enchanted.# 6308 Your %s loses some of its blessing.# +6309 You are blasted by a torrent!# 6350 END# From 97e445bd5440b830a4413e69dc82059604c39d66 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 03:48:30 +1100 Subject: [PATCH 216/244] * game version bump --- src/game.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game.hpp b/src/game.hpp index e250f175a..c59620892 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -25,9 +25,9 @@ // REMEMBER TO CHANGE THIS WITH EVERY NEW OFFICIAL VERSION!!! #ifdef NINTENDO -static const char VERSION[] = "v4.2.2"; +static const char VERSION[] = "v4.3.0"; #else -static const char VERSION[] = "v4.2.2"; +static const char VERSION[] = "v4.3.0"; #endif #define GAME_CODE From bf98737d9e9c6f6f28055ebb939b4d84f03fab4e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 04:26:10 +1100 Subject: [PATCH 217/244] * fix compile error fpermissive --- VS.2015/Barony/Barony.vcxproj | 2 ++ src/mod_tools.cpp | 6 +++--- src/mod_tools.hpp | 2 +- src/ui/MainMenu.cpp | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/VS.2015/Barony/Barony.vcxproj b/VS.2015/Barony/Barony.vcxproj index 92d61a85e..2afddd8e1 100644 --- a/VS.2015/Barony/Barony.vcxproj +++ b/VS.2015/Barony/Barony.vcxproj @@ -904,6 +904,8 @@ copy "$(TargetDir)$(TargetFileName)" "$(SolutionDir)EOS 4\barony_test.exe" $(IntDir)%(RelativeDir) /DBUILD_PR="$(BARONY_BUILD_PR)" /DBUILD_CC="$(BARONY_BUILD_CC)" /DBUILD_CS="$(BARONY_BUILD_CS)" /DBUILD_DE="$(BARONY_BUILD_DE)" /DBUILD_SA="$(BARONY_BUILD_SA)" /DBUILD_GSE="$(BARONY_BUILD_GSTATE)" /DBUILD_PFTID="$(BARONY_BUILD_PFTID)" /DBUILD_PFHID="$(BARONY_BUILD_PFHID)" %(AdditionalOptions) stdcpp17 + false + true Windows diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 46110018a..59388ff2c 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -15609,9 +15609,9 @@ void Compendium_t::readModelLimbsFromFile(std::string section) && d["map_tiles"]["mid"].IsArray() && d["map_tiles"]["top"].IsArray() ) { - auto& floor = d["map_tiles"]["floor"].GetArray(); - auto& mid = d["map_tiles"]["mid"].GetArray(); - auto& top = d["map_tiles"]["top"].GetArray(); + auto floor = d["map_tiles"]["floor"].GetArray(); + auto mid = d["map_tiles"]["mid"].GetArray(); + auto top = d["map_tiles"]["top"].GetArray(); w = d["map_tiles"]["width"].GetInt(); h = d["map_tiles"]["height"].GetInt(); if ( floor.Size() == mid.Size() && diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 8c2522e05..3f0880558 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -4130,7 +4130,7 @@ struct Compendium_t static void writeUnlocksSaveData(); static void readUnlocksSaveData(); - static const char* Compendium_t::getSkillStringForCompendium(const int skill) + static const char* getSkillStringForCompendium(const int skill) { switch ( skill ) { diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 3073e182a..2f3365039 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -34727,7 +34727,7 @@ namespace MainMenu { if ( auto txt = frame->findField("txt_1") ) { bool toggle = ticks % TICKS_PER_SECOND < TICKS_PER_SECOND / 2; - char* binding = "MenuPageRight"; + const char* binding = "MenuPageRight"; if ( input.input("MenuUp").isBindingUsingKeyboard() ) { binding = "MenuUp"; @@ -34757,7 +34757,7 @@ namespace MainMenu { } if ( auto txt = frame->findField("txt_2") ) { - char* binding = "MenuPageRightAlt"; + const char* binding = "MenuPageRightAlt"; if ( input.input("MenuRight").isBindingUsingKeyboard() ) { binding = "MenuRight"; From e18898c9d749bf43fc6f6467f49a0b22630a3d45 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 04:44:09 +1100 Subject: [PATCH 218/244] * cmakelists update --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 476da50c1..c8e3e3c91 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,8 @@ list(APPEND GAME_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/monster_shadow.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/monster_vampire.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/monster_mimic.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/monster_bugbear.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/monster_bat.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/shops.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/mechanisms.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/actgate.cpp" From 7d0b25709f109d4bdc1fcbaf1b4aa0763985abd3 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Mon, 21 Oct 2024 23:14:27 +1100 Subject: [PATCH 219/244] * compendium translations to their own files --- lang/compendium_lang/lang_codex.json | 1396 +++++++++++ lang/compendium_lang/lang_items.json | 685 ++++++ lang/compendium_lang/lang_magic.json | 171 ++ lang/compendium_lang/lang_monsters.json | 1049 +++++++++ lang/compendium_lang/lang_world.json | 2856 +++++++++++++++++++++++ src/init_game.cpp | 5 + src/interface/consolecommand.cpp | 6 + src/mod_tools.cpp | 884 ++++++- src/mod_tools.hpp | 15 +- src/ui/MainMenu.cpp | 7 + 10 files changed, 7007 insertions(+), 67 deletions(-) create mode 100644 lang/compendium_lang/lang_codex.json create mode 100644 lang/compendium_lang/lang_items.json create mode 100644 lang/compendium_lang/lang_magic.json create mode 100644 lang/compendium_lang/lang_monsters.json create mode 100644 lang/compendium_lang/lang_world.json diff --git a/lang/compendium_lang/lang_codex.json b/lang/compendium_lang/lang_codex.json new file mode 100644 index 000000000..50c301f28 --- /dev/null +++ b/lang/compendium_lang/lang_codex.json @@ -0,0 +1,1396 @@ +{ + "ac": { + "blurb": [ + "ARMOR CLASS (AC) reduces incoming physical", + "attack damage by 1 for each point, up to a", + "maximum of 75% of the total attack damage.", + "While blocking, AC can reduce", + "up to 100% physical damage." + ], + "details": [ + "Stacking as much AC as possible may", + "make an adventurer feel virtually", + "impervious to attack, which is certainly", + "a big advantage.", + "", + "However, the danger of most traps,", + "magic, and status effects remain", + "unimpeded by AC. Some enemy weapons", + "are also designed specifically to", + "penetrate armor.", + "", + "It is wise not to neglect AC, but", + "take care not to be overconfident!" + ], + "details_line_highlights": [] + }, + "alchemy skill": { + "blurb": [ + "With the aid of an alembic, alchemists can", + "brew many kinds of potions, such as the", + "unstable potions, which scale in power with", + "skill. Drinking and throwing potions also", + "practices the skill." + ], + "details": [ + "While some skill can be gained by", + "drinking and throwing potions, alchemy", + "skill is truly practiced using an", + "alembic.", + "", + "Combine any primary ingredient with a", + "secondary ingredient to form a recipe.", + "Each recipe learned improves skill.", + "", + "Keep empty bottles and tap sinks for", + "healthy supplies of water for use in", + "duplication, providing more chances", + "to solve problems with potions." + ], + "details_line_highlights": [] + }, + "appraisal skill": { + "blurb": [ + "While any adventurer can see what an item", + "is just by looking at it, APPRAISAL reveals", + "details, especially magical ones. The more", + "valuable the item, the more challenging it", + "is to appraise." + ], + "details": [ + "Appraising inexpensive items, like", + "cheese or leather items, is usually", + "great for untrained practice.", + "", + "A few classes start without the ability", + "to appraise, thanks to very poor", + "perception.", + "", + "Using unidentified potions or scrolls", + "may also yield an increase to", + "appraisal skill, up to Basic (20) skill.", + "", + "Glasses may also help." + ], + "details_line_highlights": [] + }, + "axe skill": { + "blurb": [ + "A hefty axe provides bonus ATK on wooden", + "objects, useful for breaking an obstacle in", + "an emergency such as a locked door or", + "stray mimic.", + "A legend in AXE SKILL always causes foes", + "to slow when dealing a legendary strike." + ], + "details": [ + "Axes are a reliable, if unspecialized", + "choice, with few enemies that can resist", + "its hefty blade substantially.", + "", + "Its hacking power provides an extra", + "point of guaranteed damage, making", + "it great for general slaying, and", + "chipping away at armored foes." + ], + "details_line_highlights": [] + }, + "backstabs": { + "blurb": [ + "To backstab an enemy, attack them in", + "melee from behind while they are unaware", + "of any threats, dealing greater damage.", + "If the enemy is slain with this strike,", + "it counts as an assassination!" + ], + "details": [ + "You can flee from a tougher fight,", + "and then return to your prey after", + "it has lost sight of you. Doing so gives", + "you another chance to deal backstab", + "damage and assassinate enemies", + "whose health pool might otherwise be", + "too large to cope with in a single bout.", + "", + "Consider using quickturn for the", + "fastest possible escape!" + ], + "details_line_highlights": [] + }, + "blocking": { + "blurb": [ + "When holding a shield or torch in their", + "off-hand, adventurers can deflect", + "incoming physical attacks, but the item", + "may be damaged. Higher skill improves", + "the amount of damage deflected." + ], + "details": [ + "While blocking, an adventurer can", + "fend off attacks from all sides.", + "If you decide you need to run, a", + "behind-the-back block can save your", + "life while you're retreating!", + "", + "While usually it's a good idea to", + "block every melee attack, continuing", + "an assault and soaking an unblocked", + "hit may result in less overall damage.", + "", + "Remember, AC doesn't block spell", + "damage, so only block against magic", + "if your shield has special properties." + ], + "details_line_highlights": [] + }, + "blocking skill": { + "blurb": [ + "BLOCKING SKILL provides additional armor", + "when wielding a shield, even when not", + "blocking. Legends in blocking skill can", + "deflect blows without degrading the item", + "used for blocking." + ], + "details": [ + "When seeking to improve your blocking", + "ability, the key is practice.", + "", + "Avoid quick kills and carry backup", + "shields so that you can block as many", + "times as possible before vanquishing a", + "foe, without endangering yourself.", + "", + "Training block skill with a torch can", + "only carry an adventurer to a max", + "skill of 40. After that, a shield is", + "needed to advance." + ], + "details_line_highlights": [] + }, + "casting skill": { + "blurb": [ + "Improving CASTING SKILL reduces the", + "chances that a spell fizzles, and increases", + "MP RGN. Upon reaching legendary casting,", + "an adventurer gains the forcebolt", + "spell as a cantrip." + ], + "details": [ + "When your casting skill is low, casting", + "low-cost spells will be the most", + "reliable path to growth.", + "", + "It may be tempting to cast a big spell,", + "but it may fizzle and rob you of many", + "chances to cast more, cheaper spells", + "by emptying your MP/EN/ST supply." + ], + "details_line_highlights": [] + }, + "chr": { + "blurb": [ + "Each point in CHARISMA (CHR) improves the", + "trading rates with shopkeepers. It also", + "improves follower speed, leadership skill,", + "and charisma-based spells." + ], + "details": [ + "Rather than putting one's self in", + "harm's way, an adventurer with high", + "CHARISMA has options for letting", + "somebody else do the dirty work.", + "", + "The patron stat of the leadership skill,", + "high CHR can offset inexperience,", + "empowering early access to follower", + "commands, and stronger charm spells.", + "", + "Also improving the trade skill, one", + "should not underestimate the value of", + "striking better deals with shops.", + "Gaining early access to a shopkeeper's", + "private stock can mean the difference", + "between a great adventure and a", + "desparate one." + ], + "details_line_highlights": [] + }, + "class": { + "blurb": [ + "A character's class determines their", + "starting skills, HP and MP/EN/ST,", + "equipment and spells, stats, and the", + "likelihood of each stat increasing", + "upon level up." + ], + "details": [ + "While most adventurers find success", + "playing to their strengths, some do", + "well by rounding out their weaknesses.", + "", + "Resourcefulness may be more", + "important than raw ability, depending", + "on the situation.", + "", + "When forming a party, work together", + "to select classes that will cover", + "one-another's weaknesses, or", + "enhance shared strengths." + ], + "details_line_highlights": [] + }, + "classes list": { + "blurb": [ + "Adventurers can adapt as they progress,", + "adopting skills and using equipment as", + "the dungeon's challenges may demand.", + "Still, class choice is essential to a", + "strong start, and ongoing growth." + ], + "details": [ + "" + ], + "details_line_highlights": [] + }, + "con": { + "blurb": [ + "Each point of CONSTITUTION (CON) adds", + "natural armor (AC). Those with high", + "constitution may recover from negative", + "status effects more quickly and restore", + "more health when healed." + ], + "details": [ + "Classes with strong CON growth may", + "find themselves able to make", + "equipment decisions based on benefits", + "without depending on AC added.", + "", + "Those with low AC may prefer to", + "prioritize armor and protection", + "effects.", + "", + "In both cases, AC remains important", + "for making sure you don't take too", + "much damage when struck by physical", + "attacks by foes." + ], + "details_line_highlights": [] + }, + "crits": { + "blurb": [ + "When making a melee attack, charge an", + "attack until your arm begins to shake with", + "tension. Releasing the attack at any point", + "after that will result in a CRITICAL STRIKE,", + "dealing additional damage!" + ], + "details": [ + "Your attack gains the benefit of a", + "critical strike the instant your", + "weapon begins to shake.", + "", + "Time your release to match your", + "approach to the target. This is even", + "more effective if the enemy's back", + "is turned.", + "", + "It's not usually a good idea to", + "continue performing critical strikes", + "in the middle of a fight." + ], + "details_line_highlights": [] + }, + "dex": { + "blurb": [ + "Each point of DEXTERITY (DEX) improves", + "an adventurer's potential movement speed,", + "and enhances ranged attack (ATK) power." + ], + "details": [ + "While every character may benefit", + "from increased movement speed,", + "characters who prefer to use ranged", + "attacks will benefit from DEX the most.", + "", + "Dexterity improves ranged attacks,", + "but improved speed also complements", + "skirmishing. A fast adventurer can", + "more easily stay out of enemy range,", + "outrun traps, and dodge projectiles", + "slung by foes.", + "", + "When you need to stay light on", + "your feet DEX is essential!" + ], + "details_line_highlights": [] + }, + "equipment degradation": { + "blurb": [ + "Most items have a chance to degrade upon", + "use, whether taking a hit in armor,", + "expending a charge on a magic item, or", + "attacking with a weapon. Degraded items", + "may perform worse than pristine ones." + ], + "details": [ + "Opportunities to repair equipment", + "may be rare or expensive, and", + "equipment breakage during the", + "middle of a fight can be deadly.", + "", + "Replacing your equipment regularly", + "with items that are in better condition", + "is usually a good idea.", + "", + "While tinkerers aren't blacksmiths,", + "those who are very skilled may be", + "able to patch up items for a cost.", + "Scrolls of repair, though rare, are", + "more reliable for most adventurers." + ], + "details_line_highlights": [] + }, + "flanking": { + "blurb": [ + "If an enemy is preoccupied in combat", + "against another foe, they have a harder", + "time defending themselves! Attack from", + "behind to gain additional flanking damage." + ], + "details": [ + "While a foe is focused on somebody", + "else, striking rapidly may be the", + "best way to bring down your target,", + "taking full advantage of extra", + "flanking damage.", + "", + "Fighting solo, don't expect many", + "opportunities to flank. But when", + "adventuring with allies or followers,", + "one member of the party may very", + "often be gaining flanking benefits!" + ], + "details_line_highlights": [] + }, + "hp": { + "blurb": [ + "HEALTH POINTS (HP) measure a denizen's", + "vitality, with each point of damage", + "removing 1 HP, and each point of healing", + "restoring 1 HP. A denizen is slain when", + "their HP is reduced to 0 or fewer." + ], + "details": [ + "While a denizen's maximum HP increases", + "substantially over the course of an", + "adventure as they level up, their HP", + "regeneration rate generally will not.", + "", + "As a result, it is important to continue", + "minimizing HP loss in encounters and", + "acquire means of HP restoration to", + "recover from larger scrapes." + ], + "details_line_highlights": [] + }, + "int": { + "blurb": [ + "Each point of INTELLIGENCE (INT) improves", + "magic power and MP regeneration by a", + "percentage. It also contributes to magic", + "skills, allowing more difficult spells to", + "be memorized." + ], + "details": [ + "INT is great for improving magic ability", + "generally, enhancing RGN and PWR, and", + "making it easier to learn spells.", + "", + "However, choosing items that directly", + "increase RGN or PWR may yield better", + "results than raw INT for focused", + "spellcasters." + ], + "details_line_highlights": [] + }, + "leadership skill": { + "blurb": [ + "An untrained adventurer can still gather", + "four followers, but they won't be able to", + "command them to do much. Higher levels of", + "LEADERSHIP SKILL grants access to many", + "useful commands." + ], + "details": [ + "Summons and recruited followers can", + "become quite strong if nurtured.", + "Allow them to get the last hit on", + "enemies, and you'll see their", + "confidence (and their levels) rise", + "dramatically!", + "", + "Outfit them with ranged equipment,", + "keep healing spells on hand, and", + "distract / confuse enemies to keep", + "your loyal followers safe!", + "", + "Wear a crown to inspire them further", + "with a variety of follower benefits." + ], + "details_line_highlights": [] + }, + "legendary strikes": { + "blurb": [ + "If an adventurer with LEGENDARY melee", + "skill holds a critical strike until it", + "releases on its own, they may gain an", + "additional status effect against their", + "victim. The effect depends on the weapon." + ], + "details": [ + "Gaining enough skill points to reach", + "legend in a melee skill may be tricky.", + "", + "If you are durable enough, avoid", + "critical strikes so you can get more", + "hits in, giving you more chances to", + "increase your weapon skill.", + "", + "If legendary skill is developed, it", + "may be available to save your life", + "against the dungeon's fiercest foes." + ], + "details_line_highlights": [] + }, + "leveling up": { + "blurb": [ + "Whenever a character gains 100 XP,", + "their level increases! When leveling", + "up, every character receives the", + "following: +5 Max HP, +5 Max MP,", + "and an increase to 3 different stats." + ], + "details": [ + "Each skill is based on a stat.", + "", + "If a stat increases upon leveling up,", + "and skills associated with that stat", + "have been practiced well, the increase", + "may be for +2 points instead of +1.", + "", + "Use skills aligned with your class' stat", + "growth to maximize your level-ups." + ], + "details_line_highlights": [] + }, + "mace skill": { + "blurb": [ + "Beating foes with a mace may break", + "their armor more than with other weapons.", + "A legend in MACE SKILL deals more damage", + "and has a chance to inflict paralyze with", + "a legendary strike." + ], + "details": [ + "Don't be afraid to pick up and use a", + "mace early in the dungeons, especially", + "if you're using ranged weapons, magic,", + "or swords.", + "", + "Its effectiveness at crushing undead", + "may very well help you live long", + "enough to become a master in other", + "arts." + ], + "details_line_highlights": [] + }, + "magic skill": { + "blurb": [ + "Improving MAGIC SKILL allows an", + "adventurer to learn more advanced spells", + "and increases PWR. A legend in magic", + "also learns the dominate spell." + ], + "details": [ + "A good spellcaster always stays in", + "practice!", + "", + "For those with MP/ST regeneration, a", + "full bar means you're losing out on", + "mana you could be recuperating.", + "", + "While it's good to save it for when you", + "need it, chip away at the top of a full", + "bar to keep training." + ], + "details_line_highlights": [] + }, + "melee": { + "blurb": [ + "Simply wield your fist or a melee weapon,", + "such as a mace, spear, sword, or axe, and", + "ATTACK! Fast strikes may allow an", + "adventurer to slay a foe before it can", + "respond with an attack of its own." + ], + "details": [ + "A simple tap is enough to perform a", + "full-strength melee strike.", + "", + "Only hold the attack longer if you", + "need to time your strike, or if", + "you're charging for a critical strike.", + "", + "When ambushing, adventurers often", + "have time to follow-up a critical strike", + "with one or two fast attacks. This is", + "enough to finish off a lot of weaker", + "enemies before they can even react." + ], + "details_line_highlights": [] + }, + "memorized": { + "blurb": [ + "Once memorized, spells can be readied", + "without the need for a spellbook.", + "", + "MP, EN, and ST are consumed over the", + "course of the cast time, which will be", + "lost if the spell fizzles." + ], + "details": [ + "From your spell list, use the quickcast", + "option to immediately start casting the", + "spell without the need to equip it or", + "put it in your hotbar. For spells you", + "only need occasionally, this makes it", + "easier to keep essential spells ready.", + "", + "Unless you want to use a book for", + "enhanced PWR, books can safely be", + "discarded or sold once a spell is", + "memorized. Be careful to avoid", + "reading cursed spellbooks, which will", + "result in forgetting a random spell!" + ], + "details_line_highlights": [] + }, + "missiles": { + "blurb": [ + "Slingshots, crossbows, and bows fire", + "MISSILES when used to attack. These", + "weapons may take time to load before the", + "attack fires. Crossbows and bows can be", + "loaded with special ammo when it is", + "held in the off-hand." + ], + "details": [ + "Augment crossbows and bows with", + "special ammo to increase your", + "damage output and inflict debilitating", + "status effects on dangerous targets.", + "", + "Enemies will always know where a", + "ranged weapon attack came from", + "when struck. Use this to lure your", + "foes where you want them.", + "", + "Many foes are resistant to small", + "projectiles, but your ability to", + "hit-and-run will give you the time you", + "need to chip their HP down." + ], + "details_line_highlights": [] + }, + "mp": { + "blurb": [ + "Casting memorized spells, or spells from", + "books, requires MAGIC POINTS (MP),", + "ENERGY (EN), or STEAM (ST). Casters usually", + "must have a quantity equal to or greater", + "than the cost of the spell in order to cast." + ], + "details": [ + "If MP seems too scarce, finish off foes", + "that are close to death without magic.", + "Save gold for shops, which may sell", + "potions that restore magic points.", + "", + "VAMPIRE BLOOD MAGIC:", + "Vampires can continue casting when", + "their MP is empty, consuming HP", + " instead.", + "Take care with channeled spells!", + "", + "AUTOMATON STEAM:", + "Steam regeneration depends on the", + "heat of the automaton's boiler. Some", + "fuels can cause it to super-heat,", + "resulting in rapid steam build-up. If a", + "boiler is cold, steam will depressurize", + "ultimately killing the automaton.", + "", + "INSECTOID ENERGY:", + "Insectoids metabolize energy into", + "magic, but can only eat so much.", + "Their energy pool is capped and", + "doesn't regenerate, but they can eat", + "all foods to rapidly regain energy", + "used in casting.", + "Magic potions and fruit juice also", + "restore lots of energy." + ], + "details_line_highlights": [ + {}, + {}, + {}, + {}, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "per": { + "blurb": [ + "PERCEPTION (PER) improves visibility in", + "darkness and increases appraisal.", + "", + "PER also increases the likelihood that", + "ranged attacks will pierce 50% of a", + "target's armor." + ], + "details": [ + "PER is essential for characters", + "focused on missile weapons, making", + "tough enemies more susceptible to", + "ranged attacks.", + "", + "Tinkering and appraise skills also", + "benefit from a strong perception stat.", + "", + "If you're struggling to appraise, a", + "little boost to PER can make all", + "the difference." + ], + "details_line_highlights": [] + }, + "polearm skill": { + "blurb": [ + "Polearms strikes are more accurate,", + "with reduced damage variance compared", + "to other melee skills of similar level.", + "A legend in POLEARM SKILL always causes", + "Knockback when dealing a legendary", + "strike." + ], + "details": [ + "The polearm is a popular choice for", + "many classes.", + "", + "Due to its general effectiveness", + "against human-sized targets, it's", + "generally an all-around great", + "starter weapon.", + "", + "Watch out for certain big monsters,", + "or tiny ones, who may be difficult to", + "skewer." + ], + "details_line_highlights": [] + }, + "pwr": { + "blurb": [ + "A denizen's MAGIC POWER (PWR) improves", + "the spells they cast by percentage,", + "increasing damage output, healing, or", + "status effect duration." + ], + "details": [ + "Some spells have a binary effect, and", + "as a result, PWR won't influence every", + "spell.", + "", + "But for damaging and healing spells,", + "PWR is extremely useful, particularly", + "when casting from a blessed book.", + "", + "Status effects may increase in", + "duration with improved PWR." + ], + "details_line_highlights": [] + }, + "races": { + "blurb": [ + "Humans aren't the only intelligent", + "creatures in the dungeons. RACES each", + "have their own friends and foes.", + "They also have starting abilities,", + "resistances, food preferences,", + "and penalties." + ], + "details": [ + "Most monster races can recruit their", + "own kind. Only humans and automatons", + "are welcome inside human-run shops.", + "", + "When selecting your race during", + "character creation, use the \"Show", + "Race Info\" button to reveal all of", + "your strengths, weaknesses, abilities,", + "and allegiances.", + "", + "To make the dungeons easier, pair", + "each race with a class that minimizes", + "the downsides, and doubles-down on", + "the benefits." + ], + "details_line_highlights": [] + }, + "ranged skill": { + "blurb": [ + "Pelting enemies with missiles may result", + "in penetrating their armor. Legendary", + "ranged skill allows an archer to use", + "missile weapons without degrading", + "their quality." + ], + "details": [ + "Using low-damage missile weapons like", + "the slingshot or short bow is a reliable", + "way to raise your skill, considering", + "how many shots it might take to bring", + "down an enemy.", + "", + "However, the dungeons often won't be", + "generous, with few safe spaces to run", + "and kite.", + "", + "Take care to ensure survival is your", + "top priority when training." + ], + "details_line_highlights": [] + }, + "res": { + "blurb": [ + "Magic damage ignores AC, but MAGIC RESIST", + "(RES) will reduce the amount of damage", + "taken by a percentage. The duration of", + "negative magic status effects is also", + "reduced." + ], + "details": [ + "Since AC won't reduce the damage", + "taken from spells, it's a good idea to", + "look for sources of magic resistance.", + "", + "Magic reflect completely negates", + "magic projectiles, but comes at a cost.", + "Resistance, by comparison, is passive", + "and reliable.", + "", + "Some races have a natural resistance", + "or vulnerability to magic attacks." + ], + "details_line_highlights": [] + }, + "rgn": { + "blurb": [ + "Most denizens regenerate over time,", + "restoring lost HP and MP. Increasing", + "REGENERATION (RGN) improves how fast", + "these are restored." + ], + "details": [ + "While HP and MP can be depleted very", + "rapidly, a little practice can help get", + "an adventurer comfortable with how", + "long they take to restore.", + "", + "Explore, manage inventory, and stay", + "away from threats when you are", + "depleted, allowing you to benefit fully", + "from RGN.", + "", + "Note that HP regen is disabled with", + "the hunger Game Setting turned off." + ], + "details_line_highlights": [] + }, + "skills": { + "blurb": [ + "Many actions that adventurers take will", + "be modified by the level of their SKILLS.", + "", + "As they continue practicing, skills", + "will increase organically, sometimes", + "unlocking entirely new capabilities." + ], + "details": [ + "A class' starting skills can be an", + "excellent guide to how to most", + "effectively succeed as that class.", + "", + "In some cases, classes have skills that", + "they lack the equipment to use, which", + "might motivate a hunt for certain", + "items.", + "", + "Every skill has a new capability that's", + "unlocked once the skill reaches", + "legendary status (100)." + ], + "details_line_highlights": [] + }, + "sneaking": { + "blurb": [ + "With nothing, or special ammo, held in the", + "off-hand, an adventurer can creep slowly,", + "making it harder for foes to detect them.", + "Careful SNEAKING also makes it easier to", + "see in the shadows." + ], + "details": [ + "When feeling constantly beset or", + "surprised by foes, consider how much", + "light sources are exposing you.", + "", + "When taking advantage of darkness", + "and sneaking, all but the most", + "specialized hunters have a hard", + "time finding a stealthy adventurer." + ], + "details_line_highlights": [] + }, + "spellbook casting": { + "blurb": [ + "Any adventurer can wield a spellbook", + "in their off-hand and attempt to cast", + "the spell within. Adventurers with high", + "INT will gain an additional PWR bonus", + "when casting from the book compared to", + "casting from memory." + ], + "details": [ + "Dedicated magicians may benefit from", + "keeping their favorite attack spell", + "at ready with a wielded spellbook.", + "", + "In addition to providing greater PWR,", + "another memorized spell can also be", + "readied, permitting immediate access", + "to multiple magic solutions.", + "", + "Off-hand spellbooks can be blessed", + "like any equipment, providing huge", + "bonuses to the book's PWR. Use an", + "enchanted feather to repair a", + "spellbook you depend on!" + ], + "details_line_highlights": [] + }, + "stats metastats": { + "blurb": [ + "Stats measure a character's capabilities", + "and are at the root of each skill. These", + "include STR, CON, DEX, INT, PER, CHR.", + "Metastats such as PWR, AC, RGN and ATK", + "are not raised directly by leveling up." + ], + "details": [ + "Each adventurer may find some stats", + "more essential than others. It's wise", + "to monitor your character sheet and", + "collect the kind of equipment that", + "benefits your most useful stats.", + "", + "Pay attention to the skill sheet where", + "the governing stat is shown. High stats", + "can sometimes compensate for poor", + "skill.", + "", + "Metastats are not based on level or", + "class but are instead a combination of", + "stats, skills, levels, and equipment." + ], + "details_line_highlights": [] + }, + "stealth skill": { + "blurb": [ + "High STEALTH SKILL makes it easier to hide", + "from foes and improves the damage caused", + "by backstabs and flanking strikes.", + "", + "Legends in stealth can become almost", + "invisible on a whim." + ], + "details": [ + "When creeping through the dungeons,", + "pause when you see a foe. If you take", + "an extra moment to let your enemy", + "look away, you can rush in for a", + "backstab, or even assassination!", + "", + "These actions have a higher likelihood", + "of increasing your stealth skill.", + "With dedication to melee ambushing", + "tactics, an adventurer can become", + "a legend in their stealth ability." + ], + "details_line_highlights": [] + }, + "str": { + "blurb": [ + "Each point of STRENGTH (STR) adds to", + "melee attack (ATK) power. Strong", + "adventurers can also carry heavier", + "equipment, and lug more in their", + "inventory, without being slowed." + ], + "details": [ + "A character who depends primarily on", + "melee attacks will always benefit", + "from more STR.", + "", + "Prioritize gear blessed with STR", + "bonuses to further enhance your", + "melee power." + ], + "details_line_highlights": [] + }, + "strafing": { + "blurb": [ + "When exploring, adventurers move", + "confidently forward at full speed,", + "but stepping backward or to the side", + "is slower. This is important to know", + "when dealing with threats you", + "don't want to be near." + ], + "details": [ + "While you might be tempted to back", + "away from an intimidating foe, or", + "sidestep away from a boulder, it is", + "much faster to turn and run!", + "", + "Many adventurers have been smashed", + "because they were caught staring,", + "at the source of their demise.", + "", + "Use the quickturn button if", + "you have trouble turning fast!" + ], + "details_line_highlights": [] + }, + "swimming skill": { + "blurb": [ + "Swimming isn't usually the most important", + "skill, but when you're fleeing from a", + "dangerous threat, it might just be the most", + "important skill of your life. Especially", + "useful in the swamps." + ], + "details": [ + "While the waterwalking provided by", + "the swimming skill's legendary bonus", + "can be useful in many situations, some", + "adventurers may find themselves", + "wishing they could take a quick dip to", + "put out burning flames.", + "", + "Consider your next soak carefully!" + ], + "details_line_highlights": [] + }, + "sword skill": { + "blurb": [ + "Slashing against foes with a sword", + "increases the chance they may bleed. A", + "legend in SWORD SKILL always causes bleed", + "when dealing a legendary strike." + ], + "details": [ + "Swords may seem weak at first, as", + "Skeletons are very resistant to the", + "sword's honed edge.", + "", + "As you encounter more foes, you'll", + "discover that some tough enemies are", + "quite vulnerable to the blade.", + "", + "Be patient and careful as a novice", + "swordsman." + ], + "details_line_highlights": [] + }, + "thrown": { + "blurb": [ + "Thrown weapon attacks pierce armor", + "and gain increased damage from DEX.", + "", + "Charging a THROWN ATTACK improves", + "its flight distance and damage", + "considerably." + ], + "details": [ + "Thrown weapon attacks can quickly", + "take down otherwise tough enemies,", + "with 50% reduction to target AC.", + "", + "Having to collect your items after,", + "or even during, the fight means you", + "must remain vigilant and careful with", + "your inventory.", + "", + "Prepare to disable enemies or sneak", + "away to make sure you can have your", + "bandolier full for an encounter." + ], + "details_line_highlights": [] + }, + "tinkering skill": { + "blurb": [ + "With only a lockpick, a talented tinkerer", + "can accomplish quite a lot. But the real", + "power of tinkering is accessed with a", + "tinkering kit. For every problem,", + "tinkerers have a mechanical solution." + ], + "details": [ + "After grasping the basics learned", + "from scrapping items at Expert (60)", + "skill level, raising tinkering can be a", + "challenge.", + "", + "Construct as many traps as possible", + "and let sentries get the final hit on", + "enemies to improve your odds of", + "increasing tinkering skill.", + "", + "When burdened with excess scrap, use", + "flame/freeze traps and beartraps as", + "a primary means of attack, which may", + "also grant increases to tinkering skill." + ], + "details_line_highlights": [] + }, + "trading skill": { + "blurb": [ + "TRADING SKILL helps an adventurer get", + "the most out of their gold, buying or", + "selling. Only a skilled tradesman can", + "convince them to part with a shopkeeper's", + "private reserve of consumables." + ], + "details": [ + "Trade skill will only increase when", + "purchasing items, with each item's gold", + "cost amounting to the chance that the", + "trade skill will increase (up to 100).", + "", + "In other words, items costing 100 gold", + "have a 100% chance of increasing skill.", + "", + "A shop's private stock is a great", + "source of items that have a good", + "likelihood of upping your skill while", + "also often being useful to keep on", + "hand. A new private stock item becomes", + "available every 10 skill levels up to 40." + ], + "details_line_highlights": [] + }, + "unarmed skill": { + "blurb": [ + "Those with high UNARMED SKILL may knock", + "back foes with critical strikes. Those", + "with legendary ability may briefly", + "paralyze a foe with a powerful", + "legendary strike from behind." + ], + "details": [ + "While overall dealing less damage, and", + "being less effective against many", + "targets, a tough fighter can quickly", + "increase their skill in unarmed", + "fighting.", + "", + "This is thanks in part to how many hits", + "it may take to bring down a foe.", + "", + "With great skill, an unarmed fighting", + "master is a serious threat with fewer", + "needs." + ], + "details_line_highlights": [] + }, + "wanted": { + "blurb": [ + "If an adventurer is WANTED by the", + "Merchants' Guild, shopkeepers will attack", + "them on sight. Qualities like race, certain", + "items, or magic effects might change how", + "shopkeepers feel about you as a customer." + ], + "details": [ + "Shopkeepers are tough enough that", + "attacking them is already a risky", + "prospect, but making all of them", + "hostile has great opportunity cost.", + "", + "With certain resources, it's possible to", + "get away with robbing shops only to", + "come back later as a customer.", + "", + "Use a bandit mask to mug shopkeepers", + "and remove it when finished. If your", + "real identity is wanted, use the", + "charm spell to convince shopkeepers", + "to forgive your crimes." + ], + "details_line_highlights": [] + }, + "weapon quality": { + "blurb": [ + "All weapons do less damage when they", + "are in poorer condition. High Tinkering", + "skill and Scrolls of Repair can be used", + "to improve weapon damage to its greatest", + "potential." + ], + "details": [ + "All melee and ranged weapons lose ATK", + "when they degrade in quality.", + "", + "Don't over-commit to a damaged", + "weapon.", + "", + "Even if it's blessed, you might find a", + "fresh, mundane weapon that", + "significantly outclasses a weapon", + "that's about to break.", + "", + "If you hope to repair your favorite", + "weapon, a mundane backup is a good", + "idea too." + ], + "details_line_highlights": [] + }, + "wgt": { + "blurb": [ + "Each item equipped or held in the inventory", + "adds WEIGHT (WGT). Higher STR allows an", + "adventurer to carry more WGT before it", + "slows them. The more WGT, the greater the", + "speed penalty." + ], + "details": [ + "Many adventurers make the mistake", + "of collecting every item they see", + "which leads to high weight and slow", + "movement speed.", + "", + "This invites death.", + "", + "It's usually wise to throw items away", + "you don't think you'll use in the next", + "next few minutes, just to stay light on", + "your feet." + ], + "details_line_highlights": [] + }, + "xp": { + "blurb": [ + "Whenever a character's skill increases,", + "they gain 2 EXPERIENCE POINTS (XP). Killing", + "monsters will also yield XP based on the", + "challenge of the kill.", + "", + "Gain 100 XP to level up!" + ], + "details": [ + "If you have the time, seek monsters", + "to slay and opportunities to practice", + "different skills to increase your XP.", + "Descending too quickly without", + "earning enough levels is likely", + "to end in death!", + "", + "Combat XP is split between all players", + "when adventuring together.", + "", + "If you already have a skill maxed out,", + "consider training a different skill so", + "you can keep gaining experience." + ], + "details_line_highlights": [] + } +} \ No newline at end of file diff --git a/lang/compendium_lang/lang_items.json b/lang/compendium_lang/lang_items.json new file mode 100644 index 000000000..f0ef387e2 --- /dev/null +++ b/lang/compendium_lang/lang_items.json @@ -0,0 +1,685 @@ +{ + "alembic": { + "blurb": [ + "The ALEMBIC allows an alchemist to extract", + "and combine two fluids together, usually", + "resulting in a different effect.", + "", + "Skilled brewers use it to make", + "more powerful concoctions." + ] + }, + "arbalest": { + "blurb": [ + "The arbalest is a specialized crossbow.", + "While it behaves similarly to its smaller", + "counterpart, it is slow to rearm, has a", + "short range, and massive recoil.", + "", + "A fine tradeoff for its great strength." + ] + }, + "axes": { + "blurb": [ + "What they lack in elegance, AXES make", + "up for by delivering blows that take the", + "enemy's breath away.", + "Ideal against lightly armored foes,", + "the heavy blade always bites, even", + "against heavily armored targets." + ] + }, + "backpacks": { + "blurb": [ + "Elder adventurers usually recommend", + "against hoarding items, as death catches", + "up quickly to those who move slowly.", + "", + "Some professions insist that they simply", + "need BACKPACKS for extra storage." + ] + }, + "beartrap": { + "blurb": [ + "A cruel and somewhat crude device,", + "the beartrap snaps shut on the legs of", + "enemies, dealing a vicious bite.", + "Most are also snared by the trap,", + "opening them to attack until", + "the jaws release." + ] + }, + "blood vials": { + "blurb": [ + "With the proper technique, BLOOD can be", + "stolen from victims before being soiled", + "by the filthy dungeon floors.", + "", + "Only the accursed and damned", + "would have any use for it." + ] + }, + "boomerang": { + "blurb": [ + "Tales tell of a tribe that crafts", + "a weapon that always returns to its", + " owner when thrown.", + "Once battered and old, it is offered", + "to the firstborn. Those who restore", + "a BOOMERANG are said to be worthy." + ] + }, + "booze": { + "blurb": [ + "Potent booze can lift the spirits a", + "little and offer strength of confidence.", + "However sloppy aim, dull wit, and", + "a weak stomach may follow.", + "One must decide if booze", + "is a vice or a treat." + ] + }, + "bows": { + "blurb": [ + "Beloved by skirmishers and knaves alike,", + "bows of all varieties can fire arrows", + "to slay and pester enemies from afar.", + "", + "Expert hunters and rogues", + "keep special ammunition on hand." + ] + }, + "chakrams and shurikens": { + "blurb": [ + "The fine material allows a sharper edge", + "to these weapons, and their spin allows", + "them to ricochet off walls.", + "Crafty adventurers can take out", + "enemies from around corners!", + "A thrower's dream." + ] + }, + "cheese and apples": { + "blurb": [ + "Basic MORSELS of simple food, a", + "hungry adventurer must eat several", + "to satiate themselves.", + "One might only brave spoiled food if", + "already desperately hungry, or perhaps", + "lacking a refined palate." + ] + }, + "cloaks & clothing": { + "blurb": [ + "Mundane CLOTHING isn't as practical", + "as armor when faced with violence.", + "", + "But enchanted garments can convince", + "adventurers to adopt more lightweight", + "attire for its other benefits." + ] + }, + "cream pie": { + "blurb": [ + "A dungeon delicacy, sometimes rigged", + "to splatter into a hungry adventurer's", + "face, blinding them for a time.", + "", + "If one is not peckish, a PIE could be thrown", + "into an enemy's face to befuddle them." + ] + }, + "crossbow": { + "blurb": [ + "The crossbow is mechanically assisted,", + "allowing quick reloading and firing at", + "the pull of a trigger.", + "While it's lacking in range", + "compared to other bows, it can still", + "fire special ammunition." + ] + }, + "crowns & headdresses": { + "blurb": [ + "Rare adornments, reserved for the", + "politically or magically attuned.", + "", + "While seemingly symbolic in nature,", + "powerful symbols can make", + "a powerful impact." + ] + }, + "crystal ammo": { + "blurb": [ + "When you're looking for the finest,", + "sharpest, and most lethal serrated", + "barbs to harm any enemy, you can't", + "do better than CRYSTAL AMMO.", + "", + "An uncomplicated, superior missile." + ] + }, + "crystal armor": { + "blurb": [ + "CRYSTAL so rare would normally never be", + "used as protective armor, especially", + "given its fragility.", + "", + "Only an incredible bounty of magical", + "crystal would justify using it this way." + ] + }, + "crystal shard": { + "blurb": [ + "While CRYSTAL SHARDS are dimmer than", + "torches, their light is only visible to the", + "wielder, and they never burn out.", + "", + "However they are fragile, and using them", + "to block will cause them to shatter quickly." + ] + }, + "death box": { + "blurb": [ + "A locker containing all the worldly", + "possessions of a dead adventurer.", + "", + "Carrying it slows the carrier by 25%.", + "Once broken open, all the contents", + "immediately spill out." + ] + }, + "defensive potions": { + "blurb": [ + "DEFENSIVE POTIONS provide a generally", + "beneficial effect to those who drink them.", + "", + "When allies are in need, chuck a", + "helpful bottle at their face!" + ] + }, + "djinnis brace": { + "blurb": [ + "A traveler once found a lamp of wishes,", + "occupied by a furious Djinni.", + "", + "The traveler tried to free the Djinni", + "but its shackles were invulnerable.", + "Only a wish could grant its freedom." + ] + }, + "dragons mail": { + "blurb": [ + "Legend tells of a rivalry between a", + "dragon and the traveler of myth. As they", + "competed for fame, they became friends.", + "", + "When the dragon later died, it", + "offered its scales to the traveler." + ] + }, + "dummybot": { + "blurb": [ + "Originally devised as a portable and", + "durable training companion, DUMMYBOTS", + "have been magically attuned to irritate", + "and keep the attention of foes.", + "While distracted, adventurers can", + "can flank with less risk." + ] + }, + "dyrnwyn": { + "blurb": [ + "Legend tells of a sword that served", + "high-born citizens with noble quests,", + "offering fire and divine retribution.", + "", + "Age and mixed purposes may have dulled", + "its blade, but it can be restored." + ] + }, + "empty bottle": { + "blurb": [ + "These brittle vessels are best", + "used to collect the contents of", + "fountains and water pumps.", + "", + "A budding brewer needs all the potable", + "liquid they can get to experiment with." + ] + }, + "face accessories": { + "blurb": [ + "Adventurers may complement their role", + "with the right piece of eyewear, or by", + "holding something in their mouth.", + "", + "These items confer a variety of benefits,", + "but for some, style is more important." + ] + }, + "fire and hunting ammo": { + "blurb": [ + "Some ammo leaves a lasting impression.", + "", + "FIRE AMMO lights up the room and sets", + "enemies ablaze, but the debilitating", + "HUNTING AMMO can devastate beasts", + "while slowing and poisoning them." + ] + }, + "fist weapons": { + "blurb": [ + "Perfect for those who fear the untimely", + "breakage of a weapon, fist weapons are", + "gauntlets that add a bit more", + "bite to each punch.", + "The well-trained can stun an", + "enemy with a surprise strike." + ] + }, + "gungnir": { + "blurb": [ + "Legends say this spear was forged", + "for the gods so they may always", + "land perfect strikes.", + "", + "The untrained are deadly with GUNGNIR.", + "This weapon is usually well-guarded." + ] + }, + "gyrobot": { + "blurb": [ + "Conceived first by tinkerers as a", + "simple companion toy, the GYROBOT'S", + "function has grown to include", + "many useful tasks.", + "A skilled tinkerer can command", + "their pet to assist in many ways." + ] + }, + "hats & hoods": { + "blurb": [ + "Many professions and groups use iconic", + "headwear that are practical and stylish.", + "", + "Perform and signal your role to others", + "by donning the perfect HAT or HOOD." + ] + }, + "iron armor": { + "blurb": [ + "While heavier than leather, it is twice", + "as effective in protecting the wearer.", + "One must be strong to move quickly in IRON.", + "", + "Hamlet was originally known", + "for its rich iron mines." + ] + }, + "khryselakatos": { + "blurb": [ + "Favored by a legendary hunter,", + "KHRYSELAKATOS offers enchanted arrows", + "from its unbreaking string.", + "", + "Myths tell of its owner who was", + "stripped of godhood and banished." + ] + }, + "leather armor": { + "blurb": [ + "Lightweight, inexpensive, and common, most", + "adventurers would much rather wear", + "LEATHER than no armor at all.", + "", + "A full outfit of leather can prevent", + "substantial damage from weaker foes." + ] + }, + "lockpick": { + "blurb": [ + "Those skilled in tinkering can use these", + "simple tools to unlock chests and doors,", + "disarm arrow traps, or adjust the", + "springs of tinkered traps.", + "", + "Handy for dealing with dangerous devices." + ] + }, + "maces": { + "blurb": [ + "With a head designed to shatter and", + "crush rigid targets, MACES are reliable", + "weapons that destroy armor and can knock", + "the most armored enemies senseless.", + "In the right hands, enemies may not", + "get the chance to fight back." + ] + }, + "mail & lore books": { + "blurb": [ + "While magic users may be disappointed", + "when they seek power within these mundane", + "pages, curious adventurers enjoy reading", + "the inked words for clues.", + "", + "Paper also burns well when fed to a boiler." + ] + }, + "masks & visors": { + "blurb": [ + "Some adventurers use MASKS to conceal", + "or communicate their identity", + "", + "For others, practical facewear provides", + "benefits that help fulfill their role." + ] + }, + "meat, fish, and bread": { + "blurb": [ + "Hearty portions, treasured by hungry", + "dungeon dwellers. If desperate for", + "food, one may be able to eat up", + "to two servings in and be satisfied,", + "being careful not to over-eat." + ] + }, + "mining pick": { + "blurb": [ + "These handy tools easily chop through", + "the earth, making shortcuts and providing", + "access to hidden treasures.", + "", + "MINING PICKS break quickly but can make", + "all the difference when lost or trapped." + ] + }, + "mirrors": { + "blurb": [ + "While there's no proof that mirrors can", + "steal souls, they do appear to reflect both", + "mundane and magical realities.", + "", + "Some who gaze upon them learn more", + "about themselves than they'd expect." + ] + }, + "mystic orb": { + "blurb": [ + "An enigmatic sphere that teems", + "with magical power.", + "A proper pedestal would allow", + "its great power to be tapped, though", + "somebody is likely to pay dearly", + "for such a priceless artifact." + ] + }, + "noisemaker": { + "blurb": [ + "The spring-powered mechanism uses a", + "crude but clever design to generate", + "fascinating noise that draws the", + "attention of nearby enemies.", + "Great for distracting foes and", + "luring them into an ambush." + ] + }, + "offensive potions": { + "blurb": [ + "Many potions are useful for harming", + "foes, often with devastating effects,", + "also dealing damage when thrown.", + "", + "Adventurers can drink OFFENSIVE POTIONS,", + "but it is usually done in ignorance." + ] + }, + "oracles treads": { + "blurb": [ + "A traveler of ancient myth is said", + "to have entered a realm of darkness", + "which concealed all evil.", + "Witnesses wrote of the traveler feeling", + "vibrations in the ground to seek danger,", + "quickly conquering the realm." + ] + }, + "parashu": { + "blurb": [ + "Many may call their weapon PARASHU,", + "but one such eternal axe can be", + "restored to legendary lethality.", + "", + "It was once a weapon of gods and", + "is now found in lowly places." + ] + }, + "polearms": { + "blurb": [ + "Polearms offer leverage and balance,", + "proving especially effective against", + "large and heavy beasts.", + "", + "In skilled hands, attacks with these", + "weapons can keep foes back." + ] + }, + "rocks & gems": { + "blurb": [ + "All stones can be handy when you need", + "something to throw at a trap or enemy.", + "", + "But precious gems are beloved by", + "shopkeepers and automatons alike,", + "or anyone tempted by greed." + ] + }, + "sentrybot and magic sentry": { + "blurb": [ + "Using arcane crystal to bestow", + "rudimentary intelligence, tinkerers", + "have outfitted tripods with", + "missile-slinging heads.", + "A sentry that is left alone to fire at", + "enemies can deal devastating damage." + ] + }, + "sharur": { + "blurb": [ + "Legend tells of a wise one who traveled", + "from kingdom to kingdom offering advice.", + "", + "For a time, the kingdom would be", + "guaranteed to prosper, but the", + "advisor would always be exiled." + ] + }, + "shields": { + "blurb": [ + "Shields provide passive armor to", + "adventurers wielding them, but actively", + "blocking provides much more defense.", + "", + "Some shields provide additional", + "benefits while blocking." + ] + }, + "silver and piercing ammo": { + "blurb": [ + "Some ammo is the right choice", + "depending on the foe.", + "", + "PIERCING AMMO can penetrate the", + "strongest armor. Undead and demonic", + "enemies are more afraid of SILVER AMMO." + ] + }, + "skeleton key": { + "blurb": [ + "A locksmith managed to devise this", + "style of key which allows its user to", + "operate mundane doors and chests.", + "", + "While it can't do everything a", + "lockpick can, it requires no training." + ] + }, + "slingshot": { + "blurb": [ + "The most basic of missile weapons,", + "slingshots may not seem like much.", + "", + "But when one needs to fight at a distance,", + "one could do worse than flinging bullets", + "and running away." + ] + }, + "sphinxs casque": { + "blurb": [ + "Legend tells of a traveler who met", + "a Sphinx with an impossible riddle.", + "", + "The traveler thought for a moment, and", + "responded with a question the that", + "the Sphinx could not answer." + ] + }, + "steel armor": { + "blurb": [ + "Stronger than iron, and only a little", + "heavier, one requires luck or wealth to", + "obtain and wear superior STEEL armor.", + "", + "The craft of STEEL is known to few smiths." + ] + }, + "swift and springshot ammo": { + "blurb": [ + "Turning the enemy into a pincushion", + "before they can get close is a", + "priority for many archers.", + "Lightweight SWIFT AMMO is faster to load,", + "while SPRINGSHOT AMMO is armed by", + "mechanists for powerful knockback." + ] + }, + "swords": { + "blurb": [ + "Deadly against soft targets, SWORDS are", + "ideal for one grisly task: cutting flesh.", + "Rigid, or tough hides may repel a slash,", + "but a masterful cut still draws blood." + ] + }, + "teleportation trap": { + "blurb": [ + "Like other tinkered traps, a lockpick", + "can make this ignore or detonate", + "on friendly targets.", + "But it's also configurable as a", + "TELEPORTATION destination, allowing", + "for controlled instant travel." + ] + }, + "tin and tin opener": { + "blurb": [ + "Tins hide away cooked, chopped vegetables", + "and meats of various kinds, offering an", + "invigorating and very filling meal.", + "", + "However, a tin opener is required, and", + "the grease inside can be a nuisance." + ] + }, + "tinkered traps": { + "blurb": [ + "TINKERED TRAPS can be deployed", + "on floors, walls, doors, and chests.", + "When disturbed, they detonate,", + "delivering their payload effect.", + "Many tinkerers rearm the spent", + "detonator to save a little scrap." + ] + }, + "tinkering kit": { + "blurb": [ + "The TINKERING KIT can break down", + "most gear into reusable parts.", + "", + "A skilled tinkerer can recombine them", + "into useful machines, each more efficient", + "when made by more skilled hands." + ] + }, + "tomahawks & daggers": { + "blurb": [ + "Weapons built especially for throwing,", + "these fly further and strike harder when", + "a bit more effort is put into each throw.", + "", + "Heavier than missile projectiles,", + "they also excel at penetrating armor." + ] + }, + "tomalley": { + "blurb": [ + "TOMALLEY, not to be confused with", + "a stuffed cornmeal snack, is a sludge", + "that serves as an invertebrate's liver.", + "Dungeon bugs often possess a small", + "amount of meat covered with", + "this edible substance." + ] + }, + "torch and lantern": { + "blurb": [ + "When wielded, these provide light.", + "Raise a TORCH or LANTERN to see a", + "little further, or block attacks!", + "", + "TORCHES & LANTERS slowly", + "degrade as they burn." + ] + }, + "towel": { + "blurb": [ + "While not seeming like much on its own,", + "in the right situation, a TOWEL can", + "be a lifesaver.", + "Towels make fine bandages", + "and clean up messes that might", + "ruin your next fight." + ] + }, + "water": { + "blurb": [ + "Fresh, clean WATER is a luxury", + "in the dungeons. It can satisfy an empty", + "stomach for a little while, and the brewer's", + "supply depends upon it.", + "It's also handy for washing", + "away certain magic effects." + ] + }, + "whip": { + "blurb": [ + "The specialty weapon of the Punisher,", + "skilled users can grasp weapons from", + "unwary hands with the whip.", + "", + "While it is slower to strike, it can", + "lash beyond the reach of other weapons." + ] + }, + "wraiths gown": { + "blurb": [ + "Seeking to master death, the traveler", + "of myth entered the Underworld.", + "There resided a spirit who longed for life.", + "", + "The traveler carried the spirit out,", + "but only its clothes crossed over." + ] + } +} \ No newline at end of file diff --git a/lang/compendium_lang/lang_magic.json b/lang/compendium_lang/lang_magic.json new file mode 100644 index 000000000..b143f3973 --- /dev/null +++ b/lang/compendium_lang/lang_magic.json @@ -0,0 +1,171 @@ +{ + "blessings": { + "blurb": [ + "In war, or in adventuring parties, those", + "who offer BLESSINGS are in high demand.", + "They benefit both the thaumaturgist, plus", + "nearby followers and allies.", + "", + "Some blessings have lasting effects." + ] + }, + "contravention": { + "blurb": [ + "While most magic could be described as", + "breaking the rules of reality, the study", + "of CONTRAVENTION allows sorcerors to", + "change how rules effect one's self.", + "This requires ongoing magic energy to", + "sustain, but can be disabled at will." + ] + }, + "daimonia": { + "blurb": [ + "DAIMONIA is the craft of using magic to", + "tap into the will of spirits, compelling their", + "influence with transformative results.", + "Though shunned by civilized society,", + "some mystics claim that their own spirit", + "is incomplete without this communion." + ] + }, + "divinations": { + "blurb": [ + "DIVINATIONS grant access to hidden", + "knowledge, revealing information that", + "there may be no other way to know.", + "", + "Practicing thaumaturgists claim", + "that knowledge is power." + ] + }, + "enchanted amulets": { + "blurb": [ + "AMULETS provide reactive benefits", + "to their wearer, often intervening", + "against harm a number of times", + "until their magic is spent.", + "Some amulets have the opposite objective.", + "It is wise to appraise before wearing." + ] + }, + "enchanted feather": { + "blurb": [ + "The ENCHANTED FEATHER can be depleted to", + "restore spellbooks or inscribe magic words", + "onto a blank scroll.", + "Only those who know the magic", + "words can inscribe with purpose.", + "Depleted feathers may be recharged." + ] + }, + "enchanted rings": { + "blurb": [ + "Adventurers covet magical RINGS for", + "the passive magical benefits they", + "provide, with effects that range", + "from mild, to situational, to", + "extremely powerful.", + "Magical rings may change the world." + ] + }, + "evocation": { + "blurb": [ + "Sorcerors practice EVOCATION to conjure", + "energies and material using their minds.", + "Largely this is used to launch harmful", + "projectiles at enemies by adventurers,", + "many of which leave a lasting effect", + "that may continue to harm the target." + ] + }, + "impeturia": { + "blurb": [ + "Mystics who practice IMPETURIA do so by", + "calling to the spirit of the object they", + "wish to manipulate, and compel it to alter", + "its state. Sorcerors attempting to do the", + "same thing with kinesis have so far been", + "unsuccessful." + ] + }, + "kinesis": { + "blurb": [ + "Sorcerers practice KINESIS to move", + "things with their minds. While trying to", + "improve control and accuracy, they have", + "stumbled upon other innovations, but", + "precise manipulation remains difficult." + ] + }, + "magicstaffs": { + "blurb": [ + "These weapons allow any adventurer, no", + "matter how mundane, to fire magical spells.", + "", + "The color of the staff indicates", + "what kinds of spells it might contain.", + "The item always breaks when depleted." + ] + }, + "meditations": { + "blurb": [ + "A simple MEDITATION is most magicians'", + "first introduction to thaumaturgy.", + "", + "These spells require concentration to", + "sustain, and will only effect the caster,", + "but they can be dismissed at will." + ] + }, + "miraculous feats": { + "blurb": [ + "Those in ancient times who performed", + "MIRACULOUS FEATS are the first known", + "thaumaturgists. Casting immediately", + "performs a single wonderous action.", + "It is taught that aligning one's self with", + "great purpose will yield these powers." + ] + }, + "psianimus": { + "blurb": [ + "The most commonly practiced of the", + "mystic's arts, PSIANIMUS is the craft", + "of using magic to alter the mind of a", + "living (or unliving) target.", + "The effects are usually temporary,", + "otherwise its reputation might differ." + ] + }, + "sarkomancy": { + "blurb": [ + "Considered a forbidden and dark art by", + "the church, SARKOMANCY is the craft of", + "using magic to manipulate the body,", + "usually against unwilling targets.", + "Uses of this magic to benefit one's", + "own body are said to only be rumors." + ] + }, + "scrolls": { + "blurb": [ + "A scroll can be invoked by any adventurer", + "to spend its stored spell, however it is", + "immediately consumed upon use.", + "Some spells can only be found on scrolls.", + "Blank scrolls may later be inscribed,", + "with the right tools and knowledge." + ] + }, + "temulturgy": { + "blurb": [ + "Sometimes magic is needed that does", + "not simply hurl one force at another.", + "The study of TEMULTURGY is specialized", + "toward disrupting an object's structure.", + "So far sorcerors haven't figured out", + "how to practice this on living targets." + ] + } +} \ No newline at end of file diff --git a/lang/compendium_lang/lang_monsters.json b/lang/compendium_lang/lang_monsters.json new file mode 100644 index 000000000..456d3011c --- /dev/null +++ b/lang/compendium_lang/lang_monsters.json @@ -0,0 +1,1049 @@ +{ + "algernon": { + "blurb": [ + "Due to sparse food, dungeon life isn't easy", + "for rats. However, sometimes a large food", + "source will cause a boom in the population.", + "", + "In such times, a very smart rat", + "emerges as the king." + ], + "abilities": [], + "inventory": [ + "- Emeralds", + "- Meat, Cheese" + ] + }, + "artemisia": { + "blurb": [ + "Banished to the Underworld for betraying", + "her fellow Eternals, ARTEMISIA uses her", + "ghostly abilities to be an even more", + "wily hunter.", + "Even death could not pry the", + "magic bow from her grasp." + ], + "abilities": [ + "- Mimic Other", + "- Levitation", + "- Teleportation", + "- Indominable" + ], + "inventory": [ + "- Khryselakatos", + "- Quivers of Silver Ammo", + "- Spooky Masks" + ] + }, + "automaton": { + "blurb": [ + "A construct devised by the Magicians' Guild,", + "magic and technology have given the", + "AUTOMATON the gift of life as a servant.", + "", + "They have only been known to", + "become violent when commanded." + ], + "abilities": [ + "- Salvage", + "- Self Destruct" + ], + "inventory": [ + "" + ] + }, + "baratheon": { + "blurb": [ + "Fathered by wrath, this dark spirit", + "seeks those with ambitious purpose.", + "", + "BARATHEON cares not for its own future,", + "existing only to keep heroes from their", + "great destinies." + ], + "abilities": [ + "- Mimic Other", + "- Levitation", + "- Teleportation", + "- Indominable" + ], + "inventory": [ + "- Phantom Masks", + "- Spooky Masks" + ] + }, + "bat": { + "blurb": [ + "Wild blood-sucking bats recently", + "infested the Hamlet mines.", + "Though small, wild flapping makes BATS", + "a hard target for even the most skilled", + "adventurers. Expect more than half of", + "attacks to miss these dungeon pests!" + ], + "abilities": [ + "- Levitation", + "- Evasion", + "- Bleeding Attacks", + "- Indominable" + ], + "inventory": [] + }, + "bram kindly": { + "blurb": [ + "The enigmatic master of the Hunters' Guild,", + "BRAM KINDLY is said to have disappeared", + "while investigating the surge of undead.", + "", + "It appears he found exactly", + "what he was looking for." + ], + "abilities": [ + "- Drain Soul", + "- Vampiric Aura", + "- Indominable" + ], + "inventory": [ + "- Wraith's Gown", + "- Magicstaffs of Bleed", + "- Vampire Doublet" + ] + }, + "bram succubi": { + "blurb": [ + "Beings of immense power tend to attract", + "followers. Verona, Aleera & Marishka", + "pledged themselves to a mighty vampire.", + "", + "He gladly tutored them in vampiric magics", + "inside his grim castle." + ], + "abilities": [ + "- Charm Monster", + "- Teleportation" + ], + "inventory": [ + "- Spellbooks of Bloodletting", + "- Magicstaffs of Charm Monster", + "- Masquerade Masks" + ] + }, + "bubbles": { + "blurb": [ + "Those who have survived BUBBLES claim that", + "she isn't a crab at all. Rather, she is", + "a demonic spirit taking a crab's shape.", + "", + "She carries with her the", + "treasure from her victims." + ], + "abilities": [ + "- Poisoned Attacks", + "- Spray Web" + ], + "inventory": [ + "- Dyrnwyn", + "- Ring of Invisibility" + ] + }, + "bugbear": { + "blurb": [ + "These dumb bullies fail to organize", + "large-scale war against surface humans.", + "Nevertheless, they do build coalitions", + "of brutal raiders, and seize settlements.", + "BUGBEARS often use ruins for homes, and", + "recently set up camp underneath Hamlet." + ], + "abilities": [ + "- Strafing", + "- Shield Bash" + ], + "inventory": [ + "- Steel Swords/Axes", + "- Arbalests", + "- Scutums" + ] + }, + "cockatrice": { + "blurb": [ + "A creature long thought to be extinct, ", + "the COCKATRICE is a powerful flying beast.", + "", + "Its razor sharp talons make quick work of", + "any prey it paralyzes with its breath.", + "Its feathers are thought to be magical." + ], + "abilities": [ + "- Stoneblood", + "- Levitation", + "- Double Strike" + ], + "inventory": [ + "- Enchanted Feathers", + "- Spellbook of Stoneblood", + "- Gemstones", + "- Potions" + ] + }, + "coral grimes": { + "blurb": [ + "Ghoul hunter CORAL GRIMES and his posse", + "rose to fame, but sextons warn that the", + "dead keep their compulsions from life.", + "", + "Once GRIMES' party was killed, all", + "prayed he would not again rise." + ], + "abilities": [], + "inventory": [ + "- Bounty Hunter Hat", + "- Ruby", + "- Meat" + ] + }, + "crab": { + "blurb": [ + "Giant dungeon CRABS", + "are patient hunters.", + "", + "Their slow metabolism and poison claws", + "make them well suited to waiting for prey", + "to ambush, eviscerate, and devour." + ], + "abilities": [ + "- Poisoned Attacks", + "- Spray Web" + ], + "inventory": [ + "" + ] + }, + "crystalgolem": { + "blurb": [ + "The bigger brother of the automaton,", + "the CRYSTAL GOLEM has emerged from", + "an excess of magic material and will.", + "", + "When it raises its arms, it's", + "best to be somewhere else." + ], + "abilities": [ + "- Power Strike" + ], + "inventory": [ + "- Crystal equipment", + "- Gemstones" + ] + }, + "demon": { + "blurb": [ + "Said to be a manifestation of primal", + "violence, DEMONS are found near", + "the greatest sources of evil. ", + "", + "They eviscerate enemies with their claws,", + "or roast victims with hellfire first." + ], + "abilities": [ + "- Fireball" + ], + "inventory": [ + "" + ] + }, + "deudebreau": { + "blurb": [ + "As foul as demons are, those who climb the", + "demonic ranks are fouler still.", + "", + "Founder of a vicious fraternity,", + "DEU DE'BREAU is always accompanied by an", + "entourage of demonic sycophants." + ], + "abilities": [ + "- Fireball" + ], + "inventory": [ + "" + ] + }, + "devil": { + "blurb": [ + "The devil BAPHOMET commands the", + "surrounding hells to his will, using", + "minions, magic, and brimstone.", + "", + "He will not permit interlopers", + "to quell his ambitions." + ], + "abilities": [ + "- Fireball Spray", + "- Conjure Hellspawn", + "- Conjure Shadows", + "- Hail of Boulders", + "- Indominable", + "- Telepathy", + "- Lava Portal" + ], + "inventory": [ + "" + ] + }, + "dummybot": { + "blurb": [ + "The DUMMYBOT is extraordinarily durable.", + "With proper maintenance, your shielded", + "companion(s) can serve as a powerful", + "guardian while you attend to business!" + ], + "abilities": [ + "- Taunt" + ], + "inventory": [ + "" + ] + }, + "enslaved ghoul": { + "blurb": [ + "Those ENSLAVED at death possess a", + "supernatural hunger, fueled by hatred", + "for their master.", + "", + "Consuming the living invigorates them, and", + "they move with ferocious speed." + ], + "abilities": [ + "- Vampiric Aura" + ], + "inventory": [ + "- Meat", + "- Miscellaneous tools", + "- Blessed Water" + ] + }, + "funny bones": { + "blurb": [ + "An especially cruel mercenary captain", + "was in charge of mine security and", + "slave discipline in the mines.", + "", + "With fine gear and a stern demeanor,", + "he earned himself an ironic nickname." + ], + "abilities": [], + "inventory": [ + "- Parashu", + "- Cloak of Protection", + "- Eyepatch", + "- Leather, Wooden, Iron shields/", + " helmets" + ] + }, + "gharbad": { + "blurb": [ + "Rarely a goatman lives long enough for its", + "fur to turn silver, and only somewhat less", + "rarely, one is born naturally that way.", + "", + "Whether earned or not, these lucky ones", + "attract loyal followers." + ], + "abilities": [], + "inventory": [ + "- Iron, Steel, Crystal armor", + "- Steel, Crystal Axes/Maces", + "- Crystal Shurikens", + "- Steel Chakrams", + "- Potions of Healing", + "- Bottles of Booze" + ] + }, + "ghost": { + "blurb": [ + "The Hamlet curse enables a bonded group", + "of adventurers to be together in spirit", + "after death, literally.", + "", + "With persistence, ambient necromancy may", + "cause a body to rematerialize." + ], + "abilities": [ + "- Sneak", + "- Push", + "- Haunt", + "- Chill", + "- Callout", + "- Levitation" + ], + "inventory": [] + }, + "ghoul": { + "blurb": [ + "With a primal hatred for the living, GHOULS", + "attack when their rest is disturbed.", + "", + "Nobody quite knows what turns a dead", + "person into a GHOUL, but some try to", + "prevent it with certain burial rites." + ], + "abilities": [], + "inventory": [ + "- Meat", + "- Miscellaneous tools", + "- Blessed Water" + ] + }, + "gnome": { + "blurb": [ + "GNOMES are known for their mischief, but ", + "those who become addicted to digging for", + "wealth are beyond hope.", + "", + "One must take care when seeking to", + "plunder a gnome's food and riches." + ], + "abilities": [], + "inventory": [ + "- Pickaxes", + "- Magicstaffs of Lightning", + "- Fish", + "- Pipes", + "- Lanterns", + "- Gemstones", + "- Enchanted Feathers" + ] + }, + "gnome thief": { + "blurb": [ + "When gold-addicted gnomes have none", + "to mine, they organize to hunt for it.", + "", + "Beware of GNOME THIEF ambushing parties", + "who use traps and weapons with deadly", + "skill." + ], + "abilities": [ + "- Lay Trap" + ], + "inventory": [ + "- Steel Swords/Maces", + "- Shortbows/Crossbows and Quivers", + "- Bycockets (Thief Leader)", + "- Hood Of Whispers", + "- Bandanas", + "- Bandit Masks", + "- Mouthknives", + "- Beartraps/Tinkered Traps", + "- Enchanted Feathers" + ] + }, + "goatman": { + "blurb": [ + "A tribal people from neighboring", + "mountains, GOATMEN are hardy,", + "dull-witted folk.", + "Their culture revolves around drink,", + "though they can be competent in", + "many skills when satiated." + ], + "abilities": [], + "inventory": [ + "- Iron, Steel, Crystal armor", + "- Steel, Crystal Axes/Maces", + "- Miscellaneous magicstaffs", + "- Lanterns, Crystal Shards", + "- Mirror Shields", + "- Steel Chakrams", + "- Potions of Healing", + "- Bottles of Booze" + ] + }, + "goblin": { + "blurb": [ + "Dungeon GOBLINS are similar to humans", + "but live in brutal, savage conditions.", + "", + "While highly resourceful, their minds have", + "atrophied, rendering many of them", + "dependent on raids against humans." + ], + "abilities": [], + "inventory": [ + "- Bronze, Iron Maces/Axes", + "- Shortbows and Quivers", + "- Magicstaffs of Fire", + "- Cloaks of Magic Reflection", + "- Grass Sprigs", + "- Leather, Bronze armor", + "- Cloaks", + "- Animal Hats", + "- Magus Headdresses" + ] + }, + "gyrobot": { + "blurb": [ + "While the airborne GYROBOT can avoid", + "most threats, some dangers are", + "too large to out-maneuver.", + "Curious GYROBOTS may unintentionally", + "trigger traps and cease function." + ], + "abilities": [ + "- Levitation", + "- Detection", + "- Light" + ], + "inventory": [ + "" + ] + }, + "human": { + "blurb": [ + "Hamlet commoners once hailed", + "from many nations to trade", + "and work together peaceably. ", + "", + "With proper focus, HUMANS can become", + "adept in whatever they set their minds to." + ], + "abilities": [], + "inventory": [ + "- Melee and ranged weaponry", + "- Leather, Bronze, Iron, Steel", + "armor" + ] + }, + "imp": { + "blurb": [ + "Terrible mischief makers, IMPS wander", + "wherever they can inflict suffering.", + "", + "Their wings and magical ability allow them", + "to devastate foes, often well out of reach,", + "cackling all the while." + ], + "abilities": [ + "- Fireball", + "- Levitation" + ], + "inventory": [ + "- Miscellaneous Spellbooks" + ] + }, + "incubus": { + "blurb": [ + "A curse of pride personified, the INCUBUS", + "obsesses over controlling others.", + "", + "He uses spells and weapons to toy with his", + "victims, though some take a direct approach", + "in their quest for evil." + ], + "abilities": [ + "- Teleportation", + "- Steal Weapon" + ], + "inventory": [ + "- Magicstaffs of Cold", + "- Crossbows", + "- Steel, Crystal polearms", + "- Bottles of Booze", + "- Potions of Confusion", + "- Masquerade Mask", + "- Mirror Shield" + ] + }, + "insectoid": { + "blurb": [ + "Not much is known about the elusive bug", + "people who roam in remote places.", + "", + "Their skill and mysterious physiology give", + "them an edge on the battlefield, striking", + "fear into many humans." + ], + "abilities": [ + "- Spray Acid" + ], + "inventory": [ + "- Iron, Steel, Crystal armor", + "- Steel, Crystal Swords/Polearms", + "- Shortbows/Longbows/Compound", + " Bows", + "- Crossbows", + "- Iron Daggers" + ] + }, + "kobold": { + "blurb": [ + "The KOBOLDS are small, scaly folk, and are", + "notorious cultists and bandits in the region.", + "", + "They have long been a problem for the", + "Magicians' Guild, thanks to their innate", + "magic resistance." + ], + "abilities": [ + "- Slow" + ], + "inventory": [ + "- Iron, Steel melee/ranged", + " weaponry", + "- Enchanted Feathers", + "- Tin Openers", + "- Towels" + ] + }, + "lich": { + "blurb": [ + "BARON HERX's body is blackened in the", + "death of his flesh, but wields immense", + "magical power and a dark bond with Hell.", + "", + "Those going toe-to-toe with him must be", + "well prepared for his spells." + ], + "abilities": [ + "- Lightning Volley", + "- Conjure Hellspawn", + "- Command Darkness", + "- Levitation", + "- Indominable", + "- Dash" + ], + "inventory": [ + "- Purple Mystic Orb", + "- Gold" + ] + }, + "lichfire": { + "blurb": [ + "ORPHEUS uses his passion to fuel his magic", + "and likes to dominate his problems", + "face-to-face.", + "", + "Big, powerful spells and intimidation", + "allow him to end things quickly." + ], + "abilities": [ + "- Fireball", + "- Telepathy", + "- Levitation", + "- Indominable", + "- Call Firestorm", + "- Teleport", + "- Blood Field" + ], + "inventory": [ + "- Crystal Sword", + "- Golden Mask" + ] + }, + "lichice": { + "blurb": [ + "ERUDYCE prefers the right spell,", + "placed with severe intent, to turn", + "events in her favor.", + "", + "She lets others face confrontation while", + "staying in control from behind the scenes." + ], + "abilities": [ + "- Cold", + "- Levitation", + "- Telepathy", + "- Indominable", + "- Magic Missile Volley", + "- Blood Field", + "- Teleport", + "- Command Automata" + ], + "inventory": [ + "- Magicstaff of Cold", + "- Magus Headdress" + ] + }, + "lilith": { + "blurb": [ + "A name first given in myth to the primal", + "succubus, those who lead and inspire", + "succubi are sometimes granted this title.", + "", + "Beware her influence, as her foes", + "are known to switch sides." + ], + "abilities": [ + "- Charm Monster", + "- Teleportation" + ], + "inventory": [ + "- Magicstaffs of Charm Monster", + "- Roses" + ] + }, + "mimic": { + "blurb": [ + "Little is understood of a MIMIC'S true ", + "nature, but it's assumed they didn't", + "always look like treasure chests.", + "", + "Further study has been impossible due to", + "them eating researchers and their notes." + ], + "abilities": [ + "- Devour Equipment", + "- Indominable" + ], + "inventory": [ + "- Chest contents", + "- Gold" + ] + }, + "minotaur": { + "blurb": [ + "A huge legendary beast of unknown origin,", + "the MINOTAUR can break through dungeon", + "walls and has been enchanted, permitting it", + "to levitate.", + "It is ruthless in its pursuit of its master's", + "enemies." + ], + "abilities": [ + "- Levitation", + "- Destroy Obstacle", + "- Telepathy", + "- Indominable" + ], + "inventory": [ + "- High value gemstones" + ] + }, + "mysterious shop": { + "blurb": [ + "This MYSTERIOUS MERCHANT knows things", + "he couldn't possibly know and carries", + "artifacts he couldn't possibly possess.", + "", + "He will trade only if given mystical orbs,", + "if the customer can bear to offer them." + ], + "abilities": [ + "- Magic Missile", + "- Indominable", + "- Drain Soul", + "- Bloodletting", + "- Teleportation" + ], + "inventory": [ + "- Various Shop stock" + ] + }, + "potato king": { + "blurb": [ + "In contrast to his kin, THE POTATO KING", + "appears merry and carefree, though", + "insane.", + "Fellow goblins seem drawn to his chaotic", + "personality, perhaps desperate for levity", + "in the swamps." + ], + "abilities": [], + "inventory": [ + "- Sharur", + "- Jester Hat", + "- Cloaks of Magic Reflection", + "- Grass Sprigs", + "- Leather, Bronze armor", + "- Cloaks" + ] + }, + "rat": { + "blurb": [ + "The dungeons have bred these RATS for ", + "aggression and size.", + "", + "While one would be wise to stay wary of ", + "swarms and sneaky RATS, usually the sight", + "of them means the promise of food." + ], + "abilities": [], + "inventory": [ + "- Meat, Cheese" + ] + }, + "scarab": { + "blurb": [ + "The hardy SCARAB is said to bore into dense,", + "magical materials, conveying profound", + "magical resistance into their shells.", + "", + "Their slimy meat is unappealing to some", + "but it is quite nutritious." + ], + "abilities": [], + "inventory": [ + "- Tomalleys", + "- Low value gemstones" + ] + }, + "scorpion": { + "blurb": [ + "A good shield in skilled hands can block the", + "deadly paralyzing sting of the SCORPION.", + "", + "Victims of SCORPIONS usually have a look", + "of surprise frozen on their faces when the", + "body is found." + ], + "abilities": [ + "- Paralyzing Attacks" + ], + "inventory": [ + "" + ] + }, + "sentrybot": { + "blurb": [ + "Often assumed to be a modern invention,", + "variants of this SENTRYBOT blueprint have", + "been found active among ancient ruins.", + "", + "Though easily broken, tinkerers rely", + "on sentrybots for high damage output." + ], + "abilities": [], + "inventory": [ + "" + ] + }, + "shadow": { + "blurb": [ + "An evil spirit that wanders, appearing not", + "to have any sense of itself.", + "", + "The SHADOW steals the identity of anyone it", + "encounters, and vainly attempts to replace", + "its target by murdering the original." + ], + "abilities": [ + "- Mimic Other", + "- Levitation", + "- Teleportation", + "- Indominable" + ], + "inventory": [ + "- Spooky Masks" + ] + }, + "shelob": { + "blurb": [ + "Those who have survived SHELOB claim that", + "she isn't a spider at all. Rather, she is", + "a demonic spirit taking a spider's shape.", + "", + "She carries with her the", + "treasure from her victims." + ], + "abilities": [ + "- Poisoned Attacks", + "- Spray Web" + ], + "inventory": [ + "- Dyrnwyn", + "- Ring of Invisibility" + ] + }, + "shopkeeper": { + "blurb": [ + "Good SHOPKEEPERS somehow make their", + "businesses work anywhere.", + "They are picky about what they buy,", + "and do not tolerate burglars.", + "", + "Word travels fast in the Merchants' Guild." + ], + "abilities": [ + "- Magic Missile", + "- Drain Soul", + "- Indominable", + "- Bloodletting" + ], + "inventory": [ + "- Various Shop stock" + ] + }, + "skeleton": { + "blurb": [ + "The cursed remains of a mercenary army,", + "SKELETONS carry a variety of equipment.", + "", + "One must take care both when fighting and", + "looting them, as the battered gear may", + "harm more than it helps." + ], + "abilities": [], + "inventory": [ + "- Bronze, Iron Axes/Swords/", + " Polearms", + "- Leather, Wooden, Iron shields/", + " helmets" + ] + }, + "skrabblag": { + "blurb": [ + "The exoskeleton of this beast bears", + "innumerable splits and scratches from", + "battling with weaker prey.", + "Naturalists claim that scorpions like", + "SKRABBLAG are capable of aging into", + "immortality." + ], + "abilities": [ + "- Paralyzing Attacks" + ], + "inventory": [ + "- Rubies" + ] + }, + "slime": { + "blurb": [ + "Found in a variety of colors, SLIMES are", + "mindless entities seeking endless", + "consumption.", + "They often find themselves sliding through", + "pipes or waterways in their search for", + "organic matter to liquefy." + ], + "abilities": [ + "- Waterwalking (Blue/Purple)", + "- Lavawalking (Orange)", + "- Spray Acid (Green)", + "- Spray Water (Blue)", + "- Spray Flames (Orange)", + "- Spray Tar (Purple)", + "- Spray Sludge (Silver)" + ], + "inventory": [ + "" + ] + }, + "spellbot": { + "blurb": [ + "All automated tinkering mechanisms rely", + "on magic scrap for limited behaviors.", + "", + "The SPELLBOT is affixed with a large supply", + "allowing it to create magic damage." + ], + "abilities": [ + "- Forcebolt", + "- Magic Missile" + ], + "inventory": [ + "" + ] + }, + "spider": { + "blurb": [ + "Giant dungeon SPIDERS", + "are patient hunters.", + "", + "Their slow metabolism and poison fangs", + "make them well suited to waiting for prey", + "to ambush, eviscerate, and devour." + ], + "abilities": [ + "- Poisoned Attacks", + "- Spray Web" + ], + "inventory": [ + "" + ] + }, + "succubus": { + "blurb": [ + "A personified curse of lust, the SUCCUBUS", + "is crafty and cruel.", + "", + "She has everything she needs to", + "manipulate others, though some take the", + "independent path in their quest for evil." + ], + "abilities": [ + "- Charm Monster", + "- Teleportation" + ], + "inventory": [ + "- Magicstaffs of Charm Monster", + "- Masquerade Masks" + ] + }, + "thumpus": { + "blurb": [ + "The amiability between trolls and gnomes is", + "well-documented, but typically the gnomes", + "enjoy toying with the bigger beasts.", + "", + "THUMPUS appears to have", + "earned the gnomes' respect." + ], + "abilities": [], + "inventory": [ + "- Miscellaneous dungeon floor loot", + "- Roses" + ] + }, + "troll": { + "blurb": [ + "These nearly blind giants seem to be", + "wandering the dungeons by coincidence,", + "preferring to live in isolation, even from", + "one another.", + "Cross one's path and be prepared for a ", + "powerful pummeling." + ], + "abilities": [], + "inventory": [ + "- Miscellaneous dungeon floor loot", + "- Roses" + ] + }, + "vampire": { + "blurb": [ + "In order to maintain their curse of", + "immortality, VAMPIRES must consume the", + "blood of the living.", + "They have wonderous magical abilities,", + "which are said to be gained by forfeiting", + "their very souls." + ], + "abilities": [ + "- Drain Soul", + "- Vampiric Aura" + ], + "inventory": [ + "- Magicstaffs of Bleed", + "- Vampire Doublet" + ] + }, + "xyggi": { + "blurb": [ + "The cult of sorcerors believes that all", + "intellect is born of magic, claiming that", + "where you find one, the other will follow.", + "", + "The scarab, XYGGI, is rumored to have a", + "threatening amount of both." + ], + "abilities": [ + "- Cold", + "- Magic Reflection" + ], + "inventory": [ + "- Enchanted Feathers", + "- Tomalleys", + "- Low value gemstones" + ] + } +} \ No newline at end of file diff --git a/lang/compendium_lang/lang_world.json b/lang/compendium_lang/lang_world.json new file mode 100644 index 000000000..305a326b0 --- /dev/null +++ b/lang/compendium_lang/lang_world.json @@ -0,0 +1,2856 @@ +{ + "arcane citadel": { + "blurb": [ + "Constructed according to an architectural", + "master plan, the CITADEL is arranged as", + "though it is a grand university.", + "", + "Only access to extremely powerful magic", + "can justify its grandeur." + ], + "details": [ + "INHABITANTS:", + "Goatmen, Automatons, Incubi,", + "Vampires, Crystal Golems, Cockatrices,", + "Shadows", + "", + "This rigidly organized structure", + "may be more challenging to navigate", + "than other dungeons and labyrinths", + "due to closed gates and dead-ends.", + "", + "A good source of dig and the open spell", + "can simplify exploration considerably.", + "", + "Take care not to be cornered by large", + "enemies like the cockatrice and golem." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "arrow trap": { + "blurb": [ + "First thought to be created by goblins,", + "these complex mechanisms are now known", + "to predate them.", + "", + "However, they have been repurposed by", + "resourceful goblin craftsfolk." + ], + "details": [ + "Unless the dungeon is unnaturally,", + "dark, arrow traps always have two", + "torches fixed to the face.", + "", + "Each trap is filled with a random", + "type of ammo to afflict their target.", + "This ammo can be pilfered using a", + "lockpick, if the user is skilled", + "in tinkering, disabling the trap.", + "", + "Arrow traps may also be disarmed", + "by tossing an item in front of the trap.", + "They can only fire arrows five times." + ], + "details_line_highlights": [] + }, + "bell": { + "blurb": [ + "Many towns and cities make use of BELLS", + "to draw the attention of inhabitants,", + "lifting spirits for collective events.", + "", + "Old bells that haven't been maintained", + "will sometimes fail spectacularly." + ], + "details": [ + "FOUND IN:", + "- The Ruins", + "- The Citadel", + "", + "BELL USE EFFECTS:", + "- Stamina (1 Minute)", + "- Agility (1 Minute)", + "- Mentality (1 Minute)", + "- Strength (1 Minute)", + "- HP/MP Restoration", + "- Attracts NPCs within 15 tiles", + "", + "SPAWN POSSIBILITIES:", + "- 25% Spellbook hidden inside", + "- 25% Gold hidden inside", + "- 25% Bats hidden inside", + "", + "It's usually a good idea to ring a bell,", + "if you're not afraid of a fight!", + "Each bell will always provide the same", + "effect to those who are close enough", + "to be inspired by its song. Standing so", + "close is not without risk, as bats may", + "spill out or the structure may fail", + "unexpectedly!", + "", + "Falling bells deal 80 damage to those", + "caught in it's path. The low-hanging", + "bell can also be used to deal damage", + "to an over-confident minotaur." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "boulder trap": { + "blurb": [ + "Heralded by dark holes in the ceiling,", + "some say the huge stones are the result", + "of cave-ins from excavated earth.", + "", + "Others theorize that they are a", + "punishment from the gods themselves." + ], + "details": [ + "Boulders deal 80 damage, regardless", + "of the victim's armor or constitution.", + "If they're not killed by a boulder, it", + "will shatter into several rocks.", + "", + "Boulders will only roll straight", + "North, South, East or West.", + "", + "Boulders that would block the exit", + "are shattered by the gods, leaving", + "behind a lucky rock in its rubble.", + "", + "Most boulder traps can be triggered", + "by throwing an object under the hole." + ], + "details_line_highlights": [] + }, + "brams castle": { + "blurb": [ + "While inaccessible by mundane means,", + "this manor lies somewhere in the depths", + "of the Citadel's machinery. It seems that", + "immortals like their own spaces away", + "from prying eyes." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Wraith's Gown", + "", + "Bram's Castle is more of a home and", + "less of a fortress. While his servants", + "will put up a fight, adventurers will", + "have the opportunity to manage each", + "room, eventually facing the vampire", + "at the top of the castle.", + "", + "Take care not to let the tight halls", + "overwhelm you. Be warned that the", + "upper floors are off-limits to many", + "bestial followers." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "breakable barriers": { + "blurb": [ + "Some barriers may appear impassable", + "at first, but their integrity is such", + "that a few strong hits can break", + "open the way. Depending on the material,", + "they have different weaknesses." + ], + "details": [ + "BARRIER TYPES:", + "- Wood", + " Flammable, takes extra damage", + " from axes and unarmed attacks.", + "- Brick & Stone", + " Takes extra damage from maces.", + "- Cordage (webs and thicket)", + " Flammable, takes extra damage", + " from axes and swords.", + "", + "All breakable barrier types take", + "full damage from force, lightning", + "and fire spell attacks, but they", + "are immune to cold." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "brimstone boulder": { + "blurb": [ + "Boulders are threatening enough,", + "but veins of brimstone allow this", + "hellish variety to roll over lava.", + "They'll also detonate in a", + "fiery explosion on contact.", + "AVOID." + ], + "details": [ + "Summoning brimstone boulders is", + "a power unique to Baphomet, dealing", + "50 damage regardless of the victim's", + "armor or constitution.", + "", + "Upon impact, brimstone boulders", + "explode into a column of flames", + "dealing additional magic damage.", + "", + "On the molten throne, these give", + "Baphomet a particular edge, as the", + "pools of lava he uses to traverse", + "are no longer shelters from the", + "rolling destruction.", + "", + "An adventurer who levitates can", + "still nonetheless avoid the boulders,", + "while they continue to crush through", + "the very minions he has summoned." + ], + "details_line_highlights": [] + }, + "ceiling trap": { + "blurb": [ + "By tapping directly into magical energy", + "from the crystal-dense ceilings, these", + "security systems can hinder invaders", + "with almost no maintenance cost.", + "", + "An innovation available in several varieties." + ], + "details": [ + "CEILING TRAP TYPES:", + "- Forcebolt", + "- Cold", + "- Fireball", + "- Lightning", + "- Magic Missile", + "- Sleep", + "- Slow", + "- Confuse", + "", + "Though simliar to magic traps, the", + "ceiling trap can't be destroyed.", + "", + "Dropped items can fool the trap, but it", + "will fire continuously. Time between", + "each spell permits dodging through." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "chest": { + "blurb": [ + "CHESTS may hold valuable treasures that", + "will aid adventurers. Equipment within", + "is never cursed. Unfortunately, before", + "opening, chests are indistinguishable", + "from mimics." + ], + "details": [ + "CHEST TYPES:", + "- 13.5% Weaponry and Armor", + "- 13.5% Scrolls and Magic Supplies", + "- 13.5% Potions and Alchemy Supplies", + "- 13.5% Tools and Tinkering Supplies", + "- 13.5% Jewels and Enchanted Jewelry", + "- 13.5% Foodstuffs", + "- 13.5% Random Items", + "- 5.5% Empty/Garbage", + "", + "Items found inside chests are usually", + "of a higher quality than those found", + "scattered along the dungeon floors,", + "often uncursed.", + "", + "Chests usually have a 10% chance of", + "of being locked and a 10% chance of", + "being mimics.", + "", + "When unlocked with tinkering skill", + "using a lockpick, a chest's locking", + "mechanism is salvaged, yielding", + "magic and metal scrap in the chest." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "citadel sanctum": { + "blurb": [ + "In the core of the Citadel, its nature", + "is revealed as a gigantic magical device,", + "created to open up rifts to another", + "dimension with forbidden magic.", + "", + "Its creators will protect it at all costs." + ], + "details": [ + "Orpheus and Erudyce had substantial", + "magical talent Herx never did when", + "he received the evil powers that", + "turned him into a lich.", + "", + "Expect this pair to be far more", + "formidable with their spells in combat,", + "especially with their more recent", + "submission to evil magic.", + "", + "Erudyce's cold spells will make it easier", + "for her brother to catch you in melee!", + "", + "Each sibling will become enraged if", + "their counterpart is killed, enhancing", + "their combat prowess. Think ahead!" + ], + "details_line_highlights": [] + }, + "cockatrice lair": { + "blurb": [ + "While no symbiosis between these scaly", + "creatures is known, Cockatrices and", + "Kobolds are historically depicted", + "together by Kobold tribal art. It is", + "thought they worship the beasts." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Dragon's Mail", + "", + "This fiercely guarded kobold maze", + "is the greatest known example of", + "their ingenuity and culture.", + "", + "Frought with traps and puzzles,", + "this lair is designed to keep intruders", + "out.", + "", + "Strong enchantments prevent", + "prevent teleporting, dig, open, and", + "levitation. Those seeking treasure", + "must navigate these tunnels the hard", + "way, and hope they can escape." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "containers": { + "blurb": [ + "Dungeon denizens often gather", + "belongings into containers or", + "hide them away in sheltered spots.", + "", + "Gold and items may be found when", + "bashing fragile objects open." + ], + "details": [ + "The contents of a breakable container", + "varies greatly depending on where it's", + "found.", + "", + "What remains most consistent is a", + "handful of coins, or a random item", + "squirreled away.", + "", + "Rarely, a small creature, or even a", + "small group may be tucked inside for", + "shelter." + ], + "details_line_highlights": [] + }, + "crystal caves": { + "blurb": [ + "While this rich source of extremely", + "rare crystal was only recently discovered,", + "the Magicians' Guild archmagisters", + "quickly sealed it off, for Hamlet's", + "safety, of course." + ], + "details": [ + "INHABITANTS:", + "Kobolds, Scarabs, Insectoids,", + "Automatons, Incubi, Crystal Golems,", + "Cockatrices", + "", + "NEW TRAP:", + "Ceiling Trap", + "", + "The crystal within these caves gives", + "many of its denizens intense power", + "beyond what is naturally possible.", + "", + "Confident adventurers coming from", + "the ruins would be wise to exercise", + "greater caution with new foes." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "daedalus": { + "blurb": [ + "Closely bound to the minotaur,", + "DAEDALUS SHRINES seem to always be", + "nearby when the minotaur is on the hunt.", + "", + "Savvy adventurers can touch them to", + "reveal hope for a speedy escape." + ], + "details": [ + "These shrines turn to point to the", + "primary exit when touched, sending a", + "divining beam forth in that direction.", + "This also reveals the map between the", + "shrine and the exit!", + "", + "If activated within 30 seconds of the", + "minotaur's arrival, the shrine boosts", + "user's escape with an additional", + "blessing of speed.", + "", + "Adventurers exploring together may", + "find a bit more confidence splitting", + "up where the minotaur is known to", + "hunt, thanks to those who honor", + "Daedalus." + ], + "details_line_highlights": [] + }, + "door": { + "blurb": [ + "It opens, it closes, and", + "may sometimes be locked!", + "", + "Unlike the stubborn gate, DOORS can", + "be picked or bashed open. Monsters bash", + "through doors, if too dumb to open them." + ], + "details": [ + "While the lock spell can prevent", + "monsters from opening doors easily,", + "most don't have trouble bashing", + "through them anyway.", + "Doors are not a reliable way to keep", + "out monsters, even if locked.", + "", + "Lockpicking a sealed door is usually", + "faster than bashing it, while providing", + "tinkering skill practice.", + "", + "Doors take extra damage from axes", + "or unarmed strikes, if trained.", + "Damaging spells may also be effective." + ], + "details_line_highlights": [] + }, + "fountain": { + "blurb": [ + "FOUNTAIN waters refresh or apply magic", + "to an adventurer. They've also been known", + "to summon a succubus or incubus.", + "", + "Magic doesn't come without peril." + ], + "details": [ + "FOUNTAIN USE POSSIBILITIES:", + "- 40% Apply a Random Potion Effect", + "- 30% Restore 5 HP, +Satiation", + "- 10% Summon Succubus/Incubus:", + " Level 1-10:", + " - Succubus (100%)", + " Level 11-20/Underworld:", + " - Succubus (50%)", + " - Lesser Incubus (50%)", + " Level 21+:", + " - Incubus (100%)", + "- 7.5% Bless 1 Piece of Worn Equipment", + "- 2.5% Bless All Worn Equipment", + "", + "Those skilled in alchemy avoid the", + "summoning of a foocubus when filling", + "a bottle with fountain liquids.", + "", + "The otherworldly presence causes the", + "fountain to refill, allowing multiple", + "attempts to collect more potions." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "gnomish mines": { + "blurb": [ + "If the mines beneath Hamlet ever held", + "gold, stories about it may've begun here.", + "", + "When a debt of gnomes descends on the hunt", + "for riches, their greed cannot be satisfied." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Sphinx's Casque", + "- Dozens of gold veins to mine", + "", + "The greatest reward to be found", + "here may be the experience earned", + "by slaying so many gnomes!", + "", + "Lightning magicstaffs carried by", + "gnomes are deadly, but proper", + "ambushes can put the staffs in the", + "hands of the adventurers, and turn", + "the tides in their favor.", + "Be sneaky to survive." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "gravestone": { + "blurb": [ + "Often chiseled with epitaphs, a GRAVE", + "may result in a ghoul rising from the", + "earth when disturbed. Some adventurers", + "relish the opportunity to slay, or", + "perhaps befriend, emergent undead." + ], + "details": [ + "GRAVESTONE USE POSSIBILITIES:", + "- 25% Disturb Ghoul", + " Level 1-15:", + " - Ghoul", + " Level 16+/Haunted Castle:", + " - Enslaved Ghoul", + "", + "If a ghoul is not disturbed by reading", + "the gravestone's epitaph, it otherwise", + "only acts as an obstacle.", + "", + "If a trap or puzzle activates a", + "gravestone, it will always produce", + "an undead ghoul." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "hall of trials": { + "blurb": [ + "Founded by unaffiliated adventurers,", + "the HALL OF TRIALS was established shortly", + "after Herx's imprisonment in the Devils'", + "Bastion. Nobody knows what became of", + "its benefactors." + ], + "details": [ + "While some adventurers seek to earn", + "diplomas from the Hall of Trials all", + "at once, many prefer to revisit the", + "mysterious academy from time to time,", + "mastering disciplines gradually.", + "", + "The greatest thing to be gained here", + "is knowledge. Student awards can", + "open more learning opportunities.", + "", + "While no single guild built it,", + "the Hall of Trials is the first effort", + "of the movement of united guilds,", + "whose objective is to defeat chaos." + ], + "details_line_highlights": [] + }, + "hamlet": { + "blurb": [ + "The reclaimed HAMLET has been", + "hastily renovated by the recently", + "freed prisoners of the mines.", + "", + "In this district nearest the Minehead,", + "the Magicians' Guild holds high prominence." + ], + "details": [ + "Friendly heroes have unparalleled", + "access to these kinds of Hamlet shops:", + "- 2x Arms & Armor", + "- General Store", + "- Jeweler", + "- Magicstaff", + "- Bookstore", + "- Alchemist", + "- Delicatessen", + "", + "Monsters traveling to Hamlet will be", + "attacked on sight. The inn bunkroom", + "conveniently hosts a sewer entrance,", + "through which one can travel safely", + "to the back of the Magicians' Guild.", + "", + "Hidden below, there are polymorph", + "potions, allowing them to shop freely." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "haunted castle": { + "blurb": [ + "Before he was sealed away, the citizens", + "of Hamlet were enslaved by the Baron", + "to build this CASTLE for him to occupy.", + "", + "The walls were formed by stone", + "excavated by the miners." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Oracle's Treads", + "- Hidden treasure chests", + "- 4 Potions of levitation", + "", + "Ghouls here are cursed to defend", + "the Baron's possessions, and rise when", + "his things are disturbed. Prior to", + "looting, plan the escape or defense!", + "", + "Secret treasure chests are", + "hidden behind a waterfall.", + "Explore the castle towers thoroughly", + "and find how to reveal the way." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "hell": { + "blurb": [ + "A dimension crafted for human suffering,", + "but also home to many magical beasts", + "and monsters.", + "", + "Here vile devils seek ways to dominate", + "the realms of mortals and immortals alike." + ], + "details": [ + "INHABITANTS:", + "Succubi, Incubi, Imps, Demons,", + "Shadows, Goatmen", + "", + "NEW TRAP:", + "Summoning Trap", + "", + "Few survive Hell for long.", + "Dodge spells with swift feet while", + "watching for spike traps. Arrows traps", + "might push adventurers into lava!", + "", + "To survive one must think quickly.", + "Cure ailment will aleviate burning." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "herx lair": { + "blurb": [ + "The ancient ruins descend into a place", + "that channels the flow of primordial power.", + "", + "Those with the will to control it can", + "exert their influence here, as the", + "veil between realms is thin." + ], + "details": [ + "If an adventurer brings mystic orbs", + "to this place, they can be placed on", + "pedestals in the northern rooms:", + "- Green Orb improves DEX and PER", + "- Red Orb improves STR and CON", + "- Blue Orb improves INT and Magic", + " Resistance", + "", + "Herx waits to surprise his foes", + "until they are approaching the", + "middle of his arena. Keep allies", + "on the outer edge of the room and", + "give yourself time to prepare.", + "Magic Reflection is recommended." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "hunters guild": { + "blurb": [ + "Founded by simple pioneers,", + "the HUNTERS' GUILD now employs Rogues,", + "Hunters, and Ninjas who are capable of", + "snuffing out terrifying threats with", + "well-organized efficiency." + ], + "details": [ + "Members of the hunters' guild rely on", + "ambushing to do their good work. They", + "regard this as the least costly,", + "the least risky, and thus the most", + "moral method to serve civilization.", + "", + "Members offer death as their first", + "solution. This has earned them a", + "reputation as death worshippers.", + "", + "In their own words:", + " Death is necessary for life.", + " Our duty is to choose the", + " right recipients for each." + ], + "details_line_highlights": [] + }, + "labyrinth": { + "blurb": [ + "Geomancers theorize that the waters", + "of the Swamps once burbled up from", + "a massive subterranean ocean.", + "", + "However, miners found only dry sand", + "and ruins in the depths below." + ], + "details": [ + "INHABITANTS:", + "Scarabs, Scorpions, Lesser Insectoids,", + "Goblins, Trolls", + "", + "NEW TRAP:", + "Spike Trap", + "", + "With large, winding mazes, the", + "labyrinths are quite deadly when", + "paired with two threats:", + "Starvation and minotaurs.", + "Magic mapping can help with speedy", + "exploration. Bring a method for", + "digging to open hidden paths." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "lava": { + "blurb": [ + "It's liquid-hot magma!", + "", + "Anything that falls in is", + "engulfed in flame and destroyed,", + "including clumsy adventurers." + ], + "details": [ + "Lava is a deadly obstacle with", + "little reason to attempt swimming in", + "it, setting ablaze whatever enters.", + "", + "Automatons who really want to", + "increase their temperature might", + "find a short dip to be useful - in spite", + "of the rapid damage that lava causes.", + "", + "Waterwalking allows adventurers", + "and their followers to avoid sinking", + "into the surface of lava.", + "", + "Should've called it liquidwalking." + ], + "details_line_highlights": [] + }, + "lever": { + "blurb": [ + "LEVERS are reliable tools", + "for a dungeon architect.", + "", + "Using hidden mechanisms, the power of", + "leverage can activate anything from traps", + "to doorways and even mystical gadgets." + ], + "details": [ + "Some levers have a timed effect,", + "requiring them to be cranked up", + "multiple times before activating.", + "Often these are attached to a", + "challenge which demands speed.", + "", + "Sometimes lever mechanisms overlap", + "with hidden machinery attached to", + "other dungeon traps. These overlaps", + "can be confusing but may also provide", + "emergent behaviors to exploit.", + "", + "Player ghosts can pull levers, allowing", + "some challenges to be cheated." + ], + "details_line_highlights": [] + }, + "magic trap": { + "blurb": [ + "These magical pylons once acted", + "as channels of magic for those", + "who lived here to use.", + "", + "They have since been corrupted", + "into beacons of chaos." + ], + "details": [ + "MAGIC TRAP TYPES:", + "- Forcebolt", + "- Cold", + "- Fireball", + "- Lightning", + "- Magic Missile", + "- Sleep", + "- Slow", + "- Confuse", + "", + "Some adventurers have the means to", + "dig these traps to break them, which", + "can greatly simplify exploration.", + "If guiding followers, avoid confusion", + "traps! Cure ailment saves friendships." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "magicians guild": { + "blurb": [ + "Fostering innovation and ambition, the", + "MAGICIANS' GUILD trains Arcanists,", + "Healers, and Wizards.", + "", + "A paradoxical source of time-honored", + "wisdom and ill-conceived revolution." + ], + "details": [ + "Agents of the magicians' guild are", + "are adaptable spellcasters, able", + "to read and memorize most spells,", + "as long as they have been practicing.", + "", + "Most magicians have secondary skills", + "which allow them to reserve magic for", + "when a spell is truly necessary.", + "", + "They follow a complex philosophy of", + "the makeup of the universe, pondering", + "how each element combines to account", + "for material reality, the spirit realm,", + "as well as human virtues and vices." + ], + "details_line_highlights": [] + }, + "masons guild": { + "blurb": [ + "Originally a guild of stoneworkers, the", + "Masons' Guild's role in communities now", + "includes all manner of work that", + "requires organized use of might.", + "Barbarians, Warriors, and Wanderers", + "often place membership." + ], + "details": [ + "A proud organization of workers", + "and soldiers who are certain that", + "they're the foundation of", + "civilization itself.", + "", + "While other guilds may disagree", + "on that point, none deny that their", + "mighty arms and strong backs are to", + "thank for the settlement and", + "fortification of all great cities.", + "", + "Members of the masons' guild believe", + "in their duty to protect peace and", + "build new, fruitful futures." + ], + "details_line_highlights": [] + }, + "merchants guild": { + "blurb": [ + "An organization of Merchants, Mechanists,", + "and Brewers, the MERCHANTS' GUILD is", + "quietly respected as the most", + "powerful guild in the realm.", + "", + "Some say Herx was a member long ago." + ], + "details": [ + "Merchants' guild members are defined", + "by their resourcefulness. While often", + "lacking in might or arcane talent, they", + "rely on found items more than others.", + "", + "The guild shepherds associates to", + "identify and exploit tools they have", + "at their disposal to ensure success.", + "", + "In parties, merchants' guild sponsored", + "classes are able to discover solutions", + "that traditional adventurers don't", + "often consider. Adaptability is key." + ], + "details_line_highlights": [] + }, + "minehead": { + "blurb": [ + "Just through the ruins of Hamlet", + "lies the MINEHEAD where citizens", + "slaved away for the Baron.", + "", + "Entering this place is a one-way trip unless", + "the curse on Hamlet can be broken." + ], + "details": [ + "Many adventuring parties camp here", + "as they prepare for their attempt at", + "lifting the Baron's curse.", + "", + "A convenient location to plan and", + "trade equipment or food, preparing", + "for the dangerous path ahead.", + "", + "Allied spellcasters can benefit from", + "sharing spellbooks. As long as they", + "have the durability, memorizing each", + "others' spellbooks multiplies their", + "benefit to a party." + ], + "details_line_highlights": [] + }, + "mines": { + "blurb": [ + "Initially mined for iron, and later scoured", + "in a fruitless search for gold, the MINES", + "became the home of Baron Herx's", + "mercenary army as they imprisoned the", + "Hamlet miners." + ], + "details": [ + "INHABITANTS:", + "Rats, Skeletons, Spiders, Trolls,", + "Humans", + "", + "NEW TRAP:", + "Boulder Trap", + "", + "The mines will test your basic", + "dungeoning skills, including ambushes,", + "blocking, keeping an eye out for traps", + "and managing your food supply.", + "", + "Avoid fights you can't win, and learn", + "enemy weaknesses to shorten fights." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "minetown": { + "blurb": [ + "A place of respite within the Mines,", + "hidden from Herx's army. Thought to be", + "a myth by Hamlet miners, history doesn't", + "say if the hopeful people built MINETOWN,", + "or if they discovered it." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Four random shops", + "- Four treasure chests", + "- Several humans to recruit", + "", + "Accessing the hidden treasures in", + "Minetown requires a way to get past", + "the gates which guard the lake.", + "Open, dig, pickaxes or even teleport", + "may do the job.", + "", + "Four shops means four shopkeepers.", + "Minetown can be dangerous to visit", + "for ill-prepared monsters." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "minotaur maze": { + "blurb": [ + "Myths tell of a maze that hides", + "artifacts of unmatched power.", + "", + "The high walls and broad hallways are", + "perfectly sized for a terrifyingly large", + "protector who would guard such treasure." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Red Mystic Orb", + "- Gungnir", + "", + "For most, it is wise to grab the orb", + "and outrun the minotaur to the exit.", + "Look for nearby gatehouse levers if", + "closed gates block the way. A spell", + "of opening, or digging, can help!", + "", + "The pedestal at the end of the maze", + "cripples the minotaur temporarily", + "when the orb is placed upon it.", + "Don't forget to reclaim the orb!" + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "molten throne": { + "blurb": [ + "The seat of Baphomet's power.", + "This devil has chosen a location where", + "magic may flow more easily to the mortal", + "realm, allowing his influence to seep in.", + "", + "Other realms may similarly be close by." + ], + "details": [ + "While Baphomet is a being of great", + "power, an adventurer invading his", + "realm is not what he expects. Herx", + "is his intended puppet with whom he", + "sought to corrupt the mortal world.", + "", + "Regardless, even caught unprepared,", + "his mastery of fiery brimstone with", + "the command of evil demons and spirits", + "guarantee he won't submit easily.", + "", + "If he is defeated, beware of rival", + "devils sending minotaur agents to", + "claim his throne for themselves." + ], + "details_line_highlights": [] + }, + "murky water": { + "blurb": [ + "Most denizens are careful not to", + "enter foul pools of dungeon water.", + "", + "Filled with dense rot, just about", + "anything will float on it, including", + "adventurers with places to go." + ], + "details": [ + "While murky water is usually a trivial", + "obstacle, adventurers should avoid", + "combat while swimming.", + "", + "An amulet of olympic swimming can", + "all but eliminate the inconvenience", + "of having to swim, and can save lives", + "if a hasty swimming escape is needed.", + "", + "Water will put out fires, and washes", + "away the effects of polymorph.", + "Monsters should make sure their", + "shopping is done before falling in." + ], + "details_line_highlights": [] + }, + "mystic library": { + "blurb": [ + "The Magicians' Guild still", + "holds a strange connection to", + "the MYSTIC LIBRARY, a pocket dimension", + "accessible by magic from the ruins", + "buried deep beneath Hamlet." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Blue Mystic Orb", + "- Chests of random magic items", + "- Several humans to recruit", + "", + "The library is filled with rewards", + "for friendly adventurers. Use dig and", + "levitate to explore for hidden chests.", + "", + "Caches of scrolls, spellbooks, and", + "magicstaffs can help late-blooming", + "magic-users to mature their craft.", + "Non-magic users can still benefit", + "from many of the scrolls." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "obelisk": { + "blurb": [ + "The shapeless void of the Underworld", + "can be confusing to traverse.", + "", + "These shrines seize upon hapless spirits", + "when touched, carrying them off to", + "another part of the realm." + ], + "details": [ + "While initially random, obelisks lead", + "to each room in sequence, eventually", + "looping back around to the start.", + "", + "This does enable exploration of the", + "underworld's sea of pits, but also", + "sends adventurers along with no", + "no warning of what awaits them on the", + "other side.", + "", + "Some adventurers ready potions,", + "stealth, or a raised shield. Others", + "send a follower through first, waiting", + "to see if they survive the journey." + ], + "details_line_highlights": [] + }, + "pits": { + "blurb": [ + "Ambling denizens are wise enough", + "not to fling themselves into the", + "vast emptiness of PITS.", + "Those who defy gravity should make", + "sure to land on solid ground,", + "or face certain death." + ], + "details": [ + "Pits often taunt adventurers with", + "unreachable treasures on the other", + "side.", + "", + "While levitation is a great", + "solution, often one might also find", + "access by digging through walls or", + "opening gates on the other side.", + "", + "Adventuring parties may find pits", + "to be a convenient way to dispose of", + "unwanted items, making sure allies", + "don't waste time picking through", + "piles of cursed junk." + ], + "details_line_highlights": [] + }, + "portcullis": { + "blurb": [ + "A portcullis that can only be", + "passed by raising its iron bars.", + "", + "Most often, a nearby lever will operate it,", + "though crafty magic may also do the job." + ], + "details": [ + "Not all gates have a lever that", + "operates them. A spell of opening", + "will allow adventurers to bypass", + "many dungeon challenges and traps.", + "", + "Since only the minotaur can shatter", + "gates, using a lever to close an open", + "gate is a very reliable way to keep", + "monsters away if you need a moment.", + "", + "Some gates are held closed by a", + "mechanism, rather than being held", + "open by one. These gates cannot be", + "opened with magic." + ], + "details_line_highlights": [] + }, + "ruins": { + "blurb": [ + "No one knows which ancient tribe", + "built these enchanted RUINS.", + "", + "Legends tell of a magical catastrophe that", + "once sent an entire city to the underworld", + "forever. Perhaps this is that place." + ], + "details": [ + "INHABITANTS:", + "Demons, Gnomes, Young Vampires,", + "Bugbears, Damaged Automatons", + "", + "NEW TRAP:", + "Magic Trap", + "", + "The ruins' open spaces are far easier", + "to navigate than the labyrinths, but", + "the threats may come from anywhere.", + "Situational awareness and decisive", + "damage are essential for success.", + "", + "Traverse pits to discover secrets." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "shop": { + "blurb": [ + "SHOPS are recognizable no", + "matter where they're found.", + "", + "Even monsters know not to threaten", + "shopkeepers, but they won't mind if you", + "rummage through their chests for freebies." + ], + "details": [ + "SHOP TYPES:", + "- Arms & Armor", + "- Hats", + "- Hunting Supply", + "- Delicatessen", + "- Alchemist", + "- Hardware Store", + "- Magicstaffs", + "- Bookstore", + "- Jeweler", + "- General Store", + "", + "Each shop has a sign signifying", + "the kind of wares it trades in.", + "Only general stores buy all goods.", + "", + "Usually it's a good idea to spend!", + "Shops may be rare, so taking the", + "opportunity to buy supplies and earn", + "trading skill is often a wise choice.", + "", + "With some trade skill, and charisma,", + "shoppers can access a shop's private", + "stock of consumables to help stay", + "in good health and keep gear keen.", + "A shop's private stock varies based on", + "the shop type, often complementing the", + "goods available for trade.", + "", + "Monsters, and adventurers marked", + "with the WANTED status, are attacked", + "on-sight by shopkeepers.", + "Avoid collateral damage by making", + "sure you are not between a", + "shopkeeper and his target." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "sink": { + "blurb": [ + "SINK water may restore a little hunger,", + "but the water can be scalding, slimes can", + "hide in the pipes, and the plumbing", + "quickly breaks.", + "", + "Use with empty bottles for best results." + ], + "details": [ + "SINK USE POSSIBILITIES:", + "- 60% Fresh Water", + "- 20% Scalding Water", + "- 10% Slime", + "- 10% Find Ring", + "", + "Fresh sink water, served at the proper", + "temperature, restores 1 HP. Those who", + "have recently eaten a filling meal", + "should take care when drinking from", + "sinks so as not to be over-filled.", + "", + "If a slime emerges while tapping with", + "an empty bottle, acid will be collected." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "sokoban": { + "blurb": [ + "Those who enter here say they feel", + "watched and judged, as though they", + "are being tested.", + "Cynics claim it is a trick by cruel", + "gods who enjoy watching mortals", + "starve to death." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Djinn's Brace", + "- A fortune in gold pieces", + "", + "To solve this challenge, one must rid", + "the room of all boulders using a ring", + "of might or potion of strength.", + "", + "Pushing all boulders into pits is the", + "path to victory, avoid letting them", + "become stuck in corners - boulders", + "cannot be pulled backward!", + "", + "Rewards will be reduced if boulders", + "are destroyed to complete the puzzle." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "spike trap": { + "blurb": [ + "Somebody was either very cruel or", + "terrified of something very large when", + "they created these brutal devices.", + "", + "They are easy enough to spot and avoid...", + "if you have time to watch your step." + ], + "details": [ + "Spikes deal 50 damage, regardless", + "of the victim's armor or constitution.", + "Many spike traps trigger when walked", + "on or over, regardless of levitation.", + "", + "Neutralize spikes by tossing", + "an unwanted item on the trap, and", + "they will not normally rearm until", + "the item is removed. This will help", + "followers survive as they wander.", + "", + "These traps are only dangerous when", + "thrusting from below. Navigate", + "through raised spikes to provide more", + "time to maneuver certain challenges!" + ], + "details_line_highlights": [] + }, + "summoning trap": { + "blurb": [ + "Empowered by arcane rituals the", + "SUMMONING TRAP can turn an empty room", + "into a deadly battlefield in an instant.", + "", + "Take care when near these symbols.", + "They can be activated in many ways." + ], + "details": [ + "The symbol on the floor signals", + "the presence of this trap, but", + "drawing near, operating a lever,", + "or taking an offering from the floor", + "may all trigger the summoning.", + "", + "Loud booms accompany activation", + "as demonic spirits transfer.", + "", + "Some zealous adventurers like to", + "tempt summon traps, beckoning more", + "hellspawn to slay. But this is a risky", + "prospect, with little way of knowing", + "what kind of fight awaits." + ], + "details_line_highlights": [] + }, + "swamps": { + "blurb": [ + "Dense plants here were obliterated in the", + "effort to expand the Hamlet mines, but", + "the hydrophilic flora quickly recovered.", + "", + "Sadly, the vegetation is known to", + "cause psychosis when eaten." + ], + "details": [ + "INHABITANTS:", + "Spiders, Goblins, Slimes, Ghouls, Trolls", + "", + "NEW TRAP:", + "Arrow Trap", + "", + "The swamps feature new threats", + "that attack at range. Stealth helps!", + "", + "Beware goblins with magicstaffs.", + "Magic reflection is ideal if available.", + "Otherwise, ambush, or fight from afar", + "so you can dodge projectiles.", + "Dip into water to douse flames." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "temple": { + "blurb": [ + "An evil god has heard the prayers of", + "swamp goblins and has provided a place", + "on the surface for them to make blood", + "sacrifices. A deadly path to this TEMPLE", + "is provided for worship." + ], + "details": [ + "SECRET LEVEL TREASURES:", + "- Green Mystic Orb", + "- 4 Potions of strength", + "- A hoard of gold pieces", + "- Several treasure chests", + "", + "Hidden rooms make use of the orb's", + "power, revealing more treasures.", + "The Temple's traps may shut down", + "access to secret areas if explorers", + "are careless, or prisoners are freed.", + "", + "It is wise to take time to plan ahead", + "to avoid blunders in the Temple." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + }, + "the church": { + "blurb": [ + "Officials of THE CHURCH play a passive", + "cultural role until things get out of hand.", + "", + "Clerics, Monks, and Sextons act as", + "agents of action when wrongs need", + "to be made right." + ], + "details": [ + "Members of the church see the events", + "of the world as a tapestry of great", + "purpose, and devote their lives seeking", + "to understand and uphold it.", + "", + "With little ambition, officers usually", + "withdraw from public affairs. But no", + "other organization is so unified as the", + "church when they find the course of", + "history is straying from its purpose.", + "", + "Church agents tend to be practical,", + "embracing physical and magic talents", + "that provide uncomplicated utility." + ], + "details_line_highlights": [] + }, + "transition floor": { + "blurb": [ + "Each layer of the dungeons was once", + "newly discovered, as miners dug deeper.", + "", + "Natural fissures formed between layers,", + "requiring a bridge to be built, often", + "taking weeks to finish." + ], + "details": [ + "There is a strange sense of peace", + "that occupies transition floors,", + "causing adventurers to hunger at a", + "much slower rate than normal.", + "The perfect spot to take a break.", + "", + "Be warned, mimics may still take", + "the place of treasure chests here.", + "", + "The transition floor between the", + "mines and swamps contains a hidden", + "portal to the underworld.", + "If one wishes to risk a visit, some", + "method of digging is needed." + ], + "details_line_highlights": [] + }, + "underworld": { + "blurb": [ + "These catacombs are the first destination", + "for souls who have recently died.", + "", + "Depending on how they lived, the other", + "denizens of the Underworld may cast them", + "further down into Hell." + ], + "details": [ + "INHABITANTS:", + "Skeletons, Slimes, Ghouls, Imps,", + "Shadows", + "", + "SECRET LEVEL TREASURES:", + "- Khryselakatos (Lvl 7)", + "", + "The underworld can be accessed tiwce", + "via secret entrances on floor 5 and 18.", + "In both cases, adventurers who find", + "a trapdoor down to a second floor of", + "the underworld will find a deadly", + "reward awaiting them.", + "", + "Beware, most foes attack across pits." + ], + "details_line_highlights": [ + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + }, + {}, + { + "color": { + "r": 198, + "g": 190, + "b": 179, + "a": 255 + } + }, + { + "color": { + "r": 157, + "g": 129, + "b": 93, + "a": 255 + } + } + ] + } +} \ No newline at end of file diff --git a/src/init_game.cpp b/src/init_game.cpp index 8d59166fe..f8a4dd2a2 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -99,6 +99,11 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readWorldFromFile(); CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); + CompendiumEntries.readMonstersTranslationsFromFile(); + CompendiumEntries.readCodexTranslationsFromFile(); + CompendiumEntries.readWorldTranslationsFromFile(); + CompendiumEntries.readItemsTranslationsFromFile(); + CompendiumEntries.readMagicTranslationsFromFile(); Compendium_t::AchievementData_t::readContentsLang(); Compendium_t::Events_t::readEventsTranslations(); Compendium_t::readUnlocksSaveData(); diff --git a/src/interface/consolecommand.cpp b/src/interface/consolecommand.cpp index 4fb864097..a59c4592e 100644 --- a/src/interface/consolecommand.cpp +++ b/src/interface/consolecommand.cpp @@ -4981,19 +4981,25 @@ namespace ConsoleCommands { static ConsoleCommand ccmd_reloadcompendiummonsters("/reloadcompendiummonsters", "reloads compendium entries", []CCMD{ CompendiumEntries.readMonstersFromFile(); + CompendiumEntries.readMonstersTranslationsFromFile(); }); static ConsoleCommand ccmd_reloadcompendiumworld("/reloadcompendiumworld", "reloads compendium entries", []CCMD{ CompendiumEntries.readWorldFromFile(); + CompendiumEntries.readWorldTranslationsFromFile(); + }); static ConsoleCommand ccmd_reloadcompendiumcodex("/reloadcompendiumcodex", "reloads compendium entries", []CCMD{ CompendiumEntries.readCodexFromFile(); + CompendiumEntries.readCodexTranslationsFromFile(); }); static ConsoleCommand ccmd_reloadcompendiumitems("/reloadcompendiumitems", "reloads compendium entries", []CCMD{ CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); + CompendiumEntries.readItemsTranslationsFromFile(); + CompendiumEntries.readMagicTranslationsFromFile(); }); static ConsoleCommand ccmd_reloadcompendiumevents("/reloadcompendiumevents", "reloads compendium entries", []CCMD{ diff --git a/src/mod_tools.cpp b/src/mod_tools.cpp index 59388ff2c..59b1fef26 100644 --- a/src/mod_tools.cpp +++ b/src/mod_tools.cpp @@ -11090,7 +11090,72 @@ void Compendium_t::updateTooltip() } } -void Compendium_t::readItemsFromFile() +void Compendium_t::readItemsTranslationsFromFile(bool forceLoadBaseDirectory) +{ + const std::string filename = "lang/compendium_lang/lang_items.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readItemsTranslationsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[120000]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + for ( auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr ) + { + std::string key = itr->name.GetString(); + auto find = items.find(key); + if ( find != items.end() ) + { + find->second.blurb.clear(); + if ( itr->value.HasMember("blurb") ) + { + jsonVecToVec(itr->value["blurb"], find->second.blurb); + } + } + } +} + +void Compendium_t::readItemsFromFile(bool forceLoadBaseDirectory) { const std::string filename = "data/compendium/comp_items.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) @@ -11100,6 +11165,22 @@ void Compendium_t::readItemsFromFile() } std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readItemsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + inputPath.append(PHYSFS_getDirSeparator()); inputPath.append(filename.c_str()); @@ -11110,7 +11191,7 @@ void Compendium_t::readItemsFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11137,7 +11218,10 @@ void Compendium_t::readItemsFromFile() auto& w = itr->value; auto& obj = items[name]; - jsonVecToVec(w["blurb"], obj.blurb); + if ( w.HasMember("blurb") ) + { + jsonVecToVec(w["blurb"], obj.blurb); + } for ( auto itr = w["items"].Begin(); itr != w["items"].End(); ++itr ) { for ( auto itr2 = itr->MemberBegin(); itr2 != itr->MemberEnd(); ++itr2 ) @@ -11293,9 +11377,111 @@ void Compendium_t::readItemsFromFile() } } } + + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + rapidjson::Document d; + d.SetObject(); + for ( auto& obj : items ) + { + rapidjson::Value entry(rapidjson::kObjectType); + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.blurb ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("blurb", arr, d.GetAllocator()); + } + + d.AddMember(rapidjson::Value(obj.first.c_str(), d.GetAllocator()), entry, d.GetAllocator()); + } + + char path[PATH_MAX] = ""; + completePath(path, "lang/compendium_lang/lang_items.json", outputdir); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + d.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + }*/ +} + +void Compendium_t::readMagicTranslationsFromFile(bool forceLoadBaseDirectory) +{ + const std::string filename = "lang/compendium_lang/lang_magic.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readMagicTranslationsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[120000]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + for ( auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr ) + { + std::string key = itr->name.GetString(); + auto find = magic.find(key); + if ( find != magic.end() ) + { + find->second.blurb.clear(); + if ( itr->value.HasMember("blurb") ) + { + jsonVecToVec(itr->value["blurb"], find->second.blurb); + } + } + } } -void Compendium_t::readMagicFromFile() +void Compendium_t::readMagicFromFile(bool forceLoadBaseDirectory) { const std::string filename = "data/compendium/comp_magic.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) @@ -11305,6 +11491,22 @@ void Compendium_t::readMagicFromFile() } std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readItemsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + inputPath.append(PHYSFS_getDirSeparator()); inputPath.append(filename.c_str()); @@ -11315,7 +11517,7 @@ void Compendium_t::readMagicFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -11359,8 +11561,10 @@ void Compendium_t::readMagicFromFile() auto& w = itr->value; auto& obj = magic[name]; - jsonVecToVec(w["blurb"], obj.blurb); - + if ( w.HasMember("blurb") ) + { + jsonVecToVec(w["blurb"], obj.blurb); + } std::set objSpellsLookup; for ( auto itr = w["items"].Begin(); itr != w["items"].End(); ++itr ) { @@ -11683,11 +11887,48 @@ void Compendium_t::readMagicFromFile() } } } + + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + rapidjson::Document d; + d.SetObject(); + for ( auto& obj : magic ) + { + rapidjson::Value entry(rapidjson::kObjectType); + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.blurb ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("blurb", arr, d.GetAllocator()); + } + + d.AddMember(rapidjson::Value(obj.first.c_str(), d.GetAllocator()), entry, d.GetAllocator()); + } + + char path[PATH_MAX] = ""; + completePath(path, "lang/compendium_lang/lang_magic.json", outputdir); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + d.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + }*/ } -void Compendium_t::readCodexFromFile() +void Compendium_t::readCodexTranslationsFromFile(bool forceLoadBaseDirectory) { - const std::string filename = "data/compendium/codex.json"; + const std::string filename = "lang/compendium_lang/lang_codex.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) { printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); @@ -11695,6 +11936,22 @@ void Compendium_t::readCodexFromFile() } std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readCodexTranslationsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + inputPath.append(PHYSFS_getDirSeparator()); inputPath.append(filename.c_str()); @@ -11713,60 +11970,191 @@ void Compendium_t::readCodexFromFile() rapidjson::Document d; d.ParseStream(is); - if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("codex") ) + if ( !d.IsObject() ) { printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); return; } - codex.clear(); - - Compendium_t::Events_t::eventCodexIDLookup.clear(); - Compendium_t::Events_t::eventCodexLookup.clear(); - Compendium_t::Events_t::codexIDToString.clear(); - Compendium_t::CompendiumCodex_t::readContentsLang(); - - auto& entries = d["codex"]; - for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + for ( auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr ) { - std::string name = itr->name.GetString(); - auto& w = itr->value; - auto& obj = codex[name]; - - obj.id = w["event_lookup"].GetInt(); - jsonVecToVec(w["blurb"], obj.blurb); - jsonVecToVec(w["details"], obj.details); - obj.imagePath = w["img"].GetString(); - if ( w.HasMember("enable_tutorial") ) - { - obj.enableTutorial = w["enable_tutorial"].GetBool(); - } - if ( w.HasMember("rendered_imgs") ) - { - jsonVecToVec(w["rendered_imgs"], obj.renderedImagePaths); - } - if ( w.HasMember("models") ) - { - jsonVecToVec(w["models"], obj.models); - } - if ( w.HasMember("lore_points") ) - { - obj.lorePoints = w["lore_points"].GetInt(); - } - obj.linesToHighlight.clear(); - for ( auto& line : obj.details ) + std::string key = itr->name.GetString(); + auto find = codex.find(key); + if ( find != codex.end() ) { - if ( line.size() > 0 ) + find->second.blurb.clear(); + if ( itr->value.HasMember("blurb") ) { - if ( line[0] == '-' ) - { - line[0] = '\x1E'; - } - else + jsonVecToVec(itr->value["blurb"], find->second.blurb); + } + find->second.details.clear(); + if ( itr->value.HasMember("details") ) + { + jsonVecToVec(itr->value["details"], find->second.details); + } + for ( auto& line : find->second.details ) + { + if ( line.size() > 0 ) { - for ( size_t c = 0; c < line.size(); ++c ) + if ( line[0] == '-' ) { - if ( line[c] == '-' ) + line[0] = '\x1E'; + } + else + { + for ( size_t c = 0; c < line.size(); ++c ) + { + if ( line[c] == '-' ) + { + line[c] = '\x1E'; + break; + } + else if ( line[c] != ' ' ) + { + break; + } + } + } + } + } + + find->second.linesToHighlight.clear(); + if ( itr->value.HasMember("details_line_highlights") ) + { + for ( auto itr2 = itr->value["details_line_highlights"].Begin(); itr2 != itr->value["details_line_highlights"].End(); ++itr2 ) + { + if ( itr2->HasMember("color") ) + { + Uint8 r, g, b; + if ( (*itr2)["color"].HasMember("r") ) + { + r = (*itr2)["color"]["r"].GetInt(); + } + if ( (*itr2)["color"].HasMember("g") ) + { + g = (*itr2)["color"]["g"].GetInt(); + } + if ( (*itr2)["color"].HasMember("b") ) + { + b = (*itr2)["color"]["b"].GetInt(); + } + + find->second.linesToHighlight.push_back(makeColorRGB(r, g, b)); + } + else + { + find->second.linesToHighlight.push_back(0); + } + } + } + } + } +} + +void Compendium_t::readCodexFromFile(bool forceLoadBaseDirectory) +{ + const std::string filename = "data/compendium/codex.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readCodexFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[120000]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() || !d.HasMember("version") || !d.HasMember("codex") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + codex.clear(); + + Compendium_t::Events_t::eventCodexIDLookup.clear(); + Compendium_t::Events_t::eventCodexLookup.clear(); + Compendium_t::Events_t::codexIDToString.clear(); + Compendium_t::CompendiumCodex_t::readContentsLang(); + + auto& entries = d["codex"]; + for ( auto itr = entries.MemberBegin(); itr != entries.MemberEnd(); ++itr ) + { + std::string name = itr->name.GetString(); + auto& w = itr->value; + auto& obj = codex[name]; + + obj.id = w["event_lookup"].GetInt(); + if ( w.HasMember("blurb") ) + { + jsonVecToVec(w["blurb"], obj.blurb); + } + if ( w.HasMember("details") ) + { + jsonVecToVec(w["details"], obj.details); + } + obj.imagePath = w["img"].GetString(); + if ( w.HasMember("enable_tutorial") ) + { + obj.enableTutorial = w["enable_tutorial"].GetBool(); + } + if ( w.HasMember("rendered_imgs") ) + { + jsonVecToVec(w["rendered_imgs"], obj.renderedImagePaths); + } + if ( w.HasMember("models") ) + { + jsonVecToVec(w["models"], obj.models); + } + if ( w.HasMember("lore_points") ) + { + obj.lorePoints = w["lore_points"].GetInt(); + } + obj.linesToHighlight.clear(); + for ( auto& line : obj.details ) + { + if ( line.size() > 0 ) + { + if ( line[0] == '-' ) + { + line[0] = '\x1E'; + } + else + { + for ( size_t c = 0; c < line.size(); ++c ) + { + if ( line[c] == '-' ) { line[c] = '\x1E'; break; @@ -11888,7 +12276,132 @@ void Compendium_t::readCodexFromFile() } } -void Compendium_t::readWorldFromFile() +void Compendium_t::readWorldTranslationsFromFile(bool forceLoadBaseDirectory) +{ + const std::string filename = "lang/compendium_lang/lang_world.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readWorldTranslationsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[120000]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + for ( auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr ) + { + std::string key = itr->name.GetString(); + auto find = worldObjects.find(key); + if ( find != worldObjects.end() ) + { + find->second.blurb.clear(); + if ( itr->value.HasMember("blurb") ) + { + jsonVecToVec(itr->value["blurb"], find->second.blurb); + } + find->second.details.clear(); + if ( itr->value.HasMember("details") ) + { + jsonVecToVec(itr->value["details"], find->second.details); + } + for ( auto& line : find->second.details ) + { + if ( line.size() > 0 ) + { + if ( line[0] == '-' ) + { + line[0] = '\x1E'; + } + else + { + for ( size_t c = 0; c < line.size(); ++c ) + { + if ( line[c] == '-' ) + { + line[c] = '\x1E'; + break; + } + else if ( line[c] != ' ' ) + { + break; + } + } + } + } + } + + find->second.linesToHighlight.clear(); + if ( itr->value.HasMember("details_line_highlights") ) + { + for ( auto itr2 = itr->value["details_line_highlights"].Begin(); itr2 != itr->value["details_line_highlights"].End(); ++itr2 ) + { + if ( itr2->HasMember("color") ) + { + Uint8 r, g, b; + if ( (*itr2)["color"].HasMember("r") ) + { + r = (*itr2)["color"]["r"].GetInt(); + } + if ( (*itr2)["color"].HasMember("g") ) + { + g = (*itr2)["color"]["g"].GetInt(); + } + if ( (*itr2)["color"].HasMember("b") ) + { + b = (*itr2)["color"]["b"].GetInt(); + } + + find->second.linesToHighlight.push_back(makeColorRGB(r, g, b)); + } + else + { + find->second.linesToHighlight.push_back(0); + } + } + } + } + } +} + +void Compendium_t::readWorldFromFile(bool forceLoadBaseDirectory) { const std::string filename = "data/compendium/world.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) @@ -11898,6 +12411,22 @@ void Compendium_t::readWorldFromFile() } std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readWorldFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + inputPath.append(PHYSFS_getDirSeparator()); inputPath.append(filename.c_str()); @@ -11936,8 +12465,14 @@ void Compendium_t::readWorldFromFile() auto& obj = worldObjects[name]; obj.id = w["event_lookup"].GetInt(); - jsonVecToVec(w["blurb"], obj.blurb); - jsonVecToVec(w["details"], obj.details); + if ( w.HasMember("blurb") ) + { + jsonVecToVec(w["blurb"], obj.blurb); + } + if ( w.HasMember("details") ) + { + jsonVecToVec(w["details"], obj.details); + } obj.imagePath = w["img"].GetString(); if ( w.HasMember("models") ) { @@ -12079,12 +12614,158 @@ void Compendium_t::readWorldFromFile() } } } + + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + rapidjson::Document d; + d.SetObject(); + for ( auto& obj : worldObjects ) + { + rapidjson::Value entry(rapidjson::kObjectType); + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.blurb ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("blurb", arr, d.GetAllocator()); + } + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.details ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("details", arr, d.GetAllocator()); + } + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& color : obj.second.linesToHighlight ) + { + rapidjson::Value val(rapidjson::kObjectType); + if ( color == 0 ) + { + } + else + { + Uint8 r, g, b, a; + getColor(color, &r, &g, &b, &a); + + rapidjson::Value colorVal(rapidjson::kObjectType); + colorVal.AddMember("r", r, d.GetAllocator()); + colorVal.AddMember("g", g, d.GetAllocator()); + colorVal.AddMember("b", b, d.GetAllocator()); + colorVal.AddMember("a", a, d.GetAllocator()); + + val.AddMember("color", colorVal, d.GetAllocator()); + } + arr.PushBack(val, d.GetAllocator()); + } + entry.AddMember("details_line_highlights", arr, d.GetAllocator()); + } + + d.AddMember(rapidjson::Value(obj.first.c_str(), d.GetAllocator()), entry, d.GetAllocator()); + } + + char path[PATH_MAX] = ""; + completePath(path, "lang/compendium_lang/lang_world.json", outputdir); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + d.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + }*/ } std::map Compendium_t::Events_t::monsterUniqueIDLookup; std::map> Compendium_t::Events_t::eventMonsterLookup; +void Compendium_t::readMonstersTranslationsFromFile(bool forceLoadBaseDirectory) +{ + const std::string filename = "lang/compendium_lang/lang_monsters.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readMonstersTranslationsFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[120000]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.IsObject() ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } -void Compendium_t::readMonstersFromFile() + for ( auto itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr ) + { + std::string key = itr->name.GetString(); + auto find = monsters.find(key); + if ( find != monsters.end() ) + { + find->second.blurb.clear(); + if ( itr->value.HasMember("blurb") ) + { + jsonVecToVec(itr->value["blurb"], find->second.blurb); + } + find->second.abilities.clear(); + if ( itr->value.HasMember("abilities") ) + { + jsonVecToVec(itr->value["abilities"], find->second.abilities); + } + find->second.inventory.clear(); + if ( itr->value.HasMember("inventory") ) + { + jsonVecToVec(itr->value["inventory"], find->second.inventory); + } + } + } +} + +void Compendium_t::readMonstersFromFile(bool forceLoadBaseDirectory) { const std::string filename = "data/compendium/monsters.json"; if ( !PHYSFS_getRealDir(filename.c_str()) ) @@ -12094,6 +12775,22 @@ void Compendium_t::readMonstersFromFile() } std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + if ( forceLoadBaseDirectory ) + { + inputPath = BASE_DATA_DIR; + } + else + { + if ( inputPath != BASE_DATA_DIR ) + { + readMonstersFromFile(true); // force load the base directory first, then modded paths later. + } + else + { + forceLoadBaseDirectory = true; + } + } + inputPath.append(PHYSFS_getDirSeparator()); inputPath.append(filename.c_str()); @@ -12104,7 +12801,7 @@ void Compendium_t::readMonstersFromFile() return; } - char buf[65536]; + char buf[120000]; int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); buf[count] = '\0'; rapidjson::StringStream is(buf); @@ -12203,7 +12900,10 @@ void Compendium_t::readMonstersFromFile() auto& monster = monsters[itr->name.GetString()]; monster.monsterType = monsterType; monster.unique_npc = m.HasMember("unique_npc") ? m["unique_npc"].GetString() : ""; - jsonVecToVec(m["blurb"], monster.blurb); + if ( m.HasMember("blurb") ) + { + jsonVecToVec(m["blurb"], monster.blurb); + } if ( m.HasMember("img") ) { monster.imagePath = m["img"].GetString(); @@ -12269,8 +12969,10 @@ void Compendium_t::readMonstersFromFile() } monster.species = species; } - - jsonVecToVec(m["abilities"], monster.abilities); + if ( m.HasMember("abilities") ) + { + jsonVecToVec(m["abilities"], monster.abilities); + } { int i = 0; for ( auto itr = m["resistances"].Begin(); itr != m["resistances"].End(); ++itr ) @@ -12279,12 +12981,70 @@ void Compendium_t::readMonstersFromFile() ++i; } } - jsonVecToVec(m["inventory"], monster.inventory); + if ( m.HasMember("inventory") ) + { + jsonVecToVec(m["inventory"], monster.inventory); + } if ( m.HasMember("models") ) { jsonVecToVec(m["models"], monster.models); } } + + /*if ( keystatus[SDLK_g] ) + { + keystatus[SDLK_g] = 0; + rapidjson::Document d; + d.SetObject(); + for ( auto& obj : monsters ) + { + rapidjson::Value entry(rapidjson::kObjectType); + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.blurb ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("blurb", arr, d.GetAllocator()); + } + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.abilities ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("abilities", arr, d.GetAllocator()); + } + + { + rapidjson::Value arr(rapidjson::kArrayType); + for ( auto& str : obj.second.inventory ) + { + arr.PushBack(rapidjson::Value(str.c_str(), d.GetAllocator()), d.GetAllocator()); + } + entry.AddMember("inventory", arr, d.GetAllocator()); + } + + d.AddMember(rapidjson::Value(obj.first.c_str(), d.GetAllocator()), entry, d.GetAllocator()); + } + + char path[PATH_MAX] = ""; + completePath(path, "lang/compendium_lang/lang_monsters.json", outputdir); + + File* fp = FileIO::open(path, "wb"); + if ( !fp ) + { + printlog("[JSON]: Error opening json file %s for write!", path); + return; + } + rapidjson::StringBuffer os; + rapidjson::PrettyWriter writer(os); + d.Accept(writer); + fp->write(os.GetString(), sizeof(char), os.GetSize()); + FileIO::close(fp); + }*/ } Uint32 Compendium_t::lastTickUpdate = 0; diff --git a/src/mod_tools.hpp b/src/mod_tools.hpp index 3f0880558..77766b5a2 100644 --- a/src/mod_tools.hpp +++ b/src/mod_tools.hpp @@ -4003,7 +4003,8 @@ struct Compendium_t static int numUnread; }; std::map monsters; - void readMonstersFromFile(); + void readMonstersFromFile(bool forceLoadBaseDirectory = false); + void readMonstersTranslationsFromFile(bool forceLoadBaseDirectory = false); void exportCurrentMonster(Entity* monster); void readModelLimbsFromFile(std::string section); CompendiumView_t defaultCamera; @@ -4047,7 +4048,8 @@ struct Compendium_t static int numUnread; }; std::map worldObjects; - void readWorldFromFile(); + void readWorldFromFile(bool forceLoadBaseDirectory = false); + void readWorldTranslationsFromFile(bool forceLoadBaseDirectory = false); struct CompendiumCodex_t { @@ -4074,7 +4076,8 @@ struct Compendium_t static int numUnread; }; std::map codex; - void readCodexFromFile(); + void readCodexFromFile(bool forceLoadBaseDirectory = false); + void readCodexTranslationsFromFile(bool forceLoadBaseDirectory = false); static const char* compendiumCurrentLevelToWorldString(const int currentlevel, const bool secretlevel); struct CompendiumItems_t @@ -4104,7 +4107,8 @@ struct Compendium_t static int numUnread; }; std::map items; - void readItemsFromFile(); + void readItemsFromFile(bool forceLoadBaseDirectory = false); + void readItemsTranslationsFromFile(bool forceLoadBaseDirectory = false); struct CompendiumMagic_t { @@ -4115,7 +4119,8 @@ struct Compendium_t static int numUnread; }; std::map magic; - void readMagicFromFile(); + void readMagicFromFile(bool forceLoadBaseDirectory = false); + void readMagicTranslationsFromFile(bool forceLoadBaseDirectory = false); static Item compendiumItem; static bool tooltipNeedUpdate; static void updateTooltip(); diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 2f3365039..3e2846cef 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -38962,22 +38962,27 @@ namespace MainMenu { if ( compendium_current == "monsters" ) { CompendiumEntries.readMonstersFromFile(); + CompendiumEntries.readMonstersTranslationsFromFile(); refreshCompendiumEntryMonster(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "world" ) { CompendiumEntries.readWorldFromFile(); + CompendiumEntries.readWorldTranslationsFromFile(); refreshCompendiumEntryWorld(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "codex" ) { CompendiumEntries.readCodexFromFile(); + CompendiumEntries.readCodexTranslationsFromFile(); refreshCompendiumEntryCodex(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium"), false); } else if ( compendium_current == "items" ) { CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); + CompendiumEntries.readItemsTranslationsFromFile(); + CompendiumEntries.readMagicTranslationsFromFile(); refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); std::string modelsPath = "items_single"; if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) @@ -38990,6 +38995,8 @@ namespace MainMenu { { CompendiumEntries.readItemsFromFile(); CompendiumEntries.readMagicFromFile(); + CompendiumEntries.readItemsTranslationsFromFile(); + CompendiumEntries.readMagicTranslationsFromFile(); refreshCompendiumEntryItemsBlurb(compendium_contents_current[compendium_current], main_menu_frame->findFrame("compendium")); std::string modelsPath = "items_single"; if ( Compendium_t::compendiumItemModel.skill[10] == SPELL_ITEM && Compendium_t::compendiumItemModel.flags[SPRITE] ) From e2cca6c8ef76b3b767b86a5483b1fe5565bdbafd Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 12:50:32 +1100 Subject: [PATCH 220/244] * merchant 1->3 remove curse, add 2 scrolls identify --- src/charclass.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/charclass.cpp b/src/charclass.cpp index 65a02dea9..641baa363 100644 --- a/src/charclass.cpp +++ b/src/charclass.cpp @@ -1324,7 +1324,12 @@ void initClass(const int player) free(item); // scroll of remove curse - item = newItem(SCROLL_REMOVECURSE, EXCELLENT, 0, 1, 0, true, nullptr); + item = newItem(SCROLL_REMOVECURSE, EXCELLENT, 0, 3, 0, true, nullptr); + item2 = itemPickup(player, item); + free(item); + + // scroll of identify + item = newItem(SCROLL_IDENTIFY, EXCELLENT, 0, 2, 0, true, nullptr); item2 = itemPickup(player, item); free(item); From bb1e2e1416eca1a0c6098a0dbe43fe1917ef5f2f Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 12:53:02 +1100 Subject: [PATCH 221/244] * intro level slow hunger --- src/entity.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 471f05d2b..7945f066f 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -2872,7 +2872,8 @@ int Entity::getHungerTickRate(Stat* myStats, bool isPlayer, bool checkItemsEffec || !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9) || !strncmp(map.name, "Mages Guild", 11) - || strstr(map.name, " Transition") ) + || strstr(map.name, " Transition") + || currentlevel == 0 ) { hungerring = 1; // slow down hunger on boss stages. if ( vampiricHunger > 0 ) From b0e29ddd7afdbfdce7aef00fdee7a73867e20f22 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:18:10 +1100 Subject: [PATCH 222/244] * hunger disabled modifiers can increase HP regen --- src/entity.cpp | 39 ++++++++++++++++++++++----------------- src/ui/GameUI.cpp | 34 +++++++++++++++++++++------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 7945f066f..9ea04fa8d 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -4018,17 +4018,13 @@ void Entity::handleEffects(Stat* myStats) // healing over time int healring = 0; int healthRegenInterval = getHealthRegenInterval(this, *myStats, behavior == &actPlayer); - if ( healthRegenInterval == -1 && behavior == &actPlayer && myStats->type == SKELETON ) - { - healthRegenInterval = HEAL_TIME * 4; - } bool naturalHeal = false; if ( healthRegenInterval >= 0 ) { if ( myStats->HP < myStats->MAXHP ) { this->char_heal++; - if ( (svFlags & SV_FLAG_HUNGER) || behavior == &actMonster || (behavior == &actPlayer && myStats->type == SKELETON) ) + /*if ( (svFlags & SV_FLAG_HUNGER) || behavior == &actMonster || (behavior == &actPlayer && myStats->type == SKELETON) )*/ { if ( this->char_heal >= healthRegenInterval ) { @@ -19521,13 +19517,6 @@ int Entity::getHealringFromEquipment(Entity* my, Stat& myStats, bool isPlayer) int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) { - if ( !(svFlags & SV_FLAG_HUNGER) ) - { - if ( isPlayer ) - { - return -1; - } - } if ( myStats.EFFECTS[EFF_VAMPIRICAURA] ) { if ( isPlayer && myStats.EFFECTS_TIMERS[EFF_VAMPIRICAURA] > 0 ) @@ -19544,18 +19533,27 @@ int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) { return -1; } + double healring = 0; - if ( isPlayer && myStats.type != HUMAN ) + double bonusHealring = 0.0; + if ( myStats.type == SKELETON && isPlayer ) { - if ( myStats.type == SKELETON ) + healring = -1; + } + if ( !(svFlags & SV_FLAG_HUNGER) && isPlayer ) + { + bonusHealring += Entity::getHealringFromEquipment(my, myStats, isPlayer); + bonusHealring += Entity::getHealringFromEffects(my, myStats); + if ( bonusHealring < 0.01 && myStats.type != SKELETON ) { - healring = -1; // 0.25x regen speed. + return -1; } } - - double bonusHealring = 0.0; + else + { bonusHealring += Entity::getHealringFromEquipment(my, myStats, isPlayer); bonusHealring += Entity::getHealringFromEffects(my, myStats); + } healring += bonusHealring; if ( my && bonusHealring >= 2.0 && ::ticks % TICKS_PER_SECOND == 0 && isPlayer ) @@ -19582,8 +19580,15 @@ int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) if ( healring > 0 ) { + if ( !(svFlags & SV_FLAG_HUNGER) && isPlayer ) + { + return (HEAL_TIME / (healring * 4)); // 1 HP each 12 sec base + } + else + { return (HEAL_TIME / (healring * 6)); // 1 HP each 12 sec base } + } else if ( healring < 0 ) { return (abs(healring) * HEAL_TIME * 4); // 1 HP each 48 sec if negative regen diff --git a/src/ui/GameUI.cpp b/src/ui/GameUI.cpp index e5af3167d..47e5f97bc 100644 --- a/src/ui/GameUI.cpp +++ b/src/ui/GameUI.cpp @@ -15252,24 +15252,24 @@ real_t getDisplayedHPRegen(Entity* my, Stat& myStats, Uint32* outColor, char buf { regen = (static_cast(Entity::getHealthRegenInterval(my, myStats, true)) / TICKS_PER_SECOND); - if ( myStats.type == SKELETON ) + /*if ( myStats.type == SKELETON ) { if ( !(svFlags & SV_FLAG_HUNGER) ) { regen = HEAL_TIME * 4 / TICKS_PER_SECOND; } - } + }*/ if ( regen < 0 ) { regen = 0.0; - if ( !(svFlags & SV_FLAG_HUNGER) ) + /*if ( !(svFlags & SV_FLAG_HUNGER) ) { if ( outColor ) { *outColor = hudColors.characterSheetNeutral; } } - else + else*/ { if ( outColor ) { @@ -15297,11 +15297,11 @@ real_t getDisplayedHPRegen(Entity* my, Stat& myStats, Uint32* outColor, char buf } if ( buf ) { - if ( !(svFlags & SV_FLAG_HUNGER) ) + /*if ( !(svFlags & SV_FLAG_HUNGER) && regen < 0.01 ) { snprintf(buf, 32, "- "); } - else + else*/ { snprintf(buf, 32, "%.f%%", regen * 100.0); } @@ -16631,7 +16631,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element titleText = getHoverTextString("attributes_rgn_hp_title"); if ( !(svFlags & SV_FLAG_HUNGER) ) { - descText = getHoverTextString("attributes_rgn_hp_desc_no_hunger"); + descText = getHoverTextString("attributes_rgn_hp_desc_no_hunger2"); } else { @@ -16749,11 +16749,11 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_hp_base").c_str()); char hpbuf[32] = ""; getDisplayedHPRegen(players[player.playernum]->entity, *stats[player.playernum], nullptr, hpbuf); - if ( !(svFlags & SV_FLAG_HUNGER) ) + /*if ( !(svFlags & SV_FLAG_HUNGER) ) { snprintf(valueBuf, sizeof(valueBuf), "%s", hpbuf); } - else + else*/ { snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_nobonus_format").c_str(), hpbuf); } @@ -17186,7 +17186,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element bool hasEntryInfoLines = false; if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 3, buf, valueBuf) || (element != SHEET_ATK - && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER)) + && !(element == SHEET_RGN && false/*&& !(svFlags & SV_FLAG_HUNGER) && stats[player.playernum]->type != SKELETON*/) && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER)) )) { @@ -17326,6 +17326,10 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element snprintf(buf, sizeof(buf), getHoverTextString("attributes_rgn_base_value").c_str(), race.c_str()); real_t regen = 100.0; + if ( !(svFlags & SV_FLAG_HUNGER) ) + { + regen = 0.0; + } if ( type == SKELETON ) { regen = 25.0; @@ -17506,7 +17510,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element if ( (element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 4, buf, valueBuf)) || (element != SHEET_ATK - && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER)) + && !(element == SHEET_RGN && false/*&& !(svFlags & SV_FLAG_HUNGER) && stats[player.playernum]->type != SKELETON*/) && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER)) ) ) @@ -17575,6 +17579,10 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element } } real_t baseRegen = 100.0; + if ( !(svFlags & SV_FLAG_HUNGER) ) + { + baseRegen = 0.0; + } if ( type == SKELETON ) { baseRegen = 25.0; @@ -17769,7 +17777,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 5, buf, valueBuf) || (element != SHEET_ATK && element != SHEET_RES - && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER)) + && !(element == SHEET_RGN && false/*&& !(svFlags & SV_FLAG_HUNGER) && stats[player.playernum]->type != SKELETON*/) && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER)) ) ) @@ -18376,7 +18384,7 @@ void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element } entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters; entry->setSize(entryPos); - if ( element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER) ) + if ( element == SHEET_RGN && false/*&& !(svFlags & SV_FLAG_HUNGER) && stats[player.playernum]->type != SKELETON*/ ) { entry->setColor(hudColors.itemContextMenuHeadingText); } From bf411df7807b03b092fe6d494212f6a75ec3537c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:29:32 +1100 Subject: [PATCH 223/244] * mapseed now uses custom seed fixed rng, /map_sequence_rng to toggle old behaviour * mainmenu maps set map hash to -1 --- src/files.cpp | 20 ++++++++++---------- src/game.cpp | 3 ++- src/game.hpp | 1 + 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/files.cpp b/src/files.cpp index 8ca5deb2d..da60aa6cd 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -588,15 +588,15 @@ std::unordered_map mapHashes = { { "labyrinth32e.lmp", 26968 }, { "labyrinthsecret.lmp", 73135 }, { "labyrinthtoruins.lmp", 137530 }, - { "mainmenu1.lmp", 12427 }, - { "mainmenu2.lmp", 23291 }, - { "mainmenu3.lmp", 67606 }, - { "mainmenu4.lmp", 59632 }, - { "mainmenu5.lmp", 122197 }, - { "mainmenu6.lmp", 84492 }, - { "mainmenu7.lmp", 607316 }, - { "mainmenu8.lmp", 97824 }, - { "mainmenu9.lmp", 4654457 }, + { "mainmenu1.lmp", -1 }, + { "mainmenu2.lmp", -1 }, + { "mainmenu3.lmp", -1 }, + { "mainmenu4.lmp", -1 }, + { "mainmenu5.lmp", -1 }, + { "mainmenu6.lmp", -1 }, + { "mainmenu7.lmp", -1 }, + { "mainmenu8.lmp", -1 }, + { "mainmenu9.lmp", -1 }, { "mine.lmp", 80741 }, { "mine00.lmp", 12890 }, { "mine01.lmp", 52889 }, @@ -3134,7 +3134,7 @@ int physfsLoadMapFile(int levelToLoad, Uint32 seed, bool useRandSeed, int* check mapName = physfsFormatMapName(tempstr); if ( useRandSeed ) { - if ( gameModeManager.currentSession.seededRun.seed == 0 ) + if ( gameModeManager.currentSession.seededRun.seed == 0 && !*cvar_map_sequence_rng ) { mapseed = local_rng.rand(); } diff --git a/src/game.cpp b/src/game.cpp index d9fb25ea9..764f6a606 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -224,6 +224,7 @@ LONG CALLBACK unhandled_handler(EXCEPTION_POINTERS* e) ConsoleVariable cvar_enableKeepAlives("/keepalive_enabled", true); ConsoleVariable cvar_animate_tiles("/animate_tiles", true); +ConsoleVariable cvar_map_sequence_rng("/map_sequence_rng", true); std::vector randomPlayerNamesMale; std::vector randomPlayerNamesFemale; @@ -2080,7 +2081,7 @@ void gameLogic(void) skipLevelsOnLoad = 0; // signal clients about level change - if ( gameModeManager.currentSession.seededRun.seed == 0 ) + if ( gameModeManager.currentSession.seededRun.seed == 0 && !*cvar_map_sequence_rng ) { mapseed = local_rng.rand(); } diff --git a/src/game.hpp b/src/game.hpp index c59620892..08d8af1e7 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -524,6 +524,7 @@ class DebugStatsClass }; extern ConsoleVariable cvar_enableKeepAlives; +extern ConsoleVariable cvar_map_sequence_rng; extern DebugStatsClass DebugStats; //extern ConsoleVariable cvar_useTimerInterpolation; From 651b7d989d8bfd0163c1e21ac9f5fc1faff5e28b Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:29:46 +1100 Subject: [PATCH 224/244] * double shield durability rng --- src/player.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/player.cpp b/src/player.cpp index d802e55a5..9f5a2c8da 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -6884,22 +6884,22 @@ bool Player::PlayerMechanics_t::itemDegradeRoll(Item* item, int* checkInterval) switch ( item->type ) { case WOODEN_SHIELD: - interval = 5; + interval = 10; break; case BRONZE_SHIELD: - interval = 10; + interval = 20; break; case IRON_SHIELD: - interval = 10; + interval = 20; break; case STEEL_SHIELD: - interval = 15; + interval = 30; break; case STEEL_SHIELD_RESISTANCE: - interval = 15; + interval = 30; break; case CRYSTAL_SHIELD: - interval = 10; + interval = 20; break; default: break; From 017677dc25eb8f41e8c0679ce8daed669c57963d Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:45:59 +1100 Subject: [PATCH 225/244] * starvation is now 5% maxhp --- src/entity.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 9ea04fa8d..74532e660 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -3746,7 +3746,8 @@ void Entity::handleEffects(Stat* myStats) } else { - this->modHP(-4); + int damage = std::max(1, myStats->MAXHP / 20); + this->modHP(-damage); if ( myStats->HP <= 0 ) { From a75fa86c35b8b5b7feb1cd4b0eea0ef649b1d21e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:46:37 +1100 Subject: [PATCH 226/244] * insectoid max mp is 100 from 50 --- src/entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entity.cpp b/src/entity.cpp index 74532e660..8a582c8ae 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -3082,7 +3082,7 @@ void Entity::handleEffects(Stat* myStats) myStats->MAXMP += MP_MOD; if ( behavior == &actPlayer && myStats->playerRace == RACE_INSECTOID && myStats->stat_appearance == 0 ) { - myStats->MAXMP = std::min(50, myStats->MAXMP); + myStats->MAXMP = std::min(100, myStats->MAXMP); if ( svFlags & SV_FLAG_HUNGER ) { Sint32 hungerPointPerMana = playerInsectoidHungerValueOfManaPoint(*myStats); From 470e8074375cc711c12a420412d230d5bd3b7803 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 13:47:46 +1100 Subject: [PATCH 227/244] * rhythm of the knight procs on taking damage too, remove skilled block penalty skill rng --- src/entity.cpp | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index 8a582c8ae..54a2f13a4 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -9497,10 +9497,6 @@ void Entity::attack(int pose, int charge, Entity* target) || (hitstats->defending) ) { int roll = 16; - if ( hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) - { - roll = 32; - } if ( damage == 0 ) { roll /= 2; @@ -10308,14 +10304,12 @@ void Entity::attack(int pose, int charge, Entity* target) if ( player >= 0 && hit.entity->behavior == &actMonster ) { - if ( damage > 0 ) - { bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[player]; updateAchievementRhythmOfTheKnight(player, hit.entity, false); if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[player] ) { //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on atk"); - if ( local_rng.rand() % 10 < 6 ) + if ( local_rng.rand() % 10 < 8 ) { bool increaseSkill = true; if ( this->behavior == &actPlayer ) @@ -10335,14 +10329,6 @@ void Entity::attack(int pose, int charge, Entity* target) achievementRhythmOfTheKnightVec[player].clear(); // reset for the next one } } - else - { - if ( !achievementStatusRhythmOfTheKnight[player] ) - { - achievementRhythmOfTheKnightVec[player].clear(); // didn't inflict damage. - } - } - } bool artifactWeaponProc = parashuProc || dyrnwynSmite || dyrnwynBurn || gugnirProc; @@ -10935,10 +10921,6 @@ void Entity::attack(int pose, int charge, Entity* target) messagePlayerMonsterEvent(playerhit, color, *myStats, Language::get(698), Language::get(699), MSG_ATTACKS); if ( playerhit >= 0 ) { - if ( !achievementStatusRhythmOfTheKnight[playerhit] ) - { - achievementRhythmOfTheKnightVec[playerhit].clear(); - } if ( !achievementStatusThankTheTank[playerhit] ) { achievementThankTheTankPair[playerhit] = std::make_pair(0, 0); @@ -10981,6 +10963,8 @@ void Entity::attack(int pose, int charge, Entity* target) { steamAchievementClient(playerhit, "BARONY_ACH_ONE_WHO_KNOCKS"); } + } + if ( playerhit >= 0 ) { if ( hitstats->defending ) @@ -10992,7 +10976,7 @@ void Entity::attack(int pose, int charge, Entity* target) if ( !shieldIncreased ) { //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on hit"); - if ( local_rng.rand() % 10 < 6 ) + if ( local_rng.rand() % 10 < 8 ) { bool skillIncrease = true; if ( hit.entity->behavior == &actPlayer ) @@ -11015,13 +10999,13 @@ void Entity::attack(int pose, int charge, Entity* target) } updateAchievementThankTheTank(playerhit, this, false); } - else if ( !achievementStatusRhythmOfTheKnight[playerhit] ) + else { + achievementStatusRhythmOfTheKnight[playerhit] = false; achievementRhythmOfTheKnightVec[playerhit].clear(); //messagePlayer(0, "used AC!"); } } - } if ( playerhit >= 0 ) { From bce1fdadefb14928075a102a9587b729adf5a6c0 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 15:33:01 +1100 Subject: [PATCH 228/244] * insectoid food only scales up til base 50 mp --- src/item_usage_funcs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/item_usage_funcs.cpp b/src/item_usage_funcs.cpp index 1a7dd4aa0..18acd01f4 100644 --- a/src/item_usage_funcs.cpp +++ b/src/item_usage_funcs.cpp @@ -4663,7 +4663,7 @@ void item_Food(Item*& item, int player) break; } manaRegenPercent *= foodMult; - int manaAmount = stats[player]->MAXMP * manaRegenPercent; + int manaAmount = std::min(stats[player]->MAXMP, 50) * manaRegenPercent; players[player]->entity->modMP(manaAmount); } } @@ -4914,7 +4914,7 @@ void item_FoodTin(Item*& item, int player) if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->stat_appearance == 0 ) { real_t manaRegenPercent = 0.6 * foodMult; - int manaAmount = stats[player]->MAXMP * manaRegenPercent; + int manaAmount = std::min(stats[player]->MAXMP, 50) * manaRegenPercent; players[player]->entity->modMP(manaAmount); } } From 84ddee026ba9f6bbbb86381fd8855fde79998fc7 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 15:39:05 +1100 Subject: [PATCH 229/244] * blocking within .3sec adds bonus roll to shield skill proc, arrows can increase/degrade shield too --- src/actarrow.cpp | 186 ++++++++++++++++++++++++++++++++++++++++++++++- src/entity.cpp | 134 +++++++++++++++++++++------------- src/player.hpp | 1 + 3 files changed, 268 insertions(+), 53 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index c8a1152b5..f2c8bef96 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -1215,7 +1215,189 @@ void actArrow(Entity* my) } } - if ( damage == 0 && !statusEffectApplied ) + bool armorDegraded = false; + + // hit armor degrade + if ( hitstats && parent && parent->getStats() ) + { + Item* armor = NULL; + int armornum = 0; + bool isWeakArmor = false; + if ( damage > 0 || (damage == 0 && !(hitstats->shield && hitstats->defending)) ) + { + armornum = hitstats->pickRandomEquippedItemToDegradeOnHit(&armor, true, false, false, true); + if ( armor != NULL && armor->status > BROKEN ) + { + switch ( armor->type ) + { + case CRYSTAL_HELM: + case CRYSTAL_SHIELD: + case CRYSTAL_BREASTPIECE: + case CRYSTAL_BOOTS: + case CRYSTAL_GLOVES: + isWeakArmor = true; + break; + default: + isWeakArmor = false; + break; + } + } + + int armorDegradeChance = 30; + if ( isWeakArmor ) + { + armorDegradeChance = 25; + } + if ( hitstats->type == GOBLIN ) + { + armorDegradeChance += 10; + } + + if ( armorDegradeChance == 100 || (local_rng.rand() % armorDegradeChance > 0) ) + { + armor = NULL; + armornum = 0; + } + } + + // if nothing chosen to degrade, check extra shield chances to degrade + if ( hitstats->shield != NULL && hitstats->shield->status > BROKEN && armor == NULL + && !itemTypeIsQuiver(hitstats->shield->type) && itemCategory(hitstats->shield) != SPELLBOOK + && hitstats->shield->type != TOOL_TINKERING_KIT ) + { + if ( hitstats->shield->type == TOOL_CRYSTALSHARD && hitstats->defending ) + { + // shards degrade by 1 stage each hit. + armor = hitstats->shield; + armornum = 4; + } + else if ( hitstats->shield->type == MIRROR_SHIELD && hitstats->defending ) + { + // mirror shield degrade by 1 stage each hit. + armor = hitstats->shield; + armornum = 4; + } + { + // if no armor piece was chosen to break, grant chance to improve shield skill. + if ( itemCategory(hitstats->shield) == ARMOR + || (hitstats->defending) ) + { + int roll = 20; + if ( damage == 0 ) + { + roll /= 2; + } + if ( roll > 0 ) + { + bool success = (local_rng.rand() % roll == 0); + if ( !success && hit.entity->behavior == &actPlayer && hitstats->defending ) + { + if ( players[hit.entity->skill[2]]->mechanics.defendTicks != 0 ) + { + if ( (::ticks - players[hit.entity->skill[2]]->mechanics.defendTicks) < (TICKS_PER_SECOND / 3) ) + { + // perfect block timing, roll again + success = (local_rng.rand() % roll == 0); + } + } + } + + if ( success ) + { + bool increaseSkill = true; + if ( hit.entity->behavior == &actPlayer && parent->behavior == &actPlayer ) + { + increaseSkill = false; + } + else if ( hit.entity->behavior == &actPlayer && parent->monsterAllyGetPlayerLeader() ) + { + increaseSkill = false; + } + else if ( hit.entity->behavior == &actPlayer + && !players[hit.entity->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*parent) ) + { + increaseSkill = false; + } + else if ( hitstats->EFFECTS[EFF_SHAPESHIFT] ) + { + increaseSkill = false; + } + else if ( itemCategory(hitstats->shield) != ARMOR + && hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) + { + increaseSkill = false; // non-shield offhands dont increase skill past 40. + } + if ( increaseSkill ) + { + hit.entity->increaseSkill(PRO_SHIELD); // increase shield skill + if ( hit.entity->behavior == &actPlayer ) + { + players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[parent->getUID()]++; + } + } + } + } + } + + // shield still has chance to degrade after raising skill. + int shieldDegradeChance = 20; + if ( svFlags & SV_FLAG_HARDCORE ) + { + shieldDegradeChance = 40; + } + if ( hitstats->type == GOBLIN ) + { + shieldDegradeChance += 10; + } + if ( hit.entity->behavior == &actPlayer ) + { + if ( itemCategory(hitstats->shield) == ARMOR ) + { + shieldDegradeChance += 2 * (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); // 2x shield bonus offhand + if ( !players[hit.entity->skill[2]]->mechanics.itemDegradeRoll(hitstats->shield) ) + { + shieldDegradeChance = 100; // don't break. + } + } + else + { + shieldDegradeChance += (hitstats->getModifiedProficiency(PRO_SHIELD) / 10); + } + if ( skillCapstoneUnlocked(hit.entity->skill[2], PRO_SHIELD) ) + { + shieldDegradeChance = 100; // don't break. + } + } + if ( shieldDegradeChance < 100 && armor == NULL && + (hitstats->defending && local_rng.rand() % shieldDegradeChance == 0) + ) + { + armor = hitstats->shield; + armornum = 4; + } + } + } + + if ( armor != NULL && armor->status > BROKEN ) + { + hit.entity->degradeArmor(*hitstats, *armor, armornum); + armorDegraded = true; + if ( armor->status == BROKEN ) + { + if ( parent && parent->behavior == &actPlayer && hit.entity->behavior == &actMonster ) + { + steamStatisticUpdateClient(parent->skill[2], STEAM_STAT_UNSTOPPABLE_FORCE, STEAM_STAT_INT, 1); + if ( armornum == 4 && hitstats->type == BUGBEAR + && (hitstats->defending || hit.entity->monsterAttack == MONSTER_POSE_BUGBEAR_SHIELD) ) + { + steamAchievementClient(parent->skill[2], "BARONY_ACH_BEAR_WITH_ME"); + } + } + } + } + } + + if ( damage == 0 && !statusEffectApplied && !armorDegraded ) { playSoundEntity(hit.entity, 66, 64); //*tink* if ( hit.entity->behavior == &actPlayer ) @@ -1241,7 +1423,7 @@ void actArrow(Entity* my) } } } - else if ( damage == 0 && statusEffectApplied ) + else if ( damage == 0 && (statusEffectApplied || armorDegraded) ) { playSoundEntity(hit.entity, 28, 64); } diff --git a/src/entity.cpp b/src/entity.cpp index 54a2f13a4..85e3ad5da 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -3006,6 +3006,25 @@ void Entity::handleEffects(Stat* myStats) myStats->HP = 1; } } + + if ( myStats->defending ) + { + if ( myStats->shield && !myStats->EFFECTS[EFF_SHAPESHIFT] ) + { + if ( players[player]->mechanics.defendTicks == 0 ) + { + players[player]->mechanics.defendTicks = ::ticks; + } + } + else + { + players[player]->mechanics.defendTicks = 0; + } + } + else + { + players[player]->mechanics.defendTicks = 0; + } } auto& camera_shakex = cameravars[player >= 0 ? player : 0].shakex; @@ -9496,14 +9515,27 @@ void Entity::attack(int pose, int charge, Entity* target) if ( itemCategory(hitstats->shield) == ARMOR || (hitstats->defending) ) { - int roll = 16; + int roll = 20; if ( damage == 0 ) { roll /= 2; } if ( roll > 0 ) { - if ( (local_rng.rand() % roll == 0 && damage > 0) || (damage == 0 && local_rng.rand() % roll == 0) ) + bool success = (local_rng.rand() % roll == 0); + if ( !success && playerhit >= 0 && hitstats->defending ) + { + if ( players[playerhit]->mechanics.defendTicks != 0 ) + { + if ( (::ticks - players[playerhit]->mechanics.defendTicks) < (TICKS_PER_SECOND / 3) ) + { + // perfect block timing, roll again + success = (local_rng.rand() % roll == 0); + } + } + } + + if ( success ) { bool increaseSkill = true; if ( hit.entity->behavior == &actPlayer && behavior == &actPlayer ) @@ -10304,31 +10336,31 @@ void Entity::attack(int pose, int charge, Entity* target) if ( player >= 0 && hit.entity->behavior == &actMonster ) { - bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[player]; - updateAchievementRhythmOfTheKnight(player, hit.entity, false); - if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[player] ) - { - //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on atk"); + bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[player]; + updateAchievementRhythmOfTheKnight(player, hit.entity, false); + if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[player] ) + { + //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on atk"); if ( local_rng.rand() % 10 < 8 ) + { + bool increaseSkill = true; + if ( this->behavior == &actPlayer ) { - bool increaseSkill = true; - if ( this->behavior == &actPlayer ) + if ( !players[this->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*hit.entity) ) { - if ( !players[this->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*hit.entity) ) - { - increaseSkill = false; - } - players[this->skill[2]]->mechanics.enemyRaisedBlockingAgainst[hit.entity->getUID()]++; - } - if ( increaseSkill ) - { - this->increaseSkill(PRO_SHIELD); + increaseSkill = false; } + players[this->skill[2]]->mechanics.enemyRaisedBlockingAgainst[hit.entity->getUID()]++; + } + if ( increaseSkill ) + { + this->increaseSkill(PRO_SHIELD); } - achievementStatusRhythmOfTheKnight[player] = false; - achievementRhythmOfTheKnightVec[player].clear(); // reset for the next one } + achievementStatusRhythmOfTheKnight[player] = false; + achievementRhythmOfTheKnightVec[player].clear(); // reset for the next one } + } bool artifactWeaponProc = parashuProc || dyrnwynSmite || dyrnwynBurn || gugnirProc; @@ -10965,47 +10997,47 @@ void Entity::attack(int pose, int charge, Entity* target) } } - if ( playerhit >= 0 ) + if ( playerhit >= 0 ) + { + if ( hitstats->defending ) { - if ( hitstats->defending ) + bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[playerhit]; + updateAchievementRhythmOfTheKnight(playerhit, this, true); + if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[playerhit] ) { - bool oldRhythmStatus = achievementStatusRhythmOfTheKnight[playerhit]; - updateAchievementRhythmOfTheKnight(playerhit, this, true); - if ( !oldRhythmStatus && achievementStatusRhythmOfTheKnight[playerhit] ) + if ( !shieldIncreased ) { - if ( !shieldIncreased ) - { - //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on hit"); + //messagePlayer(0, MESSAGE_DEBUG, "rhythm roll on hit"); if ( local_rng.rand() % 10 < 8 ) + { + bool skillIncrease = true; + if ( hit.entity->behavior == &actPlayer ) { - bool skillIncrease = true; - if ( hit.entity->behavior == &actPlayer ) + if ( !players[hit.entity->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*this) ) { - if ( !players[hit.entity->skill[2]]->mechanics.allowedRaiseBlockingAgainstEntity(*this) ) - { - skillIncrease = false; - } - players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; - } - if ( skillIncrease ) - { - hit.entity->increaseSkill(PRO_SHIELD); - shieldIncreased = true; + skillIncrease = false; } + players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; + } + if ( skillIncrease ) + { + hit.entity->increaseSkill(PRO_SHIELD); + shieldIncreased = true; } } - achievementStatusRhythmOfTheKnight[playerhit] = false; - achievementRhythmOfTheKnightVec[playerhit].clear(); // reset for the next one } - updateAchievementThankTheTank(playerhit, this, false); + achievementStatusRhythmOfTheKnight[playerhit] = false; + achievementRhythmOfTheKnightVec[playerhit].clear(); // reset for the next one } + updateAchievementThankTheTank(playerhit, this, false); + } else - { + { achievementStatusRhythmOfTheKnight[playerhit] = false; - achievementRhythmOfTheKnightVec[playerhit].clear(); - //messagePlayer(0, "used AC!"); - } + achievementRhythmOfTheKnightVec[playerhit].clear(); + //messagePlayer(0, "used AC!"); } + } if ( playerhit >= 0 ) { @@ -19536,8 +19568,8 @@ int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) } else { - bonusHealring += Entity::getHealringFromEquipment(my, myStats, isPlayer); - bonusHealring += Entity::getHealringFromEffects(my, myStats); + bonusHealring += Entity::getHealringFromEquipment(my, myStats, isPlayer); + bonusHealring += Entity::getHealringFromEffects(my, myStats); } healring += bonusHealring; @@ -19571,8 +19603,8 @@ int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) } else { - return (HEAL_TIME / (healring * 6)); // 1 HP each 12 sec base - } + return (HEAL_TIME / (healring * 6)); // 1 HP each 12 sec base + } } else if ( healring < 0 ) { diff --git a/src/player.hpp b/src/player.hpp index 560e9a673..bf73e4bdf 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -2293,6 +2293,7 @@ class Player bool itemDegradeRoll(Item* item, int* checkInterval = nullptr); void onItemDegrade(Item* item); int sustainedSpellMPUsed = 0; + Uint32 defendTicks = 0; bool sustainedSpellLevelChance(); void sustainedSpellIncrementMP(int mpChange); std::map enemyRaisedBlockingAgainst; From 354c1564ff664e7c202970f504f3e5d750b88304 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 16:19:00 +1100 Subject: [PATCH 230/244] * quickturn console command --- src/actplayer.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index ab3ac7921..a1503bbed 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -262,6 +262,8 @@ void Player::Ghost_t::startQuickTurn() bDoingQuickTurn = true; } +static ConsoleVariable cvar_quick_turn_speed("/quick_turn_speed", 1.f); + bool Player::Ghost_t::handleQuickTurn(bool useRefreshRateDelta) { if ( !my ) { return false; } @@ -285,7 +287,7 @@ bool Player::Ghost_t::handleQuickTurn(bool useRefreshRateDelta) int dir = ((quickTurnRotation > 0) ? 1 : -1); if ( my->ticks - quickTurnStartTicks < 15 ) { - GHOSTCAM_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed; + GHOSTCAM_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed; } else { @@ -2486,7 +2488,7 @@ bool Player::PlayerMovement_t::handleQuickTurn(bool useRefreshRateDelta) int dir = ((quickTurnRotation > 0) ? 1 : -1); if ( my->ticks - quickTurnStartTicks < 15 ) { - PLAYER_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed; + PLAYER_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed; } else { From ce0bd47b72d35da42504e27ffa551daac14e0a78 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 16:19:23 +1100 Subject: [PATCH 231/244] * hunger of 0 removes hp regen --- src/entity.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/entity.cpp b/src/entity.cpp index 85e3ad5da..961727c60 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -19551,6 +19551,32 @@ int Entity::getHealthRegenInterval(Entity* my, Stat& myStats, bool isPlayer) return -1; } + if ( svFlags & SV_FLAG_HUNGER ) + { + if ( isPlayer ) + { + if ( myStats.HUNGER <= 0 ) + { + bool doStarvation = true; + if ( myStats.type == AUTOMATON ) + { + if ( myStats.MP > 0 ) + { + doStarvation = false; + } + } + else if ( myStats.type == SKELETON ) + { + doStarvation = false; + } + if ( doStarvation ) + { + return -1; + } + } + } + } + double healring = 0; double bonusHealring = 0.0; if ( myStats.type == SKELETON && isPlayer ) From 3f3ec970fecbb59788e9895e051aecda0e03cc49 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Tue, 22 Oct 2024 16:36:48 +1100 Subject: [PATCH 232/244] * fix editor, lang update --- lang/en.txt | 2 ++ src/files.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lang/en.txt b/lang/en.txt index 41da3db19..472835b97 100644 --- a/lang/en.txt +++ b/lang/en.txt @@ -6538,5 +6538,7 @@ Unlock Achievements to earn more!# is unable to be enchanted.# 6308 Your %s loses some of its blessing.# 6309 You are blasted by a torrent!# +6310 Quick Turn Speed# +6311 Affect the horizontal speed of the Quick Turn action.# 6350 END# diff --git a/src/files.cpp b/src/files.cpp index da60aa6cd..0f765183a 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -3134,7 +3134,11 @@ int physfsLoadMapFile(int levelToLoad, Uint32 seed, bool useRandSeed, int* check mapName = physfsFormatMapName(tempstr); if ( useRandSeed ) { +#ifdef EDITOR + if ( gameModeManager.currentSession.seededRun.seed == 0 ) +#else if ( gameModeManager.currentSession.seededRun.seed == 0 && !*cvar_map_sequence_rng ) +#endif { mapseed = local_rng.rand(); } From c3e897334ea81f5ecc91c912c813cff55d165cc6 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 07:16:30 +1100 Subject: [PATCH 233/244] * player cursed traps explode randomly, no drop detonator --- src/actbeartrap.cpp | 39 ++++++++++++++++++++++++++++++++------- src/item_tool.cpp | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/actbeartrap.cpp b/src/actbeartrap.cpp index 4cb59a2a2..e58338dd6 100644 --- a/src/actbeartrap.cpp +++ b/src/actbeartrap.cpp @@ -322,6 +322,7 @@ void actBeartrapLaunched(Entity* my) #define BOMB_TRIGGER_TYPE my->skill[22] #define BOMB_CHEST_STATUS my->skill[23] #define BOMB_HIT_BY_PROJECTILE my->skill[24] +#define BOMB_CURSED_RNG_EXPLODE my->skill[25] void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spawnMagicOnTriggeredMonster, bool hitByAOE ) { @@ -831,7 +832,21 @@ void actBomb(Entity* my) explosionSprite = 0; } - if ( BOMB_ENTITY_ATTACHED_TO != 0 || BOMB_HIT_BY_PROJECTILE == 1 || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL ) + bool cursedExplode = false; + if ( BOMB_CURSED_RNG_EXPLODE ) + { + if ( my->ticks >= TICKS_PER_SECOND / 2 ) + { + if ( my->ticks % TICKS_PER_SECOND == (5 * (my->getUID() % 10)) ) // randomly explode + { + cursedExplode = true; + } + } + } + + if ( BOMB_ENTITY_ATTACHED_TO != 0 || BOMB_HIT_BY_PROJECTILE == 1 + || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL + || cursedExplode ) { Entity* onEntity = uidToEntity(static_cast(BOMB_ENTITY_ATTACHED_TO)); bool shouldExplode = false; @@ -871,7 +886,7 @@ void actBomb(Entity* my) { if ( onEntity->behavior == &actDoor ) { - if ( onEntity->doorHealth < BOMB_ENTITY_ATTACHED_START_HP || onEntity->flags[PASSABLE] + if ( onEntity->doorHealth < BOMB_ENTITY_ATTACHED_START_HP || onEntity->flags[PASSABLE] || cursedExplode || BOMB_HIT_BY_PROJECTILE == 1 ) { if ( onEntity->doorHealth > 0 ) @@ -883,7 +898,7 @@ void actBomb(Entity* my) } else if ( onEntity->behavior == &actMonster && onEntity->getMonsterTypeFromSprite() == MIMIC ) { - if ( !onEntity->isInertMimic() || BOMB_HIT_BY_PROJECTILE == 1 ) + if ( !onEntity->isInertMimic() || BOMB_HIT_BY_PROJECTILE == 1 || cursedExplode ) { if ( onEntity->isInertMimic() ) { @@ -895,6 +910,7 @@ void actBomb(Entity* my) else if ( onEntity->behavior == &actChest ) { if ( onEntity->skill[3] < BOMB_ENTITY_ATTACHED_START_HP || BOMB_CHEST_STATUS != onEntity->skill[1] + || cursedExplode || BOMB_HIT_BY_PROJECTILE == 1 ) { if ( onEntity->skill[3] > 0 ) @@ -914,6 +930,7 @@ void actBomb(Entity* my) else if ( onEntity->behavior == &actColliderDecoration ) { if ( onEntity->colliderCurrentHP < BOMB_ENTITY_ATTACHED_START_HP + || cursedExplode || BOMB_HIT_BY_PROJECTILE == 1 ) { if ( onEntity->colliderCurrentHP > 0 ) @@ -936,6 +953,11 @@ void actBomb(Entity* my) shouldExplode = true; // my attached entity died. } + if ( cursedExplode ) + { + shouldExplode = true; + } + if ( shouldExplode ) { if ( onEntity ) @@ -1128,11 +1150,14 @@ void actBomb(Entity* my) // entity->itemNotMoving = 0; // entity->itemNotMovingClient = 0; //} - Item* charge = newItem(TOOL_DETONATOR_CHARGE, BROKEN, 0, 1, ITEM_TINKERING_APPEARANCE, true, nullptr); - Entity* dropped = dropItemMonster(charge, my, nullptr); - if ( dropped ) + if ( BEARTRAP_BEATITUDE >= 0 ) { - dropped->flags[USERFLAG1] = true; + Item* charge = newItem(TOOL_DETONATOR_CHARGE, BROKEN, 0, 1, ITEM_TINKERING_APPEARANCE, true, nullptr); + Entity* dropped = dropItemMonster(charge, my, nullptr); + if ( dropped ) + { + dropped->flags[USERFLAG1] = true; + } } my->removeLightField(); if ( triggered ) diff --git a/src/item_tool.cpp b/src/item_tool.cpp index 78e0fdb7b..a356691f7 100644 --- a/src/item_tool.cpp +++ b/src/item_tool.cpp @@ -1122,6 +1122,7 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, if ( this->beatitude < 0 ) { entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + entity->skill[25] = 1; // cursed rng explode } } @@ -1254,9 +1255,26 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, entity->skill[16] = placement; entity->skill[20] = dir; entity->skill[21] = type; - if ( this->beatitude < 0 ) + if ( parent && parent->behavior == &actMonster ) + { + auto& trapProps = monsterTrapIgnoreEntities[entity->getUID()]; + trapProps.parent = entity->parent; + for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + { + Entity* creature = (Entity*)node->element; + if ( creature && parent->checkFriend(creature) ) + { + trapProps.ignoreEntities.insert(creature->getUID()); + } + } + } + else { - entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + if ( this->beatitude < 0 ) + { + entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + entity->skill[25] = 1; // cursed rng explode + } } playSoundEntity(entity, 686, 64); @@ -1455,9 +1473,26 @@ void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, } entity->skill[20] = dir; entity->skill[21] = type; - if ( this->beatitude < 0 ) + if ( parent && parent->behavior == &actMonster ) + { + auto& trapProps = monsterTrapIgnoreEntities[entity->getUID()]; + trapProps.parent = entity->parent; + for ( node_t* node = map.creatures->first; node != nullptr; node = node->next ) + { + Entity* creature = (Entity*)node->element; + if ( creature && parent->checkFriend(creature) ) + { + trapProps.ignoreEntities.insert(creature->getUID()); + } + } + } + else { - entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + if ( this->beatitude < 0 ) + { + entity->skill[22] = ItemBombTriggerType::BOMB_TRIGGER_ALL; + entity->skill[25] = 1; // cursed rng explode + } } playSoundEntity(entity, 686, 64); From 7ac1a974bd447f3f55225ef3c587eae093422b3c Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 07:17:08 +1100 Subject: [PATCH 234/244] * gnomes reduce base +100 gold from lightning gnomes, add rng to not drop some equipment --- src/monster_gnome.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/monster_gnome.cpp b/src/monster_gnome.cpp index 1ae0bfacf..8e8251b5e 100644 --- a/src/monster_gnome.cpp +++ b/src/monster_gnome.cpp @@ -360,7 +360,6 @@ void initGnome(Entity* my, Stat* myStats) case 7: case 8: case 9: - myStats->GOLD += 100; myStats->weapon = newItem(MAGICSTAFF_LIGHTNING, EXCELLENT, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); break; } @@ -418,6 +417,10 @@ void initGnome(Entity* my, Stat* myStats) if ( myStats->weapon && isRangedWeapon(*myStats->weapon) ) { my->monsterGenerateQuiverItem(myStats); + if ( myStats->shield ) + { + myStats->shield->isDroppable = (rng.rand() % 2 == 0) ? true : false; + } } else { @@ -493,11 +496,13 @@ void initGnome(Entity* my, Stat* myStats) if ( myStats->leader_uid != 0 ) { myStats->helmet = newItem(HAT_HOOD_WHISPERS, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); + myStats->helmet->isDroppable = (rng.rand() % 2 == 0) ? true : false; } else { // leader has the bycocket myStats->helmet = newItem(HAT_BYCOCKET, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, 0, false, nullptr); + myStats->helmet->isDroppable = (rng.rand() % 2 == 0) ? true : false; } } else if ( gnomeVariant == GNOME_THIEF_MELEE ) @@ -505,6 +510,7 @@ void initGnome(Entity* my, Stat* myStats) if ( rng.rand() % 4 == 0 ) { myStats->helmet = newItem(HAT_BANDANA, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->helmet->isDroppable = (rng.rand() % 2 == 0) ? true : false; } else { @@ -516,6 +522,7 @@ void initGnome(Entity* my, Stat* myStats) else { myStats->helmet = newItem(HAT_HOOD_ASSASSIN, static_cast(WORN + rng.rand() % 2), -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); + myStats->helmet->isDroppable = (rng.rand() % 2 == 0) ? true : false; } } } @@ -569,7 +576,7 @@ void initGnome(Entity* my, Stat* myStats) case 8: case 9: myStats->mask = newItem(MASK_BANDIT, SERVICABLE, -1 + rng.rand() % 3, 1, rng.rand(), false, nullptr); - myStats->mask->isDroppable = (rng.rand() % 4 == 0) ? true : false; + myStats->mask->isDroppable = (rng.rand() % 8 == 0) ? true : false; break; default: break; From 20ff975239636b98e463da2319545568e6f93be4 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 07:18:25 +1100 Subject: [PATCH 235/244] * maybe fix burning bell rope when invis --- src/actgeneral.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/actgeneral.cpp b/src/actgeneral.cpp index de1d90c00..a83abbf90 100644 --- a/src/actgeneral.cpp +++ b/src/actgeneral.cpp @@ -4251,9 +4251,13 @@ void actBell(Entity* my) } } - if ( multiplayer == CLIENT && my->flags[INVISIBLE] && my->flags[BURNING] ) + if ( my->flags[INVISIBLE] && my->flags[BURNING] ) { my->flags[BURNING] = false; + if ( multiplayer != CLIENT ) + { + serverUpdateEntitySkill(my, BURNING); + } } if ( BELL_USE_DELAY > 0 ) From 213685555c99f2cc2da8df2cfb13fe767b2395aa Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 08:06:14 +1100 Subject: [PATCH 236/244] * bats harder to raise block, block increase roll required scaling with lvl --- src/actarrow.cpp | 2 ++ src/entity.cpp | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index f2c8bef96..7a738ac8d 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -1283,6 +1283,8 @@ void actArrow(Entity* my) || (hitstats->defending) ) { int roll = 20; + int hitskill = hitstats->getProficiency(PRO_SHIELD) / 20; + roll += hitskill * 5; if ( damage == 0 ) { roll /= 2; diff --git a/src/entity.cpp b/src/entity.cpp index 961727c60..a8b7745f3 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -9516,10 +9516,19 @@ void Entity::attack(int pose, int charge, Entity* target) || (hitstats->defending) ) { int roll = 20; + int hitskill = hitstats->getProficiency(PRO_SHIELD) / 20; + roll += hitskill * 5; if ( damage == 0 ) { roll /= 2; } + if ( myStats->type == BAT_SMALL ) + { + if ( hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_BASIC ) + { + roll *= 4; + } + } if ( roll > 0 ) { bool success = (local_rng.rand() % roll == 0); From e7c2fe342570d17c8540ec1da0568463d70da2f1 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 09:47:53 +1100 Subject: [PATCH 237/244] * thank the tank achievement grants shield procs --- src/game.cpp | 3 +++ src/scores.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/game.cpp b/src/game.cpp index 764f6a606..1614bb8a9 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1538,6 +1538,9 @@ void gameLogic(void) if ( achievementStatusThankTheTank[c] ) { steamAchievementClient(c, "BARONY_ACH_THANK_THE_TANK"); + achievementStatusThankTheTank[c] = false; + achievementThankTheTankPair[c].first = 0; + achievementThankTheTankPair[c].second = 0; } int bodyguards = 0; diff --git a/src/scores.cpp b/src/scores.cpp index 0f07fc92a..13484ac93 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -4100,6 +4100,10 @@ void updateAchievementThankTheTank(int player, Entity* target, bool targetKilled { return; } + if ( !target ) + { + return; + } if ( achievementStatusThankTheTank[player] || multiplayer == CLIENT ) { return; @@ -4123,6 +4127,14 @@ void updateAchievementThankTheTank(int player, Entity* target, bool targetKilled if ( (ticks - achievementThankTheTankPair[player].first) / 50.f < 3.f ) { achievementStatusThankTheTank[player] = true; + if ( players[player]->mechanics.allowedRaiseBlockingAgainstEntity(*target) ) + { + int skillLVL = 3 * (stats[player]->getProficiency(PRO_SHIELD) / 20); + if ( local_rng.rand() % (5 + skillLVL) == 0 ) + { + players[player]->entity->increaseSkill(PRO_SHIELD); + } + } } } } From fc9b84c18fac4c3c89ab1cbef877fd60525a77dc Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 12:00:39 +1100 Subject: [PATCH 238/244] * quick turn controls setting --- src/actplayer.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++-- src/player.hpp | 2 ++ src/ui/MainMenu.cpp | 27 ++++++++++++++++- 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/actplayer.cpp b/src/actplayer.cpp index a1503bbed..e2aa3c342 100644 --- a/src/actplayer.cpp +++ b/src/actplayer.cpp @@ -287,7 +287,42 @@ bool Player::Ghost_t::handleQuickTurn(bool useRefreshRateDelta) int dir = ((quickTurnRotation > 0) ? 1 : -1); if ( my->ticks - quickTurnStartTicks < 15 ) { - GHOSTCAM_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed; + int turnspeed = 1; + if ( inputs.hasController(player.playernum) ) + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed); + } + else if ( inputs.bPlayerUsingKeyboardControl(player.playernum) ) + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed_mkb); + } + else + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed); + } + turnspeed = std::min(5, std::max(1, turnspeed)); + float mult = 1.f; + switch ( turnspeed ) + { + case 1: + mult = 1.f; + break; + case 2: + mult = 1.25f; + break; + case 3: + mult = 1.5f; + break; + case 4: + mult = 2.5f; + break; + case 5: + mult = 3.f; + break; + default: + break; + } + GHOSTCAM_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed * mult; } else { @@ -2488,7 +2523,42 @@ bool Player::PlayerMovement_t::handleQuickTurn(bool useRefreshRateDelta) int dir = ((quickTurnRotation > 0) ? 1 : -1); if ( my->ticks - quickTurnStartTicks < 15 ) { - PLAYER_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed; + int turnspeed = 1; + if ( inputs.hasController(player.playernum) ) + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed); + } + else if ( inputs.bPlayerUsingKeyboardControl(player.playernum) ) + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed_mkb); + } + else + { + turnspeed = static_cast(playerSettings[multiplayer ? 0 : player.playernum].quick_turn_speed); + } + turnspeed = std::min(5, std::max(1, turnspeed)); + float mult = 1.f; + switch ( turnspeed ) + { + case 1: + mult = 1.f; + break; + case 2: + mult = 1.25f; + break; + case 3: + mult = 1.5f; + break; + case 4: + mult = 2.5f; + break; + case 5: + mult = 3.f; + break; + default: + break; + } + PLAYER_ROTX = dir * players[player.playernum]->settings.quickTurnSpeed * *cvar_quick_turn_speed * mult; } else { diff --git a/src/player.hpp b/src/player.hpp index bf73e4bdf..0c4956fac 100644 --- a/src/player.hpp +++ b/src/player.hpp @@ -65,6 +65,8 @@ struct PlayerSettings_t real_t gamepad_righty_sensitivity = 1.0; bool gamepad_rightx_invert = false; bool gamepad_righty_invert = false; + float quick_turn_speed = 1.f; + float quick_turn_speed_mkb = 1.f; void init(const int _player) { player = _player; diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index 3e2846cef..f28eeccc6 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -553,6 +553,8 @@ namespace MainMenu { float turn_sensitivity_y = 50.f; bool gamepad_camera_invert_x = false; bool gamepad_camera_invert_y = false; + float quick_turn_speed_control = 1.f; + float quick_turn_speed_mkb_control = 1.f; inline void save(int index); static inline Controls load(int index); static inline Controls reset(); @@ -2750,6 +2752,8 @@ namespace MainMenu { settings.gamepad_righty_sensitivity = std::min(std::max(25.f / 32768.f, turn_sensitivity_y / 32768.f), 200.f / 32768.f); settings.gamepad_rightx_invert = gamepad_camera_invert_x; settings.gamepad_righty_invert = gamepad_camera_invert_y; + settings.quick_turn_speed = std::max(1.f, std::min(5.f, quick_turn_speed_control)); + settings.quick_turn_speed_mkb = std::max(1.f, std::min(5.f, quick_turn_speed_mkb_control)); } inline Controls Controls::load(int index) { @@ -2766,6 +2770,8 @@ namespace MainMenu { controls.turn_sensitivity_y = settings.gamepad_righty_sensitivity * 32768.0; controls.gamepad_camera_invert_x = settings.gamepad_rightx_invert; controls.gamepad_camera_invert_y = settings.gamepad_righty_invert; + controls.quick_turn_speed_control = settings.quick_turn_speed; + controls.quick_turn_speed_mkb_control = settings.quick_turn_speed_mkb; return controls; } @@ -2786,6 +2792,8 @@ namespace MainMenu { file->property("turn_sensitivity_y", turn_sensitivity_y); file->property("gamepad_camera_invert_x", gamepad_camera_invert_x); file->property("gamepad_camera_invert_y", gamepad_camera_invert_y); + file->property("quick_turn_speed_control", quick_turn_speed_control); + file->property("quick_turn_speed_mkb_control", quick_turn_speed_mkb_control); return true; } @@ -3009,7 +3017,7 @@ namespace MainMenu { } bool AllSettings::serialize(FileInterface* file) { - int version = 20; + int version = 21; file->property("version", version); file->property("mods", mods); file->property("crossplay_enabled", crossplay_enabled); @@ -3116,6 +3124,8 @@ namespace MainMenu { file->property("gamepad_camera_invert_x", controls.gamepad_camera_invert_x); file->property("gamepad_camera_invert_y", controls.gamepad_camera_invert_y); } + file->propertyVersion("quick_turn_speed_control", version >= 21, controls.quick_turn_speed_control); + file->propertyVersion("quick_turn_speed_mkb_control", version >= 21, controls.quick_turn_speed_mkb_control); for (int c = 0; c < MAX_SPLITSCREEN; ++c) { this->controls[c] = controls; } @@ -4989,6 +4999,12 @@ namespace MainMenu { return buf; } + static const char* sliderFloorInt(float v) { + static char buf[8]; + snprintf(buf, sizeof(buf), "%d", (int)floor(v)); + return buf; + }; + static int settingsAddSlider( Frame& frame, int y, @@ -6904,6 +6920,9 @@ namespace MainMenu { y += settingsAddBooleanOption(*settings_subwindow, y, "mkb_world_tooltips", Language::get(5226), Language::get(5227), allSettings.controls[bound_player].mkb_world_tooltips_enabled, [](Button& button) {soundToggleSetting(button); allSettings.controls[bound_player].mkb_world_tooltips_enabled = button.isPressed();}); + y += settingsAddSlider(*settings_subwindow, y, "quick_turn_speed_mkb_control", Language::get(6310), Language::get(6311), + allSettings.controls[bound_player].quick_turn_speed_mkb_control, 1.f, 5.f, sliderFloorInt, [](Slider& slider) + {soundSliderSetting(slider, true); allSettings.controls[bound_player].quick_turn_speed_mkb_control = slider.getValue(); }); hookSettings(*settings_subwindow, {{Setting::Type::Customize, "bindings"}, @@ -6912,6 +6931,7 @@ namespace MainMenu { {Setting::Type::Boolean, "reverse_mouse"}, {Setting::Type::Boolean, "smooth_mouse"}, {Setting::Type::Boolean, "mkb_world_tooltips"}, + {Setting::Type::Slider, "quick_turn_speed_mkb_control"}, }); } @@ -6934,6 +6954,10 @@ namespace MainMenu { y += settingsAddBooleanOption(*settings_subwindow, y, "gamepad_camera_invert_y", Language::get(5239), Language::get(5240), allSettings.controls[bound_player].gamepad_camera_invert_y, [](Button& button) {soundToggleSetting(button); allSettings.controls[bound_player].gamepad_camera_invert_y = button.isPressed();}); + + y += settingsAddSlider(*settings_subwindow, y, "quick_turn_speed_control", Language::get(6310), Language::get(6311), + allSettings.controls[bound_player].quick_turn_speed_control, 1.f, 5.f, sliderFloorInt, [](Slider& slider) + {soundSliderSetting(slider, true); allSettings.controls[bound_player].quick_turn_speed_control = slider.getValue(); }); hookSettings(*settings_subwindow, {{Setting::Type::Customize, "bindings"}, @@ -6942,6 +6966,7 @@ namespace MainMenu { {Setting::Type::Slider, "turn_sensitivity_y"}, {Setting::Type::Boolean, "gamepad_camera_invert_x"}, {Setting::Type::Boolean, "gamepad_camera_invert_y"}, + {Setting::Type::Slider, "quick_turn_speed_control"}, }); } From 81a1f36749c8e8be143c4f9c18df55a787c77eef Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 14:46:53 +1100 Subject: [PATCH 239/244] * thank the tank can handle multiple enemies and arrow attacks, rhythm of the knight only awards achievement once --- src/actarrow.cpp | 15 +++++++++++++++ src/entity.cpp | 5 +---- src/game.cpp | 3 --- src/scores.cpp | 42 +++++++++++++++++++++++------------------- src/scores.hpp | 3 ++- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/actarrow.cpp b/src/actarrow.cpp index 7a738ac8d..c0948309d 100644 --- a/src/actarrow.cpp +++ b/src/actarrow.cpp @@ -1215,6 +1215,21 @@ void actArrow(Entity* my) } } + if ( hit.entity->behavior == &actPlayer ) + { + if ( parent ) + { + if ( hitstats->defending ) + { + updateAchievementThankTheTank(hit.entity->skill[2], parent, false); + } + else + { + achievementThankTheTankPair[hit.entity->skill[2]].erase(parent->getUID()); + } + } + } + bool armorDegraded = false; // hit armor degrade diff --git a/src/entity.cpp b/src/entity.cpp index a8b7745f3..bb7f3ae79 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -10962,10 +10962,6 @@ void Entity::attack(int pose, int charge, Entity* target) messagePlayerMonsterEvent(playerhit, color, *myStats, Language::get(698), Language::get(699), MSG_ATTACKS); if ( playerhit >= 0 ) { - if ( !achievementStatusThankTheTank[playerhit] ) - { - achievementThankTheTankPair[playerhit] = std::make_pair(0, 0); - } if ( behavior == &actMonster ) { updateAchievementBaitAndSwitch(playerhit, false); @@ -11044,6 +11040,7 @@ void Entity::attack(int pose, int charge, Entity* target) { achievementStatusRhythmOfTheKnight[playerhit] = false; achievementRhythmOfTheKnightVec[playerhit].clear(); + achievementThankTheTankPair[playerhit].erase(this->getUID()); //messagePlayer(0, "used AC!"); } } diff --git a/src/game.cpp b/src/game.cpp index 1614bb8a9..764f6a606 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1538,9 +1538,6 @@ void gameLogic(void) if ( achievementStatusThankTheTank[c] ) { steamAchievementClient(c, "BARONY_ACH_THANK_THE_TANK"); - achievementStatusThankTheTank[c] = false; - achievementThankTheTankPair[c].first = 0; - achievementThankTheTankPair[c].second = 0; } int bodyguards = 0; diff --git a/src/scores.cpp b/src/scores.cpp index 13484ac93..097c6f7b1 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -40,7 +40,8 @@ Sint32 conductGameChallenges[NUM_CONDUCT_CHALLENGES] = { 0 }; // additional 'con Sint32 gameStatistics[NUM_GAMEPLAY_STATISTICS] = { 0 }; // general saved game statistics to be stored in here. std::vector> achievementRhythmOfTheKnightVec[MAXPLAYERS] = {}; bool achievementStatusRhythmOfTheKnight[MAXPLAYERS] = { false }; -std::pair achievementThankTheTankPair[MAXPLAYERS] = { std::make_pair(0, 0) }; +bool achievementRhythmOfTheKnight[MAXPLAYERS] = { false }; +std::map achievementThankTheTankPair[MAXPLAYERS]; bool achievementStatusBaitAndSwitch[MAXPLAYERS] = { false }; Uint32 achievementBaitAndSwitchTimer[MAXPLAYERS] = { 0 }; std::unordered_set clientLearnedAlchemyIngredients[MAXPLAYERS]; @@ -3453,11 +3454,11 @@ void setDefaultPlayerConducts() for ( int c = 0; c < MAXPLAYERS; ++c ) { achievementStatusRhythmOfTheKnight[c] = false; + achievementRhythmOfTheKnight[c] = false; achievementStatusStrobe[c] = false; achievementStatusThankTheTank[c] = false; achievementRhythmOfTheKnightVec[c].clear(); - achievementThankTheTankPair[c].first = 0; - achievementThankTheTankPair[c].second = 0; + achievementThankTheTankPair[c].clear(); achievementStrobeVec[c].clear(); achievementStatusBaitAndSwitch[c] = false; achievementBaitAndSwitchTimer[c] = 0; @@ -4023,7 +4024,11 @@ void updateAchievementRhythmOfTheKnight(int player, Entity* target, bool playerI { //messagePlayer(0, "achievement get!, time taken %f", timeTaken); achievementStatusRhythmOfTheKnight[player] = true; - steamAchievementClient(player, "BARONY_ACH_RHYTHM_OF_THE_KNIGHT"); + if ( !achievementRhythmOfTheKnight[player] ) + { + steamAchievementClient(player, "BARONY_ACH_RHYTHM_OF_THE_KNIGHT"); + achievementRhythmOfTheKnight[player] = true; + } } achievementRhythmOfTheKnightVec[player].clear(); } @@ -4100,40 +4105,39 @@ void updateAchievementThankTheTank(int player, Entity* target, bool targetKilled { return; } - if ( !target ) + if ( !target || target->behavior != &actMonster ) { return; } - if ( achievementStatusThankTheTank[player] || multiplayer == CLIENT ) + if ( multiplayer == CLIENT ) { return; } + + auto& entry = achievementThankTheTankPair[player][target->getUID()]; if ( !targetKilled ) { - achievementThankTheTankPair[player] = std::make_pair(ticks, target->getUID()); // track the monster UID defending against + entry = ticks; //messagePlayer(0, "pair: %d, %d", achievementThankTheTankPair[player].first, achievementThankTheTankPair[player].second); } - else if ( achievementThankTheTankPair[player].first != 0 - && achievementThankTheTankPair[player].second != 0 ) // check there is a ticks/UID entry. + else if ( entry != 0 ) // check there is a ticks/UID entry. { if ( players[player] && players[player]->entity ) { if ( players[player]->entity->checkEnemy(target) ) { - if ( target->getUID() == achievementThankTheTankPair[player].second ) + // check timestamp within 3 seconds. + if ( (ticks - entry) / 50.f < 3.f ) { - // same target dying, check timestamp within 3 seconds. - if ( (ticks - achievementThankTheTankPair[player].first) / 50.f < 3.f ) + achievementStatusThankTheTank[player] = true; + achievementThankTheTankPair[player].erase(target->getUID()); + if ( players[player]->mechanics.allowedRaiseBlockingAgainstEntity(*target) ) { - achievementStatusThankTheTank[player] = true; - if ( players[player]->mechanics.allowedRaiseBlockingAgainstEntity(*target) ) + int skillLVL = 3 * (stats[player]->getProficiency(PRO_SHIELD) / 20); + if ( local_rng.rand() % (5 + skillLVL) == 0 ) { - int skillLVL = 3 * (stats[player]->getProficiency(PRO_SHIELD) / 20); - if ( local_rng.rand() % (5 + skillLVL) == 0 ) - { - players[player]->entity->increaseSkill(PRO_SHIELD); - } + players[player]->entity->increaseSkill(PRO_SHIELD); } } } diff --git a/src/scores.hpp b/src/scores.hpp index 990816650..ba6507418 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -288,7 +288,8 @@ extern Sint32 conductGameChallenges[NUM_CONDUCT_CHALLENGES]; extern Sint32 gameStatistics[NUM_GAMEPLAY_STATISTICS]; extern std::vector> achievementRhythmOfTheKnightVec[MAXPLAYERS]; extern bool achievementStatusRhythmOfTheKnight[MAXPLAYERS]; -extern std::pair achievementThankTheTankPair[MAXPLAYERS]; +extern bool achievementRhythmOfTheKnight[MAXPLAYERS]; +extern std::map achievementThankTheTankPair[MAXPLAYERS]; extern bool achievementStatusBaitAndSwitch[MAXPLAYERS]; extern Uint32 achievementBaitAndSwitchTimer[MAXPLAYERS]; extern std::unordered_set clientLearnedAlchemyIngredients[MAXPLAYERS]; From de0e165a319c5d22c6c08f3325253f05319ac1b5 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 15:06:39 +1100 Subject: [PATCH 240/244] * torch prevent skill increase above 40, add missing checks --- src/entity.cpp | 24 ++++++++++++++++++++++-- src/scores.cpp | 14 +++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/entity.cpp b/src/entity.cpp index bb7f3ae79..6ebba6440 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -10359,7 +10359,17 @@ void Entity::attack(int pose, int charge, Entity* target) { increaseSkill = false; } - players[this->skill[2]]->mechanics.enemyRaisedBlockingAgainst[hit.entity->getUID()]++; + if ( myStats->shield && itemCategory(myStats->shield) != ARMOR ) + { + if ( myStats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) + { + increaseSkill = false; + } + } + if ( increaseSkill ) + { + players[this->skill[2]]->mechanics.enemyRaisedBlockingAgainst[hit.entity->getUID()]++; + } } if ( increaseSkill ) { @@ -11022,7 +11032,17 @@ void Entity::attack(int pose, int charge, Entity* target) { skillIncrease = false; } - players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; + if ( hitstats->shield && itemCategory(hitstats->shield) != ARMOR ) + { + if ( hitstats->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) + { + skillIncrease = false; + } + } + if ( skillIncrease ) + { + players[hit.entity->skill[2]]->mechanics.enemyRaisedBlockingAgainst[this->getUID()]++; + } } if ( skillIncrease ) { diff --git a/src/scores.cpp b/src/scores.cpp index 097c6f7b1..f55415630 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -4137,7 +4137,19 @@ void updateAchievementThankTheTank(int player, Entity* target, bool targetKilled int skillLVL = 3 * (stats[player]->getProficiency(PRO_SHIELD) / 20); if ( local_rng.rand() % (5 + skillLVL) == 0 ) { - players[player]->entity->increaseSkill(PRO_SHIELD); + bool increase = true; + if ( stats[player]->shield && itemCategory(stats[player]->shield) != ARMOR ) + { + if ( stats[player]->getProficiency(PRO_SHIELD) >= SKILL_LEVEL_SKILLED ) + { + increase = false; + } + } + if ( increase ) + { + players[player]->entity->increaseSkill(PRO_SHIELD); + players[player]->mechanics.enemyRaisedBlockingAgainst[target->getUID()]++; + } } } } From fca021ece12ea7b7dca0bc885f29dd97790a8324 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Wed, 23 Oct 2024 15:43:32 +1100 Subject: [PATCH 241/244] * custom banners.json for update notes --- src/init_game.cpp | 1 + src/ui/MainMenu.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++-- src/ui/MainMenu.hpp | 8 ++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/init_game.cpp b/src/init_game.cpp index f8a4dd2a2..655ddb2c4 100644 --- a/src/init_game.cpp +++ b/src/init_game.cpp @@ -111,6 +111,7 @@ void initGameDatafiles(bool moddedReload) CompendiumEntries.readModelLimbsFromFile("monster"); CompendiumEntries.readModelLimbsFromFile("world"); CompendiumEntries.readModelLimbsFromFile("codex"); + MainMenu::MainMenuBanners_t::readFromFile(); } void initGameDatafilesAsync(bool moddedReload) diff --git a/src/ui/MainMenu.cpp b/src/ui/MainMenu.cpp index f28eeccc6..ec6d7664c 100644 --- a/src/ui/MainMenu.cpp +++ b/src/ui/MainMenu.cpp @@ -25756,6 +25756,61 @@ namespace MainMenu { static GetPlayersOnline getPlayersOnline; #endif + std::string MainMenuBanners_t::updateBannerImg = ""; + std::string MainMenuBanners_t::updateBannerImgHighlight = ""; + std::string MainMenuBanners_t::updateBannerURL = ""; + void MainMenuBanners_t::readFromFile() + { + const std::string filename = "data/banners.json"; + if ( !PHYSFS_getRealDir(filename.c_str()) ) + { + printlog("[JSON]: Error: Could not locate json file %s", filename.c_str()); + return; + } + + std::string inputPath = PHYSFS_getRealDir(filename.c_str()); + inputPath.append(PHYSFS_getDirSeparator()); + inputPath.append(filename.c_str()); + + File* fp = FileIO::open(inputPath.c_str(), "rb"); + if ( !fp ) + { + printlog("[JSON]: Error: Could not locate json file %s", inputPath.c_str()); + return; + } + + char buf[2048]; + int count = fp->read(buf, sizeof(buf[0]), sizeof(buf) - 1); + buf[count] = '\0'; + rapidjson::StringStream is(buf); + FileIO::close(fp); + + rapidjson::Document d; + d.ParseStream(is); + if ( !d.HasMember("version") ) + { + printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str()); + return; + } + + updateBannerImg = ""; + updateBannerImgHighlight = ""; + updateBannerURL = ""; + + if ( d.HasMember("update_banner_img") && d["update_banner_img"].IsString() ) + { + updateBannerImg = d["update_banner_img"].GetString(); + } + if ( d.HasMember("update_banner_img_highlight") && d["update_banner_img_highlight"].IsString() ) + { + updateBannerImgHighlight = d["update_banner_img_highlight"].GetString(); + } + if ( d.HasMember("update_banner_link") && d["update_banner_link"].IsString() ) + { + updateBannerURL = d["update_banner_link"].GetString(); + } + } + void createMainMenu(bool ingame) { if (!ingame) { clientnum = 0; @@ -26190,14 +26245,22 @@ namespace MainMenu { #else const char* banner_images[][2] = { { - "*#images/ui/Main Menus/Banners/banner_eatmyhat.png", - "*#images/ui/Main Menus/Banners/banner_eatmyhat-hover.png", + "", + "", }, { "*#images/ui/Main Menus/Banners/UI_MainMenu_ComboBanner1_base.png", "*#images/ui/Main Menus/Banners/UI_MainMenu_ComboBanner1_high.png", } }; + + if ( MainMenuBanners_t::updateBannerImg != "" + && MainMenuBanners_t::updateBannerImgHighlight != "" + && MainMenuBanners_t::updateBannerURL != "" ) + { + banner_images[0][0] = MainMenuBanners_t::updateBannerImg.c_str(); + banner_images[0][1] = MainMenuBanners_t::updateBannerImgHighlight.c_str(); + } // customize DLC banner. { @@ -26217,7 +26280,7 @@ namespace MainMenu { void(*banner_funcs[])(Button&) = { [](Button&) { // banner #1 - openURLTryWithOverlay("https://www.baronygame.com/blog/eat-my-hat-release"); + openURLTryWithOverlay(MainMenuBanners_t::updateBannerURL.c_str()); }, [](Button&) { // banner #2 openDLCPrompt(enabledDLCPack1 ? 1 : 0); diff --git a/src/ui/MainMenu.hpp b/src/ui/MainMenu.hpp index ab2739117..53d1b6f22 100644 --- a/src/ui/MainMenu.hpp +++ b/src/ui/MainMenu.hpp @@ -192,4 +192,12 @@ namespace MainMenu { static void update_details_text(Frame& card); static void update_details_text(Frame& card, void* stats); }; + + struct MainMenuBanners_t + { + static std::string updateBannerImg; + static std::string updateBannerImgHighlight; + static std::string updateBannerURL; + static void readFromFile(); + }; } From cb3c4693e82f96318f7a093b986236bd05b6f2e1 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 24 Oct 2024 07:30:36 +1100 Subject: [PATCH 242/244] * update global stats --- src/eos.cpp | 138 ++++++++++++++++++++++++------------------------ src/main.cpp | 70 ------------------------ src/main.hpp | 2 - src/menu.cpp | 8 ++- src/playfab.cpp | 69 ++++++++++++++++++++++++ src/playfab.hpp | 2 + src/scores.cpp | 14 +++++ src/scores.hpp | 79 ++++++++++++++++++++++++++- 8 files changed, 239 insertions(+), 143 deletions(-) diff --git a/src/eos.cpp b/src/eos.cpp index cab900b6b..1e9a5e0fd 100644 --- a/src/eos.cpp +++ b/src/eos.cpp @@ -3166,7 +3166,7 @@ void EOSFuncs::ingestStat(int stat_num, int value) static void EOS_CALL OnIngestGlobalStatComplete(const EOS_Stats_IngestStatCompleteCallbackInfo* data) { - assert(data != NULL); + /*assert(data != NULL); if (data->ResultCode == EOS_EResult::EOS_Success) { EOSFuncs::logInfo("Successfully stored global stats"); @@ -3184,12 +3184,12 @@ static void EOS_CALL OnIngestGlobalStatComplete(const EOS_Stats_IngestStatComple { EOSFuncs::logError("OnIngestGlobalStatComplete: Callback failure: %d", static_cast(data->ResultCode)); } - EOS.StatGlobalManager.bDataQueued = true; + EOS.StatGlobalManager.bDataQueued = true;*/ } void EOSFuncs::queueGlobalStatUpdate(int stat_num, int value) { - if (stat_num <= STEAM_GSTAT_INVALID || stat_num >= NUM_GLOBAL_STEAM_STATISTICS) + /*if (stat_num <= STEAM_GSTAT_INVALID || stat_num >= NUM_GLOBAL_STEAM_STATISTICS) { return; } @@ -3198,7 +3198,7 @@ void EOSFuncs::queueGlobalStatUpdate(int stat_num, int value) return; } g_SteamGlobalStats[stat_num].m_iValue += value; - StatGlobalManager.bDataQueued = true; + StatGlobalManager.bDataQueued = true;*/ } void EOSFuncs::StatGlobal_t::updateQueuedStats() @@ -3218,55 +3218,55 @@ void EOSFuncs::StatGlobal_t::updateQueuedStats() void EOSFuncs::ingestGlobalStats() { - if (StatGlobalManager.bIsDisabled) - { - return; - } - if (!ServerPlatformHandle) - { - return; - } + //if (StatGlobalManager.bIsDisabled) + //{ + // return; + //} + //if (!ServerPlatformHandle) + //{ + // return; + //} - Uint32 numStats = 0; - std::vector StatNames; - for (Uint32 i = 0; i < NUM_GLOBAL_STEAM_STATISTICS; ++i) - { - if (g_SteamGlobalStats[i].m_iValue > 0) - { - StatNames.push_back(g_SteamGlobalStats[i].m_pchStatName); - ++numStats; - } - } + //Uint32 numStats = 0; + //std::vector StatNames; + //for (Uint32 i = 0; i < NUM_GLOBAL_STEAM_STATISTICS; ++i) + //{ + // if (g_SteamGlobalStats[i].m_iValue > 0) + // { + // StatNames.push_back(g_SteamGlobalStats[i].m_pchStatName); + // ++numStats; + // } + //} - if (numStats == 0) - { - return; - } + //if (numStats == 0) + //{ + // return; + //} - EOS_Stats_IngestData* StatsToIngest = new EOS_Stats_IngestData[numStats]; - Uint32 currentIndex = 0; - for (Uint32 i = 0; i < NUM_GLOBAL_STEAM_STATISTICS && currentIndex < StatNames.size(); ++i) - { - if (g_SteamGlobalStats[i].m_iValue > 0) - { - StatsToIngest[currentIndex].ApiVersion = EOS_STATS_INGESTDATA_API_LATEST; - StatsToIngest[currentIndex].StatName = StatNames[currentIndex].c_str(); - StatsToIngest[currentIndex].IngestAmount = g_SteamGlobalStats[i].m_iValue; - //logInfo("Updated %s | %d", StatsToIngest[currentIndex].StatName, StatsToIngest[currentIndex].IngestAmount); - ++currentIndex; - } - } + //EOS_Stats_IngestData* StatsToIngest = new EOS_Stats_IngestData[numStats]; + //Uint32 currentIndex = 0; + //for (Uint32 i = 0; i < NUM_GLOBAL_STEAM_STATISTICS && currentIndex < StatNames.size(); ++i) + //{ + // if (g_SteamGlobalStats[i].m_iValue > 0) + // { + // StatsToIngest[currentIndex].ApiVersion = EOS_STATS_INGESTDATA_API_LATEST; + // StatsToIngest[currentIndex].StatName = StatNames[currentIndex].c_str(); + // StatsToIngest[currentIndex].IngestAmount = g_SteamGlobalStats[i].m_iValue; + // //logInfo("Updated %s | %d", StatsToIngest[currentIndex].StatName, StatsToIngest[currentIndex].IngestAmount); + // ++currentIndex; + // } + //} - EOS_Stats_IngestStatOptions Options{}; - Options.ApiVersion = EOS_STATS_INGESTSTAT_API_LATEST; - Options.Stats = StatsToIngest; - Options.StatsCount = numStats; - Options.LocalUserId = StatGlobalManager.getProductUserIdHandle(); - Options.TargetUserId = StatGlobalManager.getProductUserIdHandle(); + //EOS_Stats_IngestStatOptions Options{}; + //Options.ApiVersion = EOS_STATS_INGESTSTAT_API_LATEST; + //Options.Stats = StatsToIngest; + //Options.StatsCount = numStats; + //Options.LocalUserId = StatGlobalManager.getProductUserIdHandle(); + //Options.TargetUserId = StatGlobalManager.getProductUserIdHandle(); - EOS_Stats_IngestStat(EOS_Platform_GetStatsInterface(ServerPlatformHandle), &Options, nullptr, OnIngestGlobalStatComplete); + //EOS_Stats_IngestStat(EOS_Platform_GetStatsInterface(ServerPlatformHandle), &Options, nullptr, OnIngestGlobalStatComplete); - delete[] StatsToIngest; + //delete[] StatsToIngest; } void EOS_CALL EOSFuncs::OnQueryAllStatsCallback(const EOS_Stats_OnQueryStatsCompleteCallbackInfo* data) @@ -4063,34 +4063,34 @@ void EOSFuncs::StatGlobal_t::init() void EOSFuncs::StatGlobal_t::queryGlobalStatUser() { - init(); + //init(); - // Query Player Stats - EOS_Stats_QueryStatsOptions StatsQueryOptions{}; - StatsQueryOptions.ApiVersion = EOS_STATS_QUERYSTATS_API_LATEST; - StatsQueryOptions.LocalUserId = getProductUserIdHandle(); - StatsQueryOptions.TargetUserId = getProductUserIdHandle(); + //// Query Player Stats + //EOS_Stats_QueryStatsOptions StatsQueryOptions{}; + //StatsQueryOptions.ApiVersion = EOS_STATS_QUERYSTATS_API_LATEST; + //StatsQueryOptions.LocalUserId = getProductUserIdHandle(); + //StatsQueryOptions.TargetUserId = getProductUserIdHandle(); - // Optional params - StatsQueryOptions.StartTime = EOS_STATS_TIME_UNDEFINED; - StatsQueryOptions.EndTime = EOS_STATS_TIME_UNDEFINED; + //// Optional params + //StatsQueryOptions.StartTime = EOS_STATS_TIME_UNDEFINED; + //StatsQueryOptions.EndTime = EOS_STATS_TIME_UNDEFINED; - StatsQueryOptions.StatNamesCount = NUM_GLOBAL_STEAM_STATISTICS; - StatsQueryOptions.StatNames = new const char* [NUM_GLOBAL_STEAM_STATISTICS]; + //StatsQueryOptions.StatNamesCount = NUM_GLOBAL_STEAM_STATISTICS; + //StatsQueryOptions.StatNames = new const char* [NUM_GLOBAL_STEAM_STATISTICS]; - for (int i = 0; i < NUM_GLOBAL_STEAM_STATISTICS; ++i) - { - StatsQueryOptions.StatNames[i] = g_SteamGlobalStats[i].m_pchStatName; - } + //for (int i = 0; i < NUM_GLOBAL_STEAM_STATISTICS; ++i) + //{ + // StatsQueryOptions.StatNames[i] = g_SteamGlobalStats[i].m_pchStatName; + //} - if (EOS.ServerPlatformHandle) - { - return; - } + //if (EOS.ServerPlatformHandle) + //{ + // return; + //} - EOS_Stats_QueryStats(EOS_Platform_GetStatsInterface(EOS.ServerPlatformHandle), - &StatsQueryOptions, nullptr, OnQueryGlobalStatsCallback); - delete[] StatsQueryOptions.StatNames; + //EOS_Stats_QueryStats(EOS_Platform_GetStatsInterface(EOS.ServerPlatformHandle), + // &StatsQueryOptions, nullptr, OnQueryGlobalStatsCallback); + //delete[] StatsQueryOptions.StatNames; } #endif //USE_EOS diff --git a/src/main.cpp b/src/main.cpp index a2f62a885..b88fdda09 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -301,76 +301,6 @@ SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS] = { 58, STEAM_STAT_INT, "STAT_SMASH_MELEE" } }; -SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS] = -{ - { 1, STEAM_STAT_INT, "STAT_GLOBAL_GAMES_STARTED" }, - { 2, STEAM_STAT_INT, "STAT_GLOBAL_GAMES_WON" }, - { 3, STEAM_STAT_INT, "STAT_GLOBAL_BOULDER_DEATHS" }, - { 4, STEAM_STAT_INT, "STAT_GLOBAL_HERX_SLAIN" }, - { 5, STEAM_STAT_INT, "STAT_GLOBAL_BAPHOMET_SLAIN" }, - { 6, STEAM_STAT_INT, "STAT_GLOBAL_TWINSICE_SLAIN" }, - { 7, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_HUMAN" }, - { 8, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_RAT" }, - { 9, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_GOBLIN" }, - { 10, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SLIME" }, - { 11, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_TROLL" }, - { 12, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SPIDER" }, - { 13, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_GHOUL" }, - { 14, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SKELETON" }, - { 15, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SCORPION" }, - { 16, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_IMP" }, - { 17, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_GNOME" }, - { 18, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_DEMON" }, - { 19, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SUCCUBUS" }, - { 20, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_LICH" }, - { 21, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_MINOTAUR" }, - { 22, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_DEVIL" }, - { 23, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SHOPKEEPER" }, - { 24, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_KOBOLD" }, - { 25, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SCARAB" }, - { 26, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_CRYSTALGOLEM" }, - { 27, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_INCUBUS" }, - { 28, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_VAMPIRE" }, - { 29, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SHADOW" }, - { 30, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_COCKATRICE" }, - { 31, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_INSECTOID" }, - { 32, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_GOATMAN" }, - { 33, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_AUTOMATON" }, - { 34, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_LICHICE" }, - { 35, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_LICHFIRE" }, - { 36, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SENTRYBOT" }, - { 37, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_SPELLBOT" }, - { 38, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_GYROBOT" }, - { 39, STEAM_STAT_INT, "STAT_GLOBAL_DEATHS_DUMMYBOT" }, - { 40, STEAM_STAT_INT, "STAT_GLOBAL_TWINSFIRE_SLAIN" }, - { 41, STEAM_STAT_INT, "STAT_GLOBAL_SHOPKEEPERS_SLAIN" }, - { 42, STEAM_STAT_INT, "STAT_GLOBAL_MINOTAURS_SLAIN" }, - { 43, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL_ENTERED" }, - { 44, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL1_COMPLETED" }, - { 45, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL2_COMPLETED" }, - { 46, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL3_COMPLETED" }, - { 47, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL4_COMPLETED" }, - { 48, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL5_COMPLETED" }, - { 49, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL6_COMPLETED" }, - { 50, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL7_COMPLETED" }, - { 51, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL8_COMPLETED" }, - { 52, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL9_COMPLETED" }, - { 53, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL10_COMPLETED" }, - { 54, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL1_ATTEMPTS" }, - { 55, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL2_ATTEMPTS" }, - { 56, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL3_ATTEMPTS" }, - { 57, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL4_ATTEMPTS" }, - { 58, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL5_ATTEMPTS" }, - { 59, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL6_ATTEMPTS" }, - { 60, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL7_ATTEMPTS" }, - { 61, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL8_ATTEMPTS" }, - { 62, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL9_ATTEMPTS" }, - { 63, STEAM_STAT_INT, "STAT_GLOBAL_TUTORIAL10_ATTEMPTS" }, - { 64, STEAM_STAT_INT, "STAT_GLOBAL_DISABLE" }, - { 65, STEAM_STAT_INT, "STAT_GLOBAL_PROMO" }, - { 66, STEAM_STAT_INT, "STAT_GLOBAL_PROMO_INTERACT" } -}; - #ifdef STEAMWORKS bool directConnect = false; CSteamLeaderboards* g_SteamLeaderboards = NULL; diff --git a/src/main.hpp b/src/main.hpp index 3c9b77431..df08ab28f 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -892,8 +892,6 @@ void GO_SwapBuffers(SDL_Window* screen); static const int NUM_STEAM_STATISTICS = 58; extern SteamStat_t g_SteamStats[NUM_STEAM_STATISTICS]; -static const int NUM_GLOBAL_STEAM_STATISTICS = 66; -extern SteamStat_t g_SteamGlobalStats[NUM_GLOBAL_STEAM_STATISTICS]; #ifdef STEAMWORKS #include diff --git a/src/menu.cpp b/src/menu.cpp index 82f98f516..c83e80bdb 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -9354,7 +9354,13 @@ void doNewGame(bool makeHighscore) { if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_DEFAULT && !loadingsavegame ) { steamStatisticUpdate(STEAM_STAT_GAMES_STARTED, STEAM_STAT_INT, 1); - achievementObserver.updateGlobalStat(STEAM_GSTAT_GAMES_STARTED); +#ifdef USE_PLAYFAB + if ( !loadingsavegame ) + { + playfabUser.gameBegin(); + } +#endif + //achievementObserver.updateGlobalStat(STEAM_GSTAT_GAMES_STARTED); } // delete game data clutter diff --git a/src/playfab.cpp b/src/playfab.cpp index b678586e3..16d84a424 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -26,6 +26,71 @@ void PlayfabUser_t::OnEventsWrite(const PlayFab::EventsModels::WriteEventsRespon logInfo("Successfully stored events"); } +void PlayfabUser_t::gameBegin() +{ + if ( !bLoggedIn ) + { + return; + } + + if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_DEFAULT ) + { + return; + } + PlayFab::EventsModels::WriteEventsRequest eventRequest; + PlayFab::EventsModels::EventContents eventContent; + eventContent.EventNamespace = "custom.game"; + eventContent.Name = "gamestart"; + eventContent.Payload["class"] = client_classes[clientnum]; + eventContent.Payload["multiplayer"] = multiplayer; + int players = 1; + if ( multiplayer == SERVER || (multiplayer == SINGLE && splitscreen) ) + { + for ( int i = 1; i < MAXPLAYERS; ++i ) + { + if ( !client_disconnected[i] ) + { + ++players; + } + } + } + eventContent.Payload["numplayers"] = players; + eventContent.Payload["splitscreen"] = (multiplayer == SINGLE && splitscreen) ? 1 : 0; + eventContent.Payload["race"] = stats[clientnum]->playerRace; + eventContent.Payload["appearance"] = stats[clientnum]->stat_appearance; + eventContent.Payload["sex"] = stats[clientnum]->sex; + eventContent.Payload["controller"] = inputs.hasController(clientnum) ? 1 : 0; + eventContent.Payload["theme"] = *cvar_disableHoliday ? 0 : 1; + eventRequest.Events.push_back(eventContent); + PlayFab::PlayFabEventsAPI::WriteTelemetryEvents(eventRequest, OnEventsWrite, OnCloudScriptFailure); +} + +void PlayfabUser_t::globalStat(int index, int value) +{ + if ( !bLoggedIn ) + { + return; + } + + if ( index < 0 || index >= STEAM_GSTAT_MAX || value < 0 ) + { + return; + } + + if ( index >= SteamGlobalStatStr.size() ) + { + return; + } + + PlayFab::EventsModels::WriteEventsRequest eventRequest; + PlayFab::EventsModels::EventContents eventContent; + eventContent.EventNamespace = "custom.globalstats"; + eventContent.Name = "globalstats"; + eventContent.Payload[SteamGlobalStatStr[index]] = value; + eventRequest.Events.push_back(eventContent); + PlayFab::PlayFabEventsAPI::WriteTelemetryEvents(eventRequest, OnEventsWrite, OnCloudScriptFailure); +} + void PlayfabUser_t::OnLoginSuccess(const PlayFab::ClientModels::LoginResult& result, void* customData) { logInfo("Logged in successfully"); @@ -503,6 +568,10 @@ int parseOnlineHiscore(SaveGameInfo& info, Json::Value score) { player.race = RACE_HUMAN; // set to human appearance for aesthetic scores } + if ( info.dungeon_lvl >= 25 && (info.svflags & SV_FLAG_CLASSIC) ) + { + info.svflags &= ~SV_FLAG_CLASSIC; // remove classic flag from scores beyond dungeon level 25 + } player.additionalConducts[CONDUCT_MODDED] = 0; // don't display this on the leaderboard window return 0; } diff --git a/src/playfab.hpp b/src/playfab.hpp index b8d7f1d68..48712f5ad 100644 --- a/src/playfab.hpp +++ b/src/playfab.hpp @@ -47,6 +47,8 @@ class PlayfabUser_t void getLeaderboardTop100(std::string lid); void getLeaderboardAroundMe(std::string lid); void getLeaderboardTop100Alternate(std::string lid); + void gameBegin(); + void globalStat(int index, int value); struct PlayerCheckLeaderboardData_t { diff --git a/src/scores.cpp b/src/scores.cpp index f55415630..1f11161fc 100644 --- a/src/scores.cpp +++ b/src/scores.cpp @@ -26,6 +26,9 @@ #include "collision.hpp" #include "mod_tools.hpp" #include "lobbies.hpp" +#ifdef USE_PLAYFAB +#include "playfab.hpp" +#endif // definitions list_t topscores; @@ -5676,12 +5679,17 @@ void AchievementObserver::updateGlobalStat(int index, int value) { return; } +#ifndef DEBUG_ACHIEVEMENTS if ( conductGameChallenges[CONDUCT_CHEATS_ENABLED] || conductGameChallenges[CONDUCT_MODDED_NO_ACHIEVEMENTS] || Mods::disableSteamAchievements ) { return; } +#endif +#ifdef USE_PLAYFAB + playfabUser.globalStat(index, value); +#endif #if defined USE_EOS EOS.queueGlobalStatUpdate(index, value); #endif @@ -5757,6 +5765,12 @@ SteamGlobalStatIndexes getIndexForDeathType(int type) return STEAM_GSTAT_DEATHS_GYROBOT; case DUMMYBOT: return STEAM_GSTAT_DEATHS_DUMMYBOT; + case BAT_SMALL: + return STEAM_GSTAT_DEATHS_BAT; + case BUGBEAR: + return STEAM_GSTAT_DEATHS_BUGBEAR; + case MIMIC: + return STEAM_GSTAT_DEATHS_MIMIC; default: return STEAM_GSTAT_INVALID; } diff --git a/src/scores.hpp b/src/scores.hpp index ba6507418..4af0f26c8 100644 --- a/src/scores.hpp +++ b/src/scores.hpp @@ -186,7 +186,84 @@ enum SteamGlobalStatIndexes : int STEAM_GSTAT_TUTORIAL10_ATTEMPTS, STEAM_GSTAT_DISABLE, STEAM_GSTAT_PROMO, - STEAM_GSTAT_PROMO_INTERACT + STEAM_GSTAT_PROMO_INTERACT, + STEAM_GSTAT_DEATHS_BAT, + STEAM_GSTAT_DEATHS_BUGBEAR, + STEAM_GSTAT_DEATHS_MIMIC, + STEAM_GSTAT_MAX +}; + +const std::vector SteamGlobalStatStr = +{ + "GAMES_STARTED", + "GAMES_WON", + "BOULDER_DEATHS", + "HERX_SLAIN", + "BAPHOMET_SLAIN", + "TWINSFIRE_SLAIN", + "DEATHS_HUMAN", + "DEATHS_RAT", + "DEATHS_GOBLIN", + "DEATHS_SLIME", + "DEATHS_TROLL", + "DEATHS_SPIDER", + "DEATHS_GHOUL", + "DEATHS_SKELETON", + "DEATHS_SCORPION", + "DEATHS_IMP", + "DEATHS_GNOME", + "DEATHS_DEMON", + "DEATHS_SUCCUBUS", + "DEATHS_LICH", + "DEATHS_MINOTAUR", + "DEATHS_DEVIL", + "DEATHS_SHOPKEEPER", + "DEATHS_KOBOLD", + "DEATHS_SCARAB", + "DEATHS_CRYSTALGOLEM", + "DEATHS_INCUBUS", + "DEATHS_VAMPIRE", + "DEATHS_SHADOW", + "DEATHS_COCKATRICE", + "DEATHS_INSECTOID", + "DEATHS_GOATMAN", + "DEATHS_AUTOMATON", + "DEATHS_LICHICE", + "DEATHS_LICHFIRE", + "DEATHS_SENTRYBOT", + "DEATHS_SPELLBOT", + "DEATHS_GYROBOT", + "DEATHS_DUMMYBOT", + "TWINSICE_SLAIN", + "SHOPKEEPERS_SLAIN", + "MINOTAURS_SLAIN", + "TUTORIAL_ENTERED", + "TUTORIAL1_COMPLETED", + "TUTORIAL2_COMPLETED", + "TUTORIAL3_COMPLETED", + "TUTORIAL4_COMPLETED", + "TUTORIAL5_COMPLETED", + "TUTORIAL6_COMPLETED", + "TUTORIAL7_COMPLETED", + "TUTORIAL8_COMPLETED", + "TUTORIAL9_COMPLETED", + "TUTORIAL10_COMPLETED", + "TUTORIAL1_ATTEMPTS", + "TUTORIAL2_ATTEMPTS", + "TUTORIAL3_ATTEMPTS", + "TUTORIAL4_ATTEMPTS", + "TUTORIAL5_ATTEMPTS", + "TUTORIAL6_ATTEMPTS", + "TUTORIAL7_ATTEMPTS", + "TUTORIAL8_ATTEMPTS", + "TUTORIAL9_ATTEMPTS", + "TUTORIAL10_ATTEMPTS", + "DISABLE", + "PROMO", + "PROMO_INTERACT", + "DEATHS_BAT", + "DEATHS_BUGBEAR", + "DEATHS_MIMIC", }; SteamGlobalStatIndexes getIndexForDeathType(int type); From c238b004b523950d469d900136326696c016b29e Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 24 Oct 2024 08:03:37 +1100 Subject: [PATCH 243/244] * track version number --- src/playfab.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/playfab.cpp b/src/playfab.cpp index 16d84a424..937240c1b 100644 --- a/src/playfab.cpp +++ b/src/playfab.cpp @@ -55,6 +55,7 @@ void PlayfabUser_t::gameBegin() } } eventContent.Payload["numplayers"] = players; + eventContent.Payload["version"] = VERSION; eventContent.Payload["splitscreen"] = (multiplayer == SINGLE && splitscreen) ? 1 : 0; eventContent.Payload["race"] = stats[clientnum]->playerRace; eventContent.Payload["appearance"] = stats[clientnum]->stat_appearance; From eb956cbb77e8b1f7a7cafc1d0af0ed8de8020ac0 Mon Sep 17 00:00:00 2001 From: WALL OF JUSTICE <-> Date: Thu, 24 Oct 2024 08:03:53 +1100 Subject: [PATCH 244/244] * polymorph preserves charmed status --- src/magic/magic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/magic/magic.cpp b/src/magic/magic.cpp index c5dd02e1b..c1acfafc2 100644 --- a/src/magic/magic.cpp +++ b/src/magic/magic.cpp @@ -1968,6 +1968,7 @@ Entity* spellEffectPolymorph(Entity* target, Entity* parent, bool fromMagicSpell summonedStats->RANDOM_GOLD = 0; summonedStats->MISC_FLAGS[STAT_FLAG_MONSTER_DISABLE_HC_SCALING] = 1; summonedStats->leader_uid = targetStats->leader_uid; + summonedStats->monsterIsCharmed = targetStats->monsterIsCharmed; if ( summonedStats->leader_uid != 0 && summonedStats->type != SHADOW ) { Entity* leader = uidToEntity(summonedStats->leader_uid);