Merge branch 'feat/gameplay-tweaks'
Tweaks de jugabilitat persistents (gameplay.yaml a la carpeta de sistema) amb tres flags i submenú "Jugabilitat" al ServiceMenu: látec travessa sarcs, foc amic, col·lisió entre jugadors.
This commit is contained in:
commit
1d9fea28fe
11 files changed
+328
-1
No files matched your search
@@ -107,6 +107,7 @@ set(APP_SOURCES
|
||||
source/utils/log.cpp
|
||||
source/utils/utils.cpp
|
||||
source/game/debug_config.cpp
|
||||
source/game/gameplay_config.cpp
|
||||
source/game/options.cpp
|
||||
source/game/options_apply.cpp
|
||||
source/game/ui/service_menu.cpp
|
||||
@@ -397,6 +398,7 @@ if(BUILD_TESTS)
|
||||
tests/audio_cache_stubs.cpp
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/game/gameplay_config.cpp
|
||||
source/game/map/map.cpp
|
||||
source/utils/log.cpp
|
||||
${ENTITY_SOURCES_BASE}
|
||||
|
||||
@@ -62,6 +62,7 @@ menu:
|
||||
controls: "Controls"
|
||||
keyboard: "Teclat"
|
||||
gamepad: "Comandament"
|
||||
tweaks: "Jugabilitat"
|
||||
system: "Sistema"
|
||||
labels:
|
||||
player_1: "Jugador 1"
|
||||
@@ -73,6 +74,7 @@ menu:
|
||||
controls_submenu: "Controls"
|
||||
keyboard_submenu: "Teclat"
|
||||
gamepad_submenu: "Comandament"
|
||||
tweaks_submenu: "Jugabilitat"
|
||||
system_submenu: "Sistema"
|
||||
move_up: "Amunt"
|
||||
move_down: "Avall"
|
||||
@@ -101,6 +103,9 @@ menu:
|
||||
music_volume: "Volum música"
|
||||
sounds: "Sons"
|
||||
sounds_volume: "Volum sons"
|
||||
whip_pierces_walls: "Látec travessa sarcs"
|
||||
friendly_fire: "Foc amic"
|
||||
players_collide: "Col·lisió entre jugadors"
|
||||
restart: "Reiniciar"
|
||||
exit_game: "Eixir del joc"
|
||||
values:
|
||||
|
||||
@@ -61,6 +61,7 @@ menu:
|
||||
controls: "Controls"
|
||||
keyboard: "Keyboard"
|
||||
gamepad: "Gamepad"
|
||||
tweaks: "Gameplay"
|
||||
system: "System"
|
||||
labels:
|
||||
player_1: "Player 1"
|
||||
@@ -72,6 +73,7 @@ menu:
|
||||
controls_submenu: "Controls"
|
||||
keyboard_submenu: "Keyboard"
|
||||
gamepad_submenu: "Gamepad"
|
||||
tweaks_submenu: "Gameplay"
|
||||
system_submenu: "System"
|
||||
move_up: "Up"
|
||||
move_down: "Down"
|
||||
@@ -100,6 +102,9 @@ menu:
|
||||
music_volume: "Music volume"
|
||||
sounds: "Sounds"
|
||||
sounds_volume: "Sound volume"
|
||||
whip_pierces_walls: "Whip pierces sarcs"
|
||||
friendly_fire: "Friendly fire"
|
||||
players_collide: "Player collision"
|
||||
restart: "Restart"
|
||||
exit_game: "Exit game"
|
||||
values:
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#include "core/ui/ui_config.hpp"
|
||||
#include "game/debug_config.hpp"
|
||||
#include "game/gameplay_config.hpp"
|
||||
#ifdef _DEBUG
|
||||
#include "game/editor/editor_config.hpp"
|
||||
#endif
|
||||
@@ -93,6 +94,11 @@ Director::Director() {
|
||||
// el patró de coffee_crisis_arcade_edition (`gamepad_configs.json`).
|
||||
GamepadBindings::get().load(system_folder_ + "gamepad_configs.yaml");
|
||||
|
||||
// Tweaks de jugabilitat (látigo travessa sarcs, friendly fire, col·lisió
|
||||
// entre jugadors). Fitxer escrivible des del menú de servei (F9). Si
|
||||
// no existeix, GameplayConfig::load el crea amb els defaults.
|
||||
GameplayConfig::get().load(system_folder_ + "gameplay.yaml");
|
||||
|
||||
// Carrega els presets de PostFX des de shaders/postfx.yaml; si no existeix
|
||||
// es crea amb presets per defecte (off, crt, ntsc).
|
||||
Options::setPostFXFile(system_folder_ + Defaults::Video::POSTFX_FILE);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
#include "game/gameplay_config.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
|
||||
namespace {
|
||||
@@ -174,9 +175,16 @@ void WhipAttack::computeDamageRect(Direction dir, float spawn_x, float spawn_y,
|
||||
// d'atac, fins a topar amb un mur o sarc, o esgotar `range_px`.
|
||||
// L'amplada perpendicular és `tile_size` (els passadissos del joc
|
||||
// tenen exactament aquesta mida i l'atac és lateral).
|
||||
//
|
||||
// Si el tweak `whip.damage_pierces_walls` està actiu, la hitbox
|
||||
// ignora els sarcòfags (passa a través del bloc complet) però
|
||||
// continua respectant el `insidePlayfield`: fora de la sala no hi
|
||||
// ha res a colpejar i ampliar-ho generaria visuals/edge cases
|
||||
// estranys. El mur extern, doncs, sempre retalla.
|
||||
const auto TILE = static_cast<float>(Map::tile_size);
|
||||
const int MAX_PX = static_cast<int>(std::max(0.0F, range_px));
|
||||
const auto MAX = static_cast<float>(MAX_PX);
|
||||
const bool PIERCES = GameplayConfig::get().whipDamagePiercesWalls();
|
||||
float length = 0.0F;
|
||||
|
||||
auto try_probe = [&](float l) -> bool {
|
||||
@@ -198,7 +206,9 @@ void WhipAttack::computeDamageRect(Direction dir, float spawn_x, float spawn_y,
|
||||
probe = {.x = spawn_x, .y = spawn_y + (MAX - l), .w = TILE, .h = l};
|
||||
break;
|
||||
}
|
||||
return Map::insidePlayfield(probe) && !Map::collides(probe);
|
||||
if (!Map::insidePlayfield(probe)) { return false; }
|
||||
if (PIERCES) { return true; }
|
||||
return !Map::collides(probe);
|
||||
};
|
||||
|
||||
for (int i = 1; i <= MAX_PX; ++i) {
|
||||
@@ -311,6 +321,10 @@ namespace {
|
||||
WhipAttack::Direction dir,
|
||||
std::unordered_set<void*>& already_hit) {
|
||||
if (p == owner || !playerIsTargetable(p)) { return; }
|
||||
// Tweak `combat.friendly_fire`: si està desactivat, els altres
|
||||
// jugadors són immunes al látigo aliat. L'owner ja queda exclòs
|
||||
// per la primera guarda; aquest filtre cobreix només cross-player.
|
||||
if (!GameplayConfig::get().friendlyFire()) { return; }
|
||||
if (already_hit.contains(static_cast<void*>(p))) { return; }
|
||||
const auto BOX = p->getBaseBox();
|
||||
if (!SDL_HasRectIntersectionFloat(&damage_rect, &BOX)) { return; }
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "game/entities/entity.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/entities/event_bus.hpp"
|
||||
#include "game/gameplay_config.hpp"
|
||||
#include "game/options.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
|
||||
@@ -414,6 +415,10 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
||||
// al check actor↔actor més avall via el predicat `isOutOfPlay`.
|
||||
if (isOutOfPlay()) { return false; }
|
||||
if (map_.collides(box)) { return true; }
|
||||
// Tweak `players.collide_with_each_other`: si està desactivat, els
|
||||
// jugadors es travessen lliurement. La col·lisió contra el món
|
||||
// (murs/sarcs) ja s'ha avaluat dalt; saltem només la part actor↔actor.
|
||||
if (!GameplayConfig::get().playersCollide()) { return false; }
|
||||
if (all_players_ == nullptr) { return false; }
|
||||
// Per al contacte actor-actor usem la hit-box petita a banda i banda:
|
||||
// projectem la hit-box des de la posició base proposada i la comparem
|
||||
@@ -1027,6 +1032,12 @@ void Player::resolveLandingOverlap() {
|
||||
// canvi, qualsevol jugador amb el qual solape entra en HURT i
|
||||
// surt projectat. Així el solapament inicial es resol per moviment
|
||||
// del solapat, no per desplaçament forçat al lloc.
|
||||
//
|
||||
// Tweak `players.collide_with_each_other`: si està desactivat, el
|
||||
// saltador travessa l'altre net en lloc de projectar-lo. Aquest
|
||||
// camí és independent de `collidesWithWorld`, així que cal aturar-lo
|
||||
// explícitament aquí.
|
||||
if (!GameplayConfig::get().playersCollide()) { return; }
|
||||
if (all_players_ == nullptr) { return; }
|
||||
for (Player* other : *all_players_) {
|
||||
if (other == nullptr || other == this) { continue; }
|
||||
@@ -1737,6 +1748,10 @@ auto Player::tryPushSnapBlockers(
|
||||
float (Player::*get_pos)() const,
|
||||
void (Player::*set_pos)(float)) -> bool {
|
||||
if (all_players_ == nullptr) { return false; }
|
||||
// Si el tweak de col·lisió entre jugadors està desactivat, mai hi
|
||||
// ha res a empentar — els jugadors són intangibles entre si i el
|
||||
// snap pot procedir lliurement sense desplaçar ningú.
|
||||
if (!GameplayConfig::get().playersCollide()) { return false; }
|
||||
|
||||
// Blockers = jugadors que ara mateix solapen la nostra hit-box (ja
|
||||
// amb el snap aplicat). Mateix criteri actor-actor que `collidesWithWorld`.
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#include "game/gameplay_config.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
auto readFile(const std::string& path) -> std::string {
|
||||
std::ifstream file(path);
|
||||
if (!file) { return {}; }
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
// Llig una subclau booleana obligatòria (`section.key`). Retorna
|
||||
// false si la secció o la clau no hi són o el tipus no és bool —
|
||||
// qualsevol d'aquests casos és una irregularitat que dispara
|
||||
// reescriptura del fitxer.
|
||||
auto readRequiredBool(const fkyaml::node& root, const char* section, const char* key, bool& out)
|
||||
-> bool {
|
||||
if (!root.contains(section)) { return false; }
|
||||
const auto& sec = root[section];
|
||||
if (!sec.contains(key)) { return false; }
|
||||
try {
|
||||
out = sec[key].get_value<bool>();
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Escriu el fitxer amb els valors actuals + comentaris d'ajuda.
|
||||
// Format estable perquè l'usuari pot editar-lo a mà.
|
||||
void writeFile(const std::string& path,
|
||||
int version,
|
||||
bool whip_pierces,
|
||||
bool friendly_fire,
|
||||
bool players_collide) {
|
||||
std::ofstream file(path);
|
||||
if (!file) {
|
||||
Log::error("gameplay.yaml", "no s'ha pogut escriure {}", path);
|
||||
return;
|
||||
}
|
||||
file << "# gameplay.yaml — tweaks de jugabilitat persistents.\n";
|
||||
file << "# Edita des del menú de servei (F9 → Tweaks) o a mà.\n";
|
||||
file << "# Si esborres el fitxer (o aquesta capçalera de versió), es regenerarà\n";
|
||||
file << "# amb els defaults a la propera arrencada.\n";
|
||||
file << "\n";
|
||||
file << "version: " << version << "\n";
|
||||
file << "\n";
|
||||
file << "whip:\n";
|
||||
file << " damage_pierces_walls: " << (whip_pierces ? "true" : "false") << "\n";
|
||||
file << "\n";
|
||||
file << "combat:\n";
|
||||
file << " friendly_fire: " << (friendly_fire ? "true" : "false") << "\n";
|
||||
file << "\n";
|
||||
file << "players:\n";
|
||||
file << " collide_with_each_other: " << (players_collide ? "true" : "false") << "\n";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto GameplayConfig::get() -> GameplayConfig& {
|
||||
static GameplayConfig instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void GameplayConfig::load(const std::string& file_path) {
|
||||
file_path_ = file_path;
|
||||
|
||||
std::error_code error;
|
||||
const bool EXISTS = std::filesystem::exists(file_path, error);
|
||||
const auto CONTENT = EXISTS ? readFile(file_path) : std::string{};
|
||||
|
||||
// Tots els valors van a temporaris; només ens els quedem si la
|
||||
// validació completa passa. Així una validació parcial (p. ex. dues
|
||||
// claus bones i una de dolenta) no deixa l'estat a mig camí.
|
||||
bool whip_pierces = whip_damage_pierces_walls_;
|
||||
bool ff = friendly_fire_;
|
||||
bool players_collide = players_collide_;
|
||||
|
||||
auto rewrite_with_defaults = [&](const char* reason) {
|
||||
// Resetem els camps als defaults compilats abans de reescriure.
|
||||
// Necessari si load() es crida diverses vegades amb fitxers
|
||||
// distints; el primer pot haver mutat l'estat.
|
||||
whip_damage_pierces_walls_ = true;
|
||||
friendly_fire_ = true;
|
||||
players_collide_ = true;
|
||||
writeFile(file_path,
|
||||
CURRENT_VERSION,
|
||||
whip_damage_pierces_walls_,
|
||||
friendly_fire_,
|
||||
players_collide_);
|
||||
Log::info("gameplay.yaml", "{} — reescrit a {} amb defaults", reason, file_path);
|
||||
};
|
||||
|
||||
if (!EXISTS) {
|
||||
rewrite_with_defaults("fitxer absent");
|
||||
return;
|
||||
}
|
||||
if (CONTENT.empty()) {
|
||||
rewrite_with_defaults("fitxer buit");
|
||||
return;
|
||||
}
|
||||
|
||||
fkyaml::node yaml;
|
||||
try {
|
||||
yaml = fkyaml::node::deserialize(CONTENT);
|
||||
} catch (const std::exception& e) {
|
||||
rewrite_with_defaults((std::string("parse error: ") + e.what()).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// `version` ha de ser un enter present i igual a CURRENT_VERSION.
|
||||
// Qualsevol altra cosa (absent, tipus no enter, valor diferent) és
|
||||
// una irregularitat: el fitxer ve d'una versió incompatible o ha
|
||||
// estat manipulat de manera inesperada.
|
||||
if (!yaml.contains("version")) {
|
||||
rewrite_with_defaults("version absent");
|
||||
return;
|
||||
}
|
||||
int version = 0;
|
||||
try {
|
||||
version = yaml["version"].get_value<int>();
|
||||
} catch (const std::exception&) {
|
||||
rewrite_with_defaults("version no és enter");
|
||||
return;
|
||||
}
|
||||
if (version != CURRENT_VERSION) {
|
||||
rewrite_with_defaults("version desconeguda");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readRequiredBool(yaml, "whip", "damage_pierces_walls", whip_pierces) || !readRequiredBool(yaml, "combat", "friendly_fire", ff) || !readRequiredBool(yaml, "players", "collide_with_each_other", players_collide)) {
|
||||
rewrite_with_defaults("clau requerida absent o de tipus incorrecte");
|
||||
return;
|
||||
}
|
||||
|
||||
// Tot validat: ens quedem els valors llegits.
|
||||
whip_damage_pierces_walls_ = whip_pierces;
|
||||
friendly_fire_ = ff;
|
||||
players_collide_ = players_collide;
|
||||
}
|
||||
|
||||
void GameplayConfig::save() const {
|
||||
if (file_path_.empty()) {
|
||||
// Sense path establert: cap consumidor s'ha registrat encara.
|
||||
// Pot passar en tests que no carreguen el fitxer; silenciem el cas.
|
||||
return;
|
||||
}
|
||||
writeFile(file_path_,
|
||||
CURRENT_VERSION,
|
||||
whip_damage_pierces_walls_,
|
||||
friendly_fire_,
|
||||
players_collide_);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
// Tweaks de jugabilitat persistents per usuari. Carregat de
|
||||
// `gameplay.yaml` dins la carpeta de sistema (vegeu
|
||||
// `Director::createSystemFolder` per la ubicació real per plataforma).
|
||||
//
|
||||
// Diferent d'UiConfig (data/config/ui.yaml, només lectura): aquest fitxer
|
||||
// és escrivible i el ServiceMenu en modifica els valors. Els defaults
|
||||
// compilats són els que veuen els nous usuaris fins que toquen el menú.
|
||||
//
|
||||
// Política d'integritat: davant qualsevol irregularitat (fitxer absent,
|
||||
// parse error, `version` absent o diferent, clau requerida absent o de
|
||||
// tipus incorrecte), `load()` reseteja a defaults i reescriu el fitxer.
|
||||
// No es preserven fitxers parcialment vàlids — així evitem dragar
|
||||
// versions antigues incoherents quan l'esquema evolucioni.
|
||||
class GameplayConfig {
|
||||
public:
|
||||
// Esquema actual del fitxer. Incrementar quan canviï l'estructura
|
||||
// (renomenar/treure claus o canviar semàntica). Els fitxers d'usuari
|
||||
// amb una `version` distinta es reescriuen amb els defaults nous.
|
||||
// Afegir noves claus opcionals no requereix bump si els defaults
|
||||
// compilats són equivalents al comportament anterior.
|
||||
static constexpr int CURRENT_VERSION = 1;
|
||||
|
||||
static auto get() -> GameplayConfig&;
|
||||
|
||||
GameplayConfig(const GameplayConfig&) = delete;
|
||||
GameplayConfig(GameplayConfig&&) = delete;
|
||||
auto operator=(const GameplayConfig&) -> GameplayConfig& = delete;
|
||||
auto operator=(GameplayConfig&&) -> GameplayConfig& = delete;
|
||||
|
||||
// Llig el fitxer i guarda la ruta per a `save()`. Si no existeix,
|
||||
// l'escriu amb els defaults compilats. Idempotent: pot cridar-se
|
||||
// múltiples vegades (l'última crida fixa el path actiu).
|
||||
void load(const std::string& file_path);
|
||||
|
||||
// Persisteix l'estat actual al path passat a `load()`. Cridat pel
|
||||
// ServiceMenu a cada canvi de toggle. No-op si `load()` no s'ha
|
||||
// cridat encara (sense path no podem escriure enlloc útil).
|
||||
void save() const;
|
||||
|
||||
// Tweaks. Defaults: tots true.
|
||||
//
|
||||
// - whip_damage_pierces_walls: el dany del látigo travessa sarcòfags
|
||||
// (NO el mur extern del playfield, que retalla igualment per
|
||||
// evitar que el rect surti de la sala visible).
|
||||
// - friendly_fire: un latigazo pot impactar l'altre jugador.
|
||||
// - players_collide: els jugadors col·lisionen físicament entre si
|
||||
// en lloc de travessar-se.
|
||||
[[nodiscard]] auto whipDamagePiercesWalls() const -> bool { return whip_damage_pierces_walls_; }
|
||||
[[nodiscard]] auto friendlyFire() const -> bool { return friendly_fire_; }
|
||||
[[nodiscard]] auto playersCollide() const -> bool { return players_collide_; }
|
||||
|
||||
void setWhipDamagePiercesWalls(bool v) { whip_damage_pierces_walls_ = v; }
|
||||
void setFriendlyFire(bool v) { friendly_fire_ = v; }
|
||||
void setPlayersCollide(bool v) { players_collide_ = v; }
|
||||
|
||||
private:
|
||||
GameplayConfig() = default;
|
||||
~GameplayConfig() = default;
|
||||
|
||||
// Ruta absoluta al fitxer persistit. Buida fins que algú crida
|
||||
// `load()` per primera vegada; mentre estigui buida, `save()` és no-op.
|
||||
std::string file_path_;
|
||||
|
||||
bool whip_damage_pierces_walls_{true};
|
||||
bool friendly_fire_{true};
|
||||
bool players_collide_{true};
|
||||
};
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "game/entities/entity_loader.hpp"
|
||||
#include "game/entities/event_bus.hpp"
|
||||
#include "game/entities/player.hpp"
|
||||
#include "game/gameplay_config.hpp"
|
||||
#include "game/levels/level_manager.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
#include "game/options.hpp"
|
||||
@@ -108,6 +109,11 @@ namespace {
|
||||
auto* a = ps[0];
|
||||
auto* b = ps[1];
|
||||
if (a == nullptr || b == nullptr) { return; }
|
||||
// Tweak `players.collide_with_each_other`: amb el flag desactivat
|
||||
// saltem TOT el resolver — head-on, push i bounce aeri queden
|
||||
// anul·lats. La col·lisió contra el món continua intacta perquè
|
||||
// viu a `Player::collidesWithWorld`/`tryMove`, no aquí.
|
||||
if (!GameplayConfig::get().playersCollide()) { return; }
|
||||
|
||||
const bool A_AIR = a->isAirborne();
|
||||
const bool B_AIR = b->isAirborne();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "core/rendering/text.hpp"
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#include "core/ui/ui_config.hpp"
|
||||
#include "game/gameplay_config.hpp"
|
||||
#include "game/options.hpp"
|
||||
#include "game/options_apply.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
@@ -692,6 +693,8 @@ auto ServiceMenu::buildRoot() -> Page {
|
||||
[]() { ServiceMenu::get().pushPage(buildAudio()); }));
|
||||
page.items.push_back(makeSubmenu("menu.items.controls_submenu",
|
||||
[]() { ServiceMenu::get().pushPage(buildControls()); }));
|
||||
page.items.push_back(makeSubmenu("menu.items.tweaks_submenu",
|
||||
[]() { ServiceMenu::get().pushPage(buildTweaks()); }));
|
||||
page.items.push_back(makeSubmenu("menu.items.system_submenu",
|
||||
[]() { ServiceMenu::get().pushPage(buildSystem()); }));
|
||||
return page;
|
||||
@@ -1155,6 +1158,42 @@ void ServiceMenu::startDefineButtons(SDL_Gamepad* pad, const std::string& name,
|
||||
define_buttons_.enable(pad, name, path);
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildTweaks() -> Page {
|
||||
// Submenú "Tweaks" — flags de jugabilitat persistits a `gameplay.yaml`
|
||||
// dins la carpeta de sistema. Cada toggle muta `GameplayConfig` i
|
||||
// n'invoca `save()` immediatament: l'usuari veu el canvi reflectit al
|
||||
// joc sense esperar a un "aplicar" i, si tanca el joc, torna en el
|
||||
// mateix estat. Tres toggles ON/OFF, sense estat addicional.
|
||||
Page page;
|
||||
page.title_key = "menu.titles.tweaks";
|
||||
|
||||
page.items.push_back(makeToggle(
|
||||
"menu.items.whip_pierces_walls",
|
||||
[]() { return GameplayConfig::get().whipDamagePiercesWalls(); },
|
||||
[](bool v) {
|
||||
GameplayConfig::get().setWhipDamagePiercesWalls(v);
|
||||
GameplayConfig::get().save();
|
||||
}));
|
||||
|
||||
page.items.push_back(makeToggle(
|
||||
"menu.items.friendly_fire",
|
||||
[]() { return GameplayConfig::get().friendlyFire(); },
|
||||
[](bool v) {
|
||||
GameplayConfig::get().setFriendlyFire(v);
|
||||
GameplayConfig::get().save();
|
||||
}));
|
||||
|
||||
page.items.push_back(makeToggle(
|
||||
"menu.items.players_collide",
|
||||
[]() { return GameplayConfig::get().playersCollide(); },
|
||||
[](bool v) {
|
||||
GameplayConfig::get().setPlayersCollide(v);
|
||||
GameplayConfig::get().save();
|
||||
}));
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
auto ServiceMenu::buildSystem() -> Page {
|
||||
Page page;
|
||||
page.title_key = "menu.titles.system";
|
||||
|
||||
@@ -253,6 +253,7 @@ class ServiceMenu {
|
||||
[[nodiscard]] static auto buildKeyboardControls() -> Page;
|
||||
[[nodiscard]] static auto buildGamepadControls() -> Page;
|
||||
[[nodiscard]] static auto buildSystem() -> Page;
|
||||
[[nodiscard]] static auto buildTweaks() -> Page;
|
||||
|
||||
// Helpers privats d'items de vídeo: aïllen la lògica de
|
||||
// `shader_type` i `preset` fora de `buildVideo` perquè no exploti la
|
||||
|
||||
Reference in new issue
Block a user