Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,12 @@ if (GAME_ENABLED)
set_target_properties(barony PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "\$(BARONY_DATADIR)/")
endif()
target_link_libraries(barony ${SDL2_LIBRARIES} ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_NET_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${SDL2TTF_LIBRARY})
if(WIN32) #mod edit
target_link_libraries(barony
"${BARONY_WIN32_LIBRARIES}/lib/glew32.lib"
"${BARONY_WIN32_LIBRARIES}/lib/manual-link/SDL2main.lib"
)
endif() #mod edit end
if (STEAMWORKS_ENABLED)
target_link_libraries(barony ${STEAMWORKS_LIBRARY})
target_link_libraries(barony ${NFD_LIBRARY})
Expand Down Expand Up @@ -601,6 +607,12 @@ if (GAME_ENABLED)
target_link_libraries(barony -L/usr/lib64 -lstdc++)
endif()
target_link_libraries(barony ${SDL2_LIBRARIES} ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_NET_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${SDL2TTF_LIBRARY})
if(WIN32)
target_link_libraries(barony
"${BARONY_WIN32_LIBRARIES}/lib/glew32.lib"
"${BARONY_WIN32_LIBRARIES}/lib/manual-link/SDL2main.lib"
)
endif()
if (STEAMWORKS_ENABLED)
target_link_libraries(barony ${STEAMWORKS_LIBRARY})
target_link_libraries(barony ${NFD_LIBRARY})
Expand Down Expand Up @@ -716,6 +728,12 @@ if (EDITOR_ENABLED)
#target_link_libraries(${EDITOR_EXE_NAME} OpenSSL::Crypto)
endif()
target_link_libraries(${EDITOR_EXE_NAME} ${SDL2_LIBRARIES} ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} ${SDL2_NET_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${SDL2TTF_LIBRARY})
if(WIN32) #mod edit: local Windows editor dependencies
target_link_libraries(${EDITOR_EXE_NAME}
"${BARONY_WIN32_LIBRARIES}/vcpkg_installed/x64-windows/lib/glew32.lib"
"${BARONY_WIN32_LIBRARIES}/lib/SDL2main.lib"
)
endif()
if(STEAMWORKS_ENABLED)
target_link_libraries(${EDITOR_EXE_NAME} ${STEAMWORKS_LIBRARY})
target_link_libraries(${EDITOR_EXE_NAME} ${NFD_LIBRARY})
Expand Down
19 changes: 19 additions & 0 deletions lang/en.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7270,5 +7270,24 @@ has been unlocked!#
6996 Mouse Events Limit#
6997 Limits the amount of mouse movement events processed per frame to prevent large camera swings.#
6998 Unlimited#
6999 The water ruins the loaded shot in your %s.#
7000 The water ruins the loaded shots in %d of your firearms.#
7001 Your %s jams!#
7002 You clear the jam in your %s.#
7003 You need %d %s to load your %s.#
7004 You begin loading your %s.#
7005 You finish loading your %s.#
7006 You no longer have enough %s to load your %s.#
7007 You stop loading your firearm.#
7008 Entrench cannot move that object.#
7009 Entrench cannot move a locked door.#
7010 You lift the object with Entrench.#
7011 The carried object is gone.#
7012 Entrench cannot place there.#
7013 That destination is occupied.#
7014 Entrench cannot place that object on this surface.#
7015 You lay the door as a bridge.#
7016 You place the entrenched object.#
7017 The charge is ruined.#

7999 END#
25 changes: 24 additions & 1 deletion lang/item_names.json
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@
"name_identified": "pair of glasses",
"name_unidentified": "pair of glasses"
},
"spyglass": {
"name_identified": "spyglass",
"name_unidentified": "spyglass"
},
"tool_beartrap": {
"name_identified": "beartrap",
"name_unidentified": "beartrap"
Expand Down Expand Up @@ -2096,6 +2100,22 @@
"spellbook_dominate": {
"name_identified": "spellbook of dominate",
"name_unidentified": "spellbook"
},
"harpoon": {
"name_identified": "harpoon",
"name_unidentified": "harpoon"
},
"flintlock_pistol": {
"name_identified": "flintlock pistol",
"name_unidentified": "flintlock pistol"
},
"musket": {
"name_identified": "musket",
"name_unidentified": "musket"
},
"spellbook_entrench": {
"name_identified": "spellbook of entrench",
"name_unidentified": "spellbook"
}
},
"spell_names": {
Expand Down Expand Up @@ -2734,6 +2754,9 @@
},
"spell_holy_beam": {
"name": "Holy Beam"
},
"spell_entrench": {
"name": "Entrench"
}
}
}
}
174 changes: 136 additions & 38 deletions src/actarrow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,51 +397,92 @@ void actArrow(Entity* my)
ARROW_OLDY = my->y;

my->processEntityWind();
bool halfSpeedCheck = false;
static ConsoleVariable<bool> cvar_arrow_clip("/arrow_clip_test", true);
if ( my->arrowSpeed > 4.0 ) // can clip through thin gates
const real_t totalMoveDistance = sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY);
static constexpr real_t NORMAL_PROJECTILE_MAX_SPEED = 8.0;
static constexpr real_t MAX_SAFE_PROJECTILE_STEP = 4.0;
if ( totalMoveDistance > NORMAL_PROJECTILE_MAX_SPEED )
{
auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
for ( auto it : entLists )
// mod add: clipMove() samples its destination rather than sweeping the
// complete segment. Subdivide genuinely high-speed movement so thin
// objects and small creatures cannot fit between collision samples.
const int moveSteps = static_cast<int>(std::ceil(totalMoveDistance / MAX_SAFE_PROJECTILE_STEP));
const real_t stepVelX = ARROW_VELX / moveSteps;
const real_t stepVelY = ARROW_VELY / moveSteps;
const real_t stepDistance = sqrt(stepVelX * stepVelX + stepVelY * stepVelY);
const real_t intendedX = ARROW_OLDX + ARROW_VELX;
const real_t intendedY = ARROW_OLDY + ARROW_VELY;
// Include the starting position so a close-range shot spawned inside
// a target cannot leave its collision box before the first substep.
hit.entity = nullptr;
hitSomething = barony_clear(my->x, my->y, my) == 0;
for ( int step = 0; step < moveSteps && !hitSomething; ++step )
{
if ( !*cvar_arrow_clip && (svFlags & SV_FLAG_CHEATS) )
const real_t dist = clipMove(&my->x, &my->y, stepVelX, stepVelY, my);
if ( dist != stepDistance )
{
hitSomething = true;
break;
}
for ( node_t* node = it->first; node != nullptr; node = node->next )
}
if ( !hitSomething )
{
// Preserve the exact original full-tick displacement after all
// collision samples succeed; subdivision must not slow the shot.
my->x = intendedX;
my->y = intendedY;
}
// mod add end
}
else
{
// Preserve vanilla movement exactly for its normal speed range,
// including the existing conditional gate/door half-step workaround.
bool halfSpeedCheck = false;
static ConsoleVariable<bool> cvar_arrow_clip("/arrow_clip_test", true);
if ( my->arrowSpeed > 4.0 ) // can clip through thin gates
{
auto entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
for ( auto it : entLists )
{
Entity* entity = (Entity*)node->element;
if ( entity->behavior == &actGate || entity->behavior == &actDoor || entity->behavior == &actIronDoor )
if ( !*cvar_arrow_clip && (svFlags & SV_FLAG_CHEATS) )
{
if ( entityDist(my, entity) <= my->arrowSpeed )
break;
}
for ( node_t* node = it->first; node != nullptr; node = node->next )
{
Entity* entity = (Entity*)node->element;
if ( entity->behavior == &actGate || entity->behavior == &actDoor || entity->behavior == &actIronDoor )
{
halfSpeedCheck = true;
break;
if ( entityDist(my, entity) <= my->arrowSpeed )
{
halfSpeedCheck = true;
break;
}
}
}
}
if ( halfSpeedCheck )
{
break;
if ( halfSpeedCheck )
{
break;
}
}
}
}

if ( !halfSpeedCheck )
{
real_t dist = clipMove(&my->x, &my->y, ARROW_VELX, ARROW_VELY, my);
hitSomething = dist != sqrt(ARROW_VELX * ARROW_VELX + ARROW_VELY * ARROW_VELY);
}
else
{
real_t vel_x = ARROW_VELX / 2.0;
real_t vel_y = ARROW_VELY / 2.0;
real_t dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
hitSomething = dist != sqrt(vel_x * vel_x + vel_y * vel_y);
if ( !hitSomething )

if ( !halfSpeedCheck )
{
dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
real_t dist = clipMove(&my->x, &my->y, ARROW_VELX, ARROW_VELY, my);
hitSomething = dist != totalMoveDistance;
}
else
{
real_t vel_x = ARROW_VELX / 2.0;
real_t vel_y = ARROW_VELY / 2.0;
real_t dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
hitSomething = dist != sqrt(vel_x * vel_x + vel_y * vel_y);
if ( !hitSomething )
{
dist = clipMove(&my->x, &my->y, vel_x, vel_y, my);
hitSomething = dist != sqrt(vel_x * vel_x + vel_y * vel_y);
}
}
}
}
Expand All @@ -451,7 +492,7 @@ void actArrow(Entity* my)
index = std::clamp(index, 0, (int)(MAPLAYERS * map.width * map.height) - 1);
if ( map.tiles[index] )
{
if ( my->sprite == PROJECTILE_BOLT_SPRITE || my->sprite == PROJECTILE_ROCK_SPRITE ) // bolt/rock
if ( my->arrowUsesBoltSemantics() || my->sprite == PROJECTILE_ROCK_SPRITE ) // bolt/firearm/rock
{
if ( my->z >= 7 )
{
Expand Down Expand Up @@ -554,7 +595,10 @@ void actArrow(Entity* my)
{
Entity* parent = uidToEntity(my->parent);
Stat* hitstats = hit.entity->getStats();
playSoundEntity(my, 72 + local_rng.rand() % 3, 64);
//mod add: Preserve target-specific material/damage reactions below,
// but replace the generic projectile contact for firearms only.
playSoundEntity(my,
my->arrowShotByFirearm() ? 864 : 72 + local_rng.rand() % 3, 64);
if ( hit.entity->behavior == &actChest || hit.entity->isInertMimic() )
{
playSoundEntity(hit.entity, 66, 64); //*tink*
Expand Down Expand Up @@ -627,6 +671,10 @@ void actArrow(Entity* my)
entityHP = 0;

hit.entity->colliderKillerUid = parent ? parent->getUID() : 0;
if ( oldHP > 0 )
{
entrenchOnEntityDestroyedByCreature(hit.entity, parent);
}
if ( parent && parent->behavior == &actPlayer )
{
if ( oldHP > 0 )
Expand Down Expand Up @@ -908,7 +956,21 @@ void actArrow(Entity* my)
}
}
}
real_t damageMultiplier = Entity::getDamageTableMultiplier(hit.entity, *hitstats, DAMAGE_TABLE_RANGED);
real_t innateRangedMultiplierOverride = -1.0;
// mod add: Firearms compress only innate ranged affinity halfway
// toward neutral. A zero affinity remains untouched so an existing
// true-immunity path can never be converted into partial damage.
if ( my->arrowShotByFirearm() )
{
const real_t innateRangedMultiplier =
damagetables[hitstats->type][DAMAGE_TABLE_RANGED];
innateRangedMultiplierOverride = innateRangedMultiplier > 0.0
? 0.5 + 0.5 * innateRangedMultiplier
: innateRangedMultiplier;
}
real_t damageMultiplier = Entity::getDamageTableMultiplier(
hit.entity, *hitstats, DAMAGE_TABLE_RANGED, nullptr, nullptr,
innateRangedMultiplierOverride);
if ( huntingDamage || silverDamage )
{
damageMultiplier = std::max(0.75, damageMultiplier);
Expand Down Expand Up @@ -970,6 +1032,19 @@ 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);*/
// mod add: Firearms critically hit the first creature reached by the
// projectile. Keep this after all normal ranged modifiers and immediately
// before HP removal; the native creature-damage path owns hit validity.
bool firearmFirstHitCritApplied = false;
if ( my->arrowShotByFirearm()
&& !my->arrowFirearmCritConsumed )
{
damage *= 2;
my->arrowFirearmCritConsumed = true;
firearmFirstHitCritApplied = true;
}
// mod add end

Sint32 oldHP = hitstats->HP;
hit.entity->modHP(-damage);

Expand Down Expand Up @@ -1166,8 +1241,11 @@ void actArrow(Entity* my)
}
}

int chance = 10;
if ( doSkillIncrease && (local_rng.rand() % chance == 0) && parent && parent->getStats() )
const bool firearmShot = my->arrowShotByFirearm();
const int chance = firearmShot ? 4 : 10; //mod add: Damaging firearm hits have a 25% Ranged progression chance.
if ( doSkillIncrease
&& (!firearmShot || oldHP > hitstats->HP)
&& (local_rng.rand() % chance == 0) && parent && parent->getStats() )
{
if ( hitstats->type != DUMMYBOT
|| (hitstats->type == DUMMYBOT && parent->getStats()->getProficiency(PRO_RANGED) < SKILL_LEVEL_BASIC) )
Expand Down Expand Up @@ -1342,7 +1420,7 @@ void actArrow(Entity* my)
// rock.
messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT_BASIC, color, Language::get(2512));
}
else if (my->sprite == PROJECTILE_BOLT_SPRITE )
else if ( my->arrowUsesBoltSemantics() )
{
// bolt.
messagePlayerColor(hit.entity->skill[2], MESSAGE_COMBAT_BASIC, color, Language::get(2511));
Expand Down Expand Up @@ -1721,6 +1799,7 @@ void actArrow(Entity* my)
&& !itemTypeIsQuiver(hitstats->shield->type) && itemCategory(hitstats->shield) != SPELLBOOK
&& !itemTypeIsFoci(hitstats->shield->type)
&& !(hitstats->shield->type >= INSTRUMENT_FLUTE && hitstats->shield->type <= INSTRUMENT_HORN)
&& hitstats->shield->type != SPYGLASS // mod add: utility optics are not shields for degradation
&& hitstats->shield->type != TOOL_TINKERING_KIT && hitstats->shield->type != TOOL_FRYING_PAN )
{
if ( hitstats->shield->type == TOOL_CRYSTALSHARD && hitstats->defending )
Expand Down Expand Up @@ -1910,6 +1989,12 @@ void actArrow(Entity* my)
{
dmgGib = DMG_STRONGER;
}
if ( firearmFirstHitCritApplied )
{
// mod add: Match vanilla charged-melee crit presentation. An
// already-strong matchup promotes to the strongest crit tier.
dmgGib = damageMultiplier >= 1.15 ? DMG_STRONGEST : DMG_STRONGER;
}
if ( !strcmp(hitstats->name, "") )
{
updateEnemyBar(parent, hit.entity, getMonsterLocalizedName(hitstats->type).c_str(), hitstats->HP, hitstats->MAXHP,
Expand Down Expand Up @@ -1962,7 +2047,20 @@ void actArrow(Entity* my)
}
else
{
playSoundEntity(my, 72 + local_rng.rand() % 3, 64);
//mod add: Firearm wall/floor impacts use the dedicated bullet report;
// arrows, bolts, and all other projectiles retain their vanilla family.
playSoundEntity(my,
my->arrowShotByFirearm() ? 864 : 72 + local_rng.rand() % 3, 64);
if ( my->arrowShotByFirearm() && ARROW_STUCK > 0 )
{
// mod add: Entity impacts are already removed by the native branch
// above. Remove terminal firearm wall/floor impacts after their normal
// button, bomb, sound, and collision processing instead of displaying
// an embedded projectile. Piercing shots keep ARROW_STUCK at zero.
my->removeLightField();
list_RemoveNode(my->mynode);
return;
}
}
}
}
Expand Down
Loading