fix(balance): Startausruestung wird angelegt, Krit wirkt
Gemessen statt geschaetzt — es waren nicht die Zahlen: - on_create konnte nur give_item, die Startwaffe lag also im Beutel. Jeder Held schlug mit blossen Haenden zu: Schaden 2 statt 7,7, zehn Zuege pro Ratte statt zweieinhalb. Neue Aktion equip_item. - Der Dieb bekam gar keine Waffe. Jetzt ein Dolch (+3 Schaden, +5 Krit). - critical_chance percent = 15 auf einer Basis von 0 ist ein No-op — die Signaturzahl des Diebs tat nichts. Basis jetzt 5, Dieb traegt flat 10. Die Prozent-auf-Null-Falle steht im Glossar unter Modifikator: Sie folgt aus der Stapelregel aus Ticket 01 und schnappt sonst wieder zu.
This commit is contained in:
@@ -219,6 +219,22 @@ nur flache Modifikatoren geben, also genau die Hälfte, die laut demselben Ticke
|
|||||||
Speicherstand ohne Rassen-Mod hat nach der Verwerf-Regel ebenfalls keine Optionen und würde neu gebaut
|
Speicherstand ohne Rassen-Mod hat nach der Verwerf-Regel ebenfalls keine Optionen und würde neu gebaut
|
||||||
statt fortgesetzt).
|
statt fortgesetzt).
|
||||||
|
|
||||||
|
**Erste Balancing-Runde** (nach M8, 73 Tests) — ausgelöst durch „der erste Gegner ist sehr schwer,
|
||||||
|
Elf Dieb". Gemessen statt geschätzt, und es waren nicht die Zahlen:
|
||||||
|
|
||||||
|
| Ursache | Wirkung |
|
||||||
|
|---|---|
|
||||||
|
| `on_create` konnte nur `give_item` | Die Startwaffe lag im **Beutel**. Jeder Held schlug mit bloßen Händen zu: Schaden 2 statt 7,7 — zehn Züge pro Ratte statt zweieinhalb |
|
||||||
|
| Der Dieb bekam gar keine Waffe | Nur Kappe und Gold, und die Kappe war ebenfalls nicht angelegt |
|
||||||
|
| `critical_chance percent = 15` auf Basis 0 | **No-op.** Die Signaturzahl der Klasse tat nichts, und keine Zeile Code war daran falsch |
|
||||||
|
|
||||||
|
Behoben mit `equip_item` als **zehnter Aktion** (kein Engine-Begriff „Startausrüstung" — eine Aktion
|
||||||
|
wie jede andere, überall verwendbar), einem Dolch für den Dieb, und einer Basis von 5 auf
|
||||||
|
`critical_chance`. Die Prozent-auf-Null-Falle steht jetzt im Glossar unter *Modifikator*: Sie folgt
|
||||||
|
direkt aus der Stapelregel aus Ticket 01 und wird wieder zuschnappen, wenn sie nirgends steht.
|
||||||
|
|
||||||
|
Startkampf danach: Krieger 2,5 Züge je Ratte, Dieb 3,9 bei 20 % Krit und 108 Leben.
|
||||||
|
|
||||||
**Drei Befunde aus M1, die nur der Compiler liefern konnte** — alle in die Tickets zurückgetragen:
|
**Drei Befunde aus M1, die nur der Compiler liefern konnte** — alle in die Tickets zurückgetragen:
|
||||||
|
|
||||||
- **11**: Der Grenztest in seiner wörtlichen Fassung schlägt beim eigenen Doc-Kommentar an. Muss Kommentarzeilen ausnehmen.
|
- **11**: Der Grenztest in seiner wörtlichen Fassung schlägt beim eigenen Doc-Kommentar an. Muss Kommentarzeilen ausnehmen.
|
||||||
|
|||||||
10
CONTEXT.md
10
CONTEXT.md
@@ -51,6 +51,11 @@ Ein **benannter** Modifikator (`core:poisoned`) ist ein Bündel: mehrere Umrechn
|
|||||||
von der [Aktion](#aktion) `apply_modifier` aufgelegt. Er **wohnt beim Träger** — stirbt der, verschwindet
|
von der [Aktion](#aktion) `apply_modifier` aufgelegt. Er **wohnt beim Träger** — stirbt der, verschwindet
|
||||||
er mit ihm.
|
er mit ihm.
|
||||||
|
|
||||||
|
Die Stapelregel ist `(Basis + Σ flach) × (1 + Σ prozentual)`, und daraus folgt eine Falle, die schon
|
||||||
|
einmal zugeschnappt hat: **`percent` auf einer Basis von 0 bleibt 0.** „+15 % kritische Chance" an einer
|
||||||
|
Klasse tat nichts, weil `critical_chance` beim Helden bei 0 anfing. Wer ein Attribut prozentual
|
||||||
|
verändern will, braucht dort eine Basis ungleich null — sonst gehört der Wert als `flat` hin.
|
||||||
|
|
||||||
## Aktion
|
## Aktion
|
||||||
|
|
||||||
Eine einmalige Zustandsänderung. Sie feuert, wirkt, ist vorbei. Quellen sind Angriffe,
|
Eine einmalige Zustandsänderung. Sie feuert, wirkt, ist vorbei. Quellen sind Angriffe,
|
||||||
@@ -61,8 +66,9 @@ Beispiele: „12 Schaden zufügen", „20 Leben heilen", „50 Gold geben", „G
|
|||||||
Eine Aktion kann einen befristeten [Modifikator](#modifikator) erzeugen — so hängen die beiden
|
Eine Aktion kann einen befristeten [Modifikator](#modifikator) erzeugen — so hängen die beiden
|
||||||
Begriffe zusammen, ohne dasselbe zu sein.
|
Begriffe zusammen, ohne dasselbe zu sein.
|
||||||
|
|
||||||
**Neun** sind gebaut: `damage`, `change_attribute`, `apply_modifier`, `remove_modifier`, `flee`,
|
**Zehn** sind gebaut: `damage`, `change_attribute`, `apply_modifier`, `remove_modifier`, `flee`,
|
||||||
`open_shop`, `give_exp`, `start_quest`, `give_item`. Offen bleiben `remove_item` und `run_script`.
|
`open_shop`, `give_exp`, `start_quest`, `give_item`, `equip_item`. Offen bleiben `remove_item` und
|
||||||
|
`run_script`.
|
||||||
|
|
||||||
Der Katalog trägt **keine Ordnungszahlen** mehr. Vier Tickets haben ihn erweitert und dabei jeweils vom
|
Der Katalog trägt **keine Ordnungszahlen** mehr. Vier Tickets haben ihn erweitert und dabei jeweils vom
|
||||||
eigenen Stand aus gezählt — „`start_quest` als siebte", „`flee` als achte", „`open_shop` als elfte" —,
|
eigenen Stand aus gezählt — „`start_quest` als siebte", „`flee` als achte", „`open_shop` als elfte" —,
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ abilities = ["core:power_strike"]
|
|||||||
# `on_create` ist KEINE neue Mechanik, sondern gewoehnliche Aktionen, einmal
|
# `on_create` ist KEINE neue Mechanik, sondern gewoehnliche Aktionen, einmal
|
||||||
# beim Bau ausgefuehrt (Ticket 02). Die Engine kennt den Begriff
|
# beim Bau ausgefuehrt (Ticket 02). Die Engine kennt den Begriff
|
||||||
# "Startausruestung" deshalb gar nicht.
|
# "Startausruestung" deshalb gar nicht.
|
||||||
on_create = [ { action = "give_item", item = "core:iron_sword" },
|
on_create = [ { action = "equip_item", item = "core:iron_sword" },
|
||||||
{ action = "give_item", item = "core:health_potion", count = 2 } ]
|
{ action = "give_item", item = "core:health_potion", count = 2 } ]
|
||||||
|
|
||||||
[[options]]
|
[[options]]
|
||||||
id = "thief"
|
id = "thief"
|
||||||
@@ -52,7 +52,10 @@ name = "Dieb"
|
|||||||
description = "Nimmt, was nicht niet- und nagelfest ist."
|
description = "Nimmt, was nicht niet- und nagelfest ist."
|
||||||
modifiers = [ { attribute = "dexterity", flat = 2 },
|
modifiers = [ { attribute = "dexterity", flat = 2 },
|
||||||
{ attribute = "luck", flat = 2 },
|
{ attribute = "luck", flat = 2 },
|
||||||
{ attribute = "critical_chance", percent = 15 } ]
|
# `flat`, nicht `percent`: Die Basis war 0, und Prozent auf 0
|
||||||
|
# bleibt 0 — die Signaturzahl des Diebs tat nichts (Befund M9).
|
||||||
|
{ attribute = "critical_chance", flat = 10 } ]
|
||||||
abilities = ["core:backstab"]
|
abilities = ["core:backstab"]
|
||||||
on_create = [ { action = "give_item", item = "core:leather_cap" },
|
on_create = [ { action = "equip_item", item = "core:dagger" },
|
||||||
{ action = "give_item", item = "core:gold", count = 40 } ]
|
{ action = "equip_item", item = "core:leather_cap" },
|
||||||
|
{ action = "give_item", item = "core:gold", count = 40 } ]
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ symbol = "/"
|
|||||||
color = "white"
|
color = "white"
|
||||||
modifiers = [ { attribute = "damage", flat = 5 } ]
|
modifiers = [ { attribute = "damage", flat = 5 } ]
|
||||||
|
|
||||||
|
[[items]]
|
||||||
|
id = "dagger"
|
||||||
|
name = "Dolch"
|
||||||
|
base_price = 28
|
||||||
|
slot = "core:hand"
|
||||||
|
symbol = "/"
|
||||||
|
color = "white"
|
||||||
|
modifiers = [ { attribute = "damage", flat = 3 },
|
||||||
|
{ attribute = "critical_chance", flat = 5 } ]
|
||||||
|
|
||||||
[[items]]
|
[[items]]
|
||||||
id = "leather_cap"
|
id = "leather_cap"
|
||||||
name = "Lederkappe"
|
name = "Lederkappe"
|
||||||
|
|||||||
@@ -137,6 +137,14 @@ pub enum Action {
|
|||||||
#[serde(default = "one")]
|
#[serde(default = "one")]
|
||||||
count: u32,
|
count: u32,
|
||||||
},
|
},
|
||||||
|
/// Gibt **und legt an**. Aus M9: `on_create` konnte bis dahin nur `give_item`,
|
||||||
|
/// also startete jeder Held mit der Waffe im Beutel und schlug mit blossen
|
||||||
|
/// Haenden zu. „Startausruestung" als eigener Engine-Begriff waere die
|
||||||
|
/// falsche Antwort gewesen — das hier ist eine Aktion wie jede andere und
|
||||||
|
/// steht ueberall, wo eine stehen darf.
|
||||||
|
EquipItem {
|
||||||
|
item: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn one() -> u32 {
|
fn one() -> u32 {
|
||||||
@@ -163,7 +171,8 @@ impl Action {
|
|||||||
| Action::OpenShop
|
| Action::OpenShop
|
||||||
| Action::GiveExp { .. }
|
| Action::GiveExp { .. }
|
||||||
| Action::StartQuest { .. }
|
| Action::StartQuest { .. }
|
||||||
| Action::GiveItem { .. } => Target::Zelf,
|
| Action::GiveItem { .. }
|
||||||
|
| Action::EquipItem { .. } => Target::Zelf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ impl Default for Attributes {
|
|||||||
a.set(Attribute::MaxStamina, 100.0);
|
a.set(Attribute::MaxStamina, 100.0);
|
||||||
a.set(Attribute::Damage, 2.0);
|
a.set(Attribute::Damage, 2.0);
|
||||||
a.set(Attribute::HitChance, 0.8);
|
a.set(Attribute::HitChance, 0.8);
|
||||||
|
// **Befund aus M9:** Die Basis stand auf 0, und ein Modifikator mit
|
||||||
|
// `percent` rechnet auf 0 immer 0 — die Signaturzahl des Diebs
|
||||||
|
// („+15 % kritische Chance") tat schlicht nichts. Gegner tragen seit M3
|
||||||
|
// eine Basis von 5; der Held hatte keine.
|
||||||
|
a.set(Attribute::CriticalChance, 5.0);
|
||||||
a.set(Attribute::CriticalFactor, 1.5);
|
a.set(Attribute::CriticalFactor, 1.5);
|
||||||
a.set(Attribute::CarryCapacity, 50.0);
|
a.set(Attribute::CarryCapacity, 50.0);
|
||||||
a
|
a
|
||||||
|
|||||||
@@ -430,6 +430,15 @@ impl Game {
|
|||||||
|
|
||||||
Action::StartQuest { quest } => self.start_quest(quest, reg),
|
Action::StartQuest { quest } => self.start_quest(quest, reg),
|
||||||
|
|
||||||
|
Action::EquipItem { item } => {
|
||||||
|
self.give(ItemInstance::new(item.clone()), reg);
|
||||||
|
// Ueber den normalen Weg, damit Verdraengtes zurueck ins
|
||||||
|
// Inventar wandert statt zu verschwinden.
|
||||||
|
if let Some(i) = self.player.inventory.iter().rposition(|x| x.def == *item) {
|
||||||
|
self.equip(i, reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Action::GiveItem { item, count } => {
|
Action::GiveItem { item, count } => {
|
||||||
let inst = ItemInstance::stack(item.clone(), *count);
|
let inst = ItemInstance::stack(item.clone(), *count);
|
||||||
let name = self.item_label(&inst, reg);
|
let name = self.item_label(&inst, reg);
|
||||||
@@ -2205,7 +2214,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// `on_create` hat die Ausruestung gegeben — als gewoehnliche Aktionen.
|
// `on_create` hat die Ausruestung gegeben — als gewoehnliche Aktionen.
|
||||||
assert_eq!(game.count_of("core:iron_sword"), 1);
|
// **Befund aus M9:** `equip_item`, nicht `give_item`. Vorher lag das
|
||||||
|
// Schwert im Beutel und der Held schlug mit blossen Haenden zu.
|
||||||
|
assert_eq!(
|
||||||
|
game.player.equipment.get("core:hand").map(|i| i.def.as_str()),
|
||||||
|
Some("core:iron_sword"),
|
||||||
|
"die Waffe muss getragen werden, nicht getragen *werden koennen*"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
game.attribute(p, Attribute::Damage, None, ®) > 7.0,
|
||||||
|
"mit Waffe, nicht ohne: {}",
|
||||||
|
game.attribute(p, Attribute::Damage, None, ®)
|
||||||
|
);
|
||||||
assert_eq!(game.count_of("core:health_potion"), 2, "der Stapel kommt als Stapel");
|
assert_eq!(game.count_of("core:health_potion"), 2, "der Stapel kommt als Stapel");
|
||||||
|
|
||||||
// Die Faehigkeiten von Rasse *und* Klasse stehen im Kampfmenue.
|
// Die Faehigkeiten von Rasse *und* Klasse stehen im Kampfmenue.
|
||||||
@@ -2214,4 +2234,34 @@ mod tests {
|
|||||||
assert!(koennen.iter().any(|a| a == "core:power_strike"), "Klasse: {koennen:?}");
|
assert!(koennen.iter().any(|a| a == "core:power_strike"), "Klasse: {koennen:?}");
|
||||||
assert!(koennen.iter().any(|a| a == "core:attack"), "und die impliziten bleiben");
|
assert!(koennen.iter().any(|a| a == "core:attack"), "und die impliziten bleiben");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// **Befund aus M9:** Ein Modifikator mit `percent` rechnet auf einer Basis
|
||||||
|
/// von 0 immer 0. `critical_chance` stand auf 0, also tat „+15 % kritische
|
||||||
|
/// Chance" am Dieb schlicht nichts — die Signaturzahl der Klasse war ein
|
||||||
|
/// No-op, und keine Zeile Code war daran falsch.
|
||||||
|
#[test]
|
||||||
|
fn prozent_auf_null_bleibt_null() {
|
||||||
|
let (reg, _) = load_mods().expect("core");
|
||||||
|
let mut game = Game::new(Creature::new("M"), ®, Rng::new(7)).expect("Etage 1");
|
||||||
|
let p = CreatureRef::Player;
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
game.attribute(p, Attribute::CriticalChance, None, ®) > 0.0,
|
||||||
|
"ohne Basis wirkt kein prozentualer Modifikator darauf"
|
||||||
|
);
|
||||||
|
|
||||||
|
game.execute(
|
||||||
|
Command::CreateCharacter(vec![
|
||||||
|
String::from("core:elf"),
|
||||||
|
String::from("core:thief"),
|
||||||
|
]),
|
||||||
|
®,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
game.attribute(p, Attribute::CriticalChance, None, ®) >= 15.0,
|
||||||
|
"der Dieb muss seine Signaturzahl auch spueren: {}",
|
||||||
|
game.attribute(p, Attribute::CriticalChance, None, ®)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user