combat system overhaul - d20/hack-like
This commit is contained in:
parent
32044dbb6a
commit
c169a1eae6
20 changed files with 762 additions and 292 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2278,6 +2278,7 @@ dependencies = [
|
||||||
"bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)",
|
"bracket-lib 0.8.7 (git+https://github.com/amethyst/bracket-lib.git?rev=851f6f08675444fb6fa088b9e67bee9fd75554c6)",
|
||||||
"criterion",
|
"criterion",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
"rltk",
|
"rltk",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rltk = { version = "^0.8.7", features = ["serde"] }
|
rltk = { version = "^0.8.7", features = ["serde"] }
|
||||||
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] }
|
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git", rev = "851f6f08675444fb6fa088b9e67bee9fd75554c6", features = ["serde"] }
|
||||||
|
regex = "1.3.6"
|
||||||
specs = { version = "0.16.1", features = ["serde"] }
|
specs = { version = "0.16.1", features = ["serde"] }
|
||||||
specs-derive = "0.4.1"
|
specs-derive = "0.4.1"
|
||||||
serde = { version = "1.0.93", features = ["derive"]}
|
serde = { version = "1.0.93", features = ["derive"]}
|
||||||
|
|
|
||||||
|
|
@ -59,43 +59,43 @@
|
||||||
"id": "equip_dagger",
|
"id": "equip_dagger",
|
||||||
"name": { "name": "dagger", "plural": "daggers" },
|
"name": { "name": "dagger", "plural": "daggers" },
|
||||||
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE", "FINESSE"],
|
||||||
"effects": { "melee_power_bonus": "1" }
|
"effects": { "base_damage": "1d4"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "equip_shortsword",
|
"id": "equip_shortsword",
|
||||||
"name": { "name": "shortsword", "plural": "shortswords" },
|
"name": { "name": "shortsword", "plural": "shortswords" },
|
||||||
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE", "STRENGTH"],
|
||||||
"effects": { "melee_power_bonus": "2" }
|
"effects": { "base_damage": "1d6"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "equip_longsword",
|
"id": "equip_longsword",
|
||||||
"name": { "name": "longsword", "plural": "longswords" },
|
"name": { "name": "longsword", "plural": "longswords" },
|
||||||
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_MELEE"],
|
"flags": ["EQUIP_MELEE", "STRENGTH"],
|
||||||
"effects": { "melee_power_bonus": "3" }
|
"effects": { "base_damage": "1d8" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "equip_smallshield",
|
"id": "equip_smallshield",
|
||||||
"name": { "name": "buckler", "plural": "bucklers" },
|
"name": { "name": "buckler", "plural": "bucklers" },
|
||||||
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
"effects": { "defence_bonus": "1" }
|
"effects": { "ac": "1" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "equip_mediumshield",
|
"id": "equip_mediumshield",
|
||||||
"name": { "name": "medium shield", "plural": "medium shields" },
|
"name": { "name": "medium shield", "plural": "medium shields" },
|
||||||
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
"effects": { "defence_bonus": "2", "melee_power_bonus": "-1" }
|
"effects": { "ac": "2", "melee_power_bonus": "-1" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "equip_largeshield",
|
"id": "equip_largeshield",
|
||||||
"name": { "name": "large shield", "plural": "large shields" },
|
"name": { "name": "large shield", "plural": "large shields" },
|
||||||
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
"renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 },
|
||||||
"flags": ["EQUIP_SHIELD"],
|
"flags": ["EQUIP_SHIELD"],
|
||||||
"effects": { "defence_bonus": "4", "melee_power_bonus": "-2" }
|
"effects": { "ac": "4", "melee_power_bonus": "-2" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "wand_magicmissile",
|
"id": "wand_magicmissile",
|
||||||
|
|
|
||||||
167
raws/mobs.json
167
raws/mobs.json
|
|
@ -3,204 +3,233 @@
|
||||||
"id": "npc_barkeep",
|
"id": "npc_barkeep",
|
||||||
"name": "barkeep",
|
"name": "barkeep",
|
||||||
"renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#EE82EE", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
|
||||||
"quips": ["Drink?"]
|
"quips": ["Drink?"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_townsperson",
|
"id": "npc_townsperson",
|
||||||
"name": "townsperson",
|
"name": "townsperson",
|
||||||
"renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#9fa86c", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
|
||||||
"quips": ["You won't catch me quipping."]
|
"quips": ["You won't catch me quipping."]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_drunk",
|
"id": "npc_drunk",
|
||||||
"name": "drunk",
|
"name": "drunk",
|
||||||
"renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#a0a83c", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
|
||||||
"quips": ["Hic!", "H-Hic'."]
|
"quips": ["Hic!", "H-Hic'."]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_fisher",
|
"id": "npc_fisher",
|
||||||
"name": "fisher",
|
"name": "fisher",
|
||||||
"renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#3ca3a8", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
|
||||||
"quips": ["Placeholder."]
|
"quips": ["Placeholder."]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_dockworker",
|
"id": "npc_dockworker",
|
||||||
"name": "dock worker",
|
"name": "dock worker",
|
||||||
"renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#68d8de", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
|
||||||
"quips": ["Placeholder."]
|
"quips": ["Placeholder."]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_priest",
|
"id": "npc_priest",
|
||||||
"name": "priest",
|
"name": "priest",
|
||||||
"renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
|
"vision_range": 4
|
||||||
"vision_range": 4,
|
|
||||||
"ai": "bystander"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_miner",
|
"id": "npc_miner",
|
||||||
"name": "miner",
|
"name": "miner",
|
||||||
"renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#946123", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 1 },
|
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d8"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "npc_guard",
|
"id": "npc_guard",
|
||||||
"name": "smalltown guard",
|
"name": "smalltown guard",
|
||||||
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "@", "fg": "#034efc", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 12, "hp": 12, "defence": 3, "power": 3 },
|
"level": 2,
|
||||||
"vision_range": 4,
|
"vision_range": 4,
|
||||||
"ai": "bystander",
|
"quips": ["You wont catch me down the mine.", "I'm not paid enough for that."],
|
||||||
"quips": ["You wont catch me down the mine.", "I'm not paid enough for that."]
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d8"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dog_little",
|
"id": "dog_little",
|
||||||
"name": "little dog",
|
"name": "little dog",
|
||||||
"renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#FFFFFF", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["BYSTANDER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
|
"level": 2,
|
||||||
|
"bac": 6,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d6"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "rat",
|
"id": "rat",
|
||||||
"name": "rat",
|
"name": "rat",
|
||||||
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "r", "fg": "#aa6000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
|
"bac": 6,
|
||||||
"vision_range": 8,
|
"vision_range": 8,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d2"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "rat_giant",
|
"id": "rat_giant",
|
||||||
"name": "giant rat",
|
"name": "giant rat",
|
||||||
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
|
"level": 2,
|
||||||
|
"bac": 7,
|
||||||
"vision_range": 8,
|
"vision_range": 8,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d3"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dog",
|
"id": "dog",
|
||||||
"name": "dog",
|
"name": "dog",
|
||||||
"renderable": { "glyph": "d", "fg": "#EEEEEE", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#EEEEEE", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 2 },
|
"level": 4,
|
||||||
|
"bac": 5,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d6"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dog_large",
|
"id": "dog_large",
|
||||||
"name": "large dog",
|
"name": "large dog",
|
||||||
"renderable": { "glyph": "d", "fg": "#DDDDDD", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#DDDDDD", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 12, "hp": 12, "defence": 0, "power": 3 },
|
"level": 6,
|
||||||
|
"bac": 4,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "2d4"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "goblin",
|
"id": "goblin",
|
||||||
"name": "goblin",
|
"name": "goblin",
|
||||||
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 2 },
|
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d4"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "kobold",
|
"id": "kobold",
|
||||||
"name": "kobold",
|
"name": "kobold",
|
||||||
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
|
|
||||||
"vision_range": 7,
|
"vision_range": 7,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d4"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "jackal",
|
"id": "jackal",
|
||||||
"name": "jackal",
|
"name": "jackal",
|
||||||
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 1 },
|
"bac": 7,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d2"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fox",
|
"id": "fox",
|
||||||
"name": "fox",
|
"name": "fox",
|
||||||
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#FF0000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 4, "hp": 4, "defence": 0, "power": 1 },
|
"bac": 7,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d3"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "coyote",
|
"id": "coyote",
|
||||||
"name": "coyote",
|
"name": "coyote",
|
||||||
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 2 },
|
"bac": 7,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "1d4"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wolf",
|
||||||
|
"name": "wolf",
|
||||||
|
"renderable": { "glyph": "d", "fg": "#5E4225", "bg": "#000000", "order": 1 },
|
||||||
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
|
"bac": 4,
|
||||||
|
"vision_range": 12,
|
||||||
|
"attacks": [
|
||||||
|
{ "name": "bites", "hit_bonus": 0, "damage": "2d4"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "goblin_chieftain",
|
"id": "goblin_chieftain",
|
||||||
"name": "goblin chieftain",
|
"name": "goblin chieftain",
|
||||||
"renderable": { "glyph": "G", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "G", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 1, "power": 2 },
|
"level": 2,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d8"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "orc",
|
"id": "orc",
|
||||||
"name": "orc",
|
"name": "orc",
|
||||||
"renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 3 },
|
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d6"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "orc_large",
|
"id": "orc_large",
|
||||||
"name": "large orc",
|
"name": "large orc",
|
||||||
"renderable": { "glyph": "o", "fg": "#008000", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "o", "fg": "#008000", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 12, "hp": 12, "defence": 1, "power": 3 },
|
"level": 2,
|
||||||
"vision_range": 12,
|
"vision_range": 12,
|
||||||
"ai": "melee"
|
"attacks": [
|
||||||
|
{ "name": "hits", "hit_bonus": 0, "damage": "1d6"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ogre",
|
"id": "ogre",
|
||||||
"name": "ogre",
|
"name": "ogre",
|
||||||
"renderable": { "glyph": "O", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
"renderable": { "glyph": "O", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
||||||
"flags": ["BLOCKS_TILE"],
|
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||||
"stats": { "max_hp": 24, "hp": 24, "defence": 3, "power": 3 },
|
"level": 5,
|
||||||
"vision_range": 8,
|
"bac": 5,
|
||||||
"ai": "melee"
|
"vision_range": 8
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -60,10 +60,13 @@
|
||||||
{ "id": "orc_large", "weight": 1, "difficulty": 3},
|
{ "id": "orc_large", "weight": 1, "difficulty": 3},
|
||||||
{ "id": "goblin_chieftain", "weight": 1, "difficulty": 3},
|
{ "id": "goblin_chieftain", "weight": 1, "difficulty": 3},
|
||||||
|
|
||||||
{ "id": "dog", "weight": 1, "difficulty": 4},
|
|
||||||
{ "id": "ogre", "weight": 1, "difficulty": 4},
|
{ "id": "ogre", "weight": 1, "difficulty": 4},
|
||||||
|
|
||||||
{ "id": "dog_large", "weight": 1, "difficulty": 5}
|
{ "id": "dog", "weight": 1, "difficulty": 5},
|
||||||
|
|
||||||
|
{ "id": "wolf", "weight": 2, "difficulty": 6},
|
||||||
|
|
||||||
|
{ "id": "dog_large", "weight": 1, "difficulty": 7}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -100,12 +100,19 @@ pub struct HungerClock {
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ProvidesNutrition {}
|
pub struct ProvidesNutrition {}
|
||||||
|
|
||||||
#[derive(Component, Debug, ConvertSaveload, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct CombatStats {
|
pub struct Pool {
|
||||||
pub max_hp: i32,
|
pub max: i32,
|
||||||
pub hp: i32,
|
pub current: i32,
|
||||||
pub defence: i32,
|
}
|
||||||
pub power: i32,
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Pools {
|
||||||
|
pub hit_points: Pool,
|
||||||
|
pub mana: Pool,
|
||||||
|
pub xp: i32,
|
||||||
|
pub bac: i32,
|
||||||
|
pub level: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
@ -115,6 +122,18 @@ pub struct Attribute {
|
||||||
pub bonus: i32,
|
pub bonus: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Skill {
|
||||||
|
Melee,
|
||||||
|
Defence,
|
||||||
|
Magic,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Skills {
|
||||||
|
pub skills: HashMap<Skill, i32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
pub strength: Attribute,
|
pub strength: Attribute,
|
||||||
|
|
@ -153,15 +172,46 @@ pub struct Item {}
|
||||||
pub enum EquipmentSlot {
|
pub enum EquipmentSlot {
|
||||||
Melee,
|
Melee,
|
||||||
Shield,
|
Shield,
|
||||||
|
Head,
|
||||||
|
Neck,
|
||||||
|
Torso,
|
||||||
|
Hands,
|
||||||
|
Legs,
|
||||||
|
Feet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum WeaponAttribute {
|
||||||
|
Strength,
|
||||||
|
Dexterity,
|
||||||
|
Finesse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct MeleeWeapon {
|
||||||
|
pub attribute: WeaponAttribute,
|
||||||
|
pub damage_n_dice: i32,
|
||||||
|
pub damage_die_type: i32,
|
||||||
|
pub damage_bonus: i32,
|
||||||
|
pub hit_bonus: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct NaturalAttack {
|
||||||
|
pub name: String,
|
||||||
|
pub damage_n_dice: i32,
|
||||||
|
pub damage_die_type: i32,
|
||||||
|
pub damage_bonus: i32,
|
||||||
|
pub hit_bonus: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct NaturalAttacks {
|
||||||
|
pub attacks: Vec<NaturalAttack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
pub struct MeleePowerBonus {
|
pub struct ArmourClassBonus {
|
||||||
pub amount: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, ConvertSaveload, Clone)]
|
|
||||||
pub struct DefenceBonus {
|
|
||||||
pub amount: i32,
|
pub amount: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use super::{gamelog, CombatStats, Entities, Item, Map, Name, Player, Position, RunState, SufferDamage};
|
use super::{gamelog, Entities, Item, Map, Name, Player, Pools, Position, RunState, SufferDamage};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
pub struct DamageSystem {}
|
pub struct DamageSystem {}
|
||||||
|
|
||||||
impl<'a> System<'a> for DamageSystem {
|
impl<'a> System<'a> for DamageSystem {
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
WriteStorage<'a, CombatStats>,
|
WriteStorage<'a, Pools>,
|
||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
WriteExpect<'a, Map>,
|
WriteExpect<'a, Map>,
|
||||||
ReadStorage<'a, Position>,
|
ReadStorage<'a, Position>,
|
||||||
|
|
@ -16,7 +16,7 @@ impl<'a> System<'a> for DamageSystem {
|
||||||
let (mut stats, mut damage, mut map, positions, entities) = data;
|
let (mut stats, mut damage, mut map, positions, entities) = data;
|
||||||
|
|
||||||
for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() {
|
for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() {
|
||||||
stats.hp -= damage.amount.iter().sum::<i32>();
|
stats.hit_points.current -= damage.amount.iter().sum::<i32>();
|
||||||
let pos = positions.get(entity);
|
let pos = positions.get(entity);
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
let idx = map.xy_idx(pos.x, pos.y);
|
let idx = map.xy_idx(pos.x, pos.y);
|
||||||
|
|
@ -32,13 +32,13 @@ pub fn delete_the_dead(ecs: &mut World) {
|
||||||
let mut dead: Vec<Entity> = Vec::new();
|
let mut dead: Vec<Entity> = Vec::new();
|
||||||
// Using scope to make borrow checker happy
|
// Using scope to make borrow checker happy
|
||||||
{
|
{
|
||||||
let combat_stats = ecs.read_storage::<CombatStats>();
|
let combat_stats = ecs.read_storage::<Pools>();
|
||||||
let players = ecs.read_storage::<Player>();
|
let players = ecs.read_storage::<Player>();
|
||||||
let names = ecs.read_storage::<Name>();
|
let names = ecs.read_storage::<Name>();
|
||||||
let items = ecs.read_storage::<Item>();
|
let items = ecs.read_storage::<Item>();
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
for (entity, stats) in (&entities, &combat_stats).join() {
|
for (entity, stats) in (&entities, &combat_stats).join() {
|
||||||
if stats.hp < 1 {
|
if stats.hit_points.current < 1 {
|
||||||
let player = players.get(entity);
|
let player = players.get(entity);
|
||||||
match player {
|
match player {
|
||||||
None => {
|
None => {
|
||||||
|
|
|
||||||
62
src/gamesystem.rs
Normal file
62
src/gamesystem.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
use super::{Skill, Skills};
|
||||||
|
|
||||||
|
pub fn attr_bonus(value: i32) -> i32 {
|
||||||
|
return (value - 10) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_hp_per_level(rng: &mut rltk::RandomNumberGenerator, constitution: i32) -> i32 {
|
||||||
|
return rng.roll_dice(1, 8) + attr_bonus(constitution);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_hp_at_level(rng: &mut rltk::RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
|
||||||
|
let mut total = 10 + attr_bonus(constitution);
|
||||||
|
for _i in 0..level {
|
||||||
|
total += player_hp_per_level(rng, constitution);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn npc_hp(rng: &mut rltk::RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
|
||||||
|
let mut total = 1;
|
||||||
|
for _i in 0..level {
|
||||||
|
total += rng.roll_dice(1, 8) + attr_bonus(constitution);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mana_per_level(rng: &mut rltk::RandomNumberGenerator, intelligence: i32) -> i32 {
|
||||||
|
return rng.roll_dice(1, 4) + attr_bonus(intelligence);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mana_at_level(rng: &mut rltk::RandomNumberGenerator, intelligence: i32, level: i32) -> i32 {
|
||||||
|
let mut total = 12;
|
||||||
|
for _i in 0..level {
|
||||||
|
total += mana_per_level(rng, intelligence);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skill_bonus(skill: Skill, skills: &Skills) -> i32 {
|
||||||
|
if skills.skills.contains_key(&skill) {
|
||||||
|
return skills.skills[&skill];
|
||||||
|
} else {
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roll_4d6(rng: &mut rltk::RandomNumberGenerator) -> i32 {
|
||||||
|
let mut rolls: Vec<i32> = Vec::new();
|
||||||
|
for _i in 0..4 {
|
||||||
|
rolls.push(rng.roll_dice(1, 6));
|
||||||
|
}
|
||||||
|
rolls.sort_unstable();
|
||||||
|
|
||||||
|
let mut roll = 0;
|
||||||
|
rltk::console::log(format!("roll 0"));
|
||||||
|
for i in 1..rolls.len() {
|
||||||
|
roll += rolls[i];
|
||||||
|
rltk::console::log(format!("+ {}", rolls[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return roll;
|
||||||
|
}
|
||||||
70
src/gui.rs
70
src/gui.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
camera, gamelog, rex_assets::RexAssets, CombatStats, Equipped, Hidden, HungerClock, HungerState, InBackpack, Map,
|
camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, Equipped, Hidden, HungerClock,
|
||||||
Name, Player, Point, Position, RunState, State, Viewshed,
|
HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, RunState, Skill, Skills, State, Viewshed,
|
||||||
};
|
};
|
||||||
use rltk::{Rltk, VirtualKeyCode, RGB};
|
use rltk::{Rltk, VirtualKeyCode, RGB};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -40,13 +40,69 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
||||||
ctx.draw_hollow_box(71, 0, 28, 55, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); // Side box
|
ctx.draw_hollow_box(71, 0, 28, 55, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); // Side box
|
||||||
|
|
||||||
// Render stats
|
// Render stats
|
||||||
let combat_stats = ecs.read_storage::<CombatStats>();
|
let pools = ecs.read_storage::<Pools>();
|
||||||
|
let attributes = ecs.read_storage::<Attributes>();
|
||||||
let players = ecs.read_storage::<Player>();
|
let players = ecs.read_storage::<Player>();
|
||||||
let hunger = ecs.read_storage::<HungerClock>();
|
let hunger = ecs.read_storage::<HungerClock>();
|
||||||
for (_player, stats, hunger) in (&players, &combat_stats, &hunger).join() {
|
let skills = ecs.read_storage::<Skills>();
|
||||||
draw_lerping_bar(ctx, 2, 53, 26, stats.hp, stats.max_hp, RGB::from_u8(0, 255, 0), RGB::from_u8(255, 0, 0));
|
for (_player, stats, attributes, hunger, skills) in (&players, &pools, &attributes, &hunger, &skills).join() {
|
||||||
//ctx.draw_bar_horizontal(2, 53, 26, stats.hp, stats.max_hp, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK));
|
// Draw hp/mana bars
|
||||||
draw_lerping_bar(ctx, 2, 54, 26, stats.hp, stats.max_hp, RGB::named(rltk::BLUE), RGB::named(rltk::BLACK));
|
draw_lerping_bar(
|
||||||
|
ctx,
|
||||||
|
2,
|
||||||
|
53,
|
||||||
|
26,
|
||||||
|
stats.hit_points.current,
|
||||||
|
stats.hit_points.max,
|
||||||
|
RGB::from_u8(0, 255, 0),
|
||||||
|
RGB::from_u8(255, 0, 0),
|
||||||
|
);
|
||||||
|
draw_lerping_bar(
|
||||||
|
ctx,
|
||||||
|
2,
|
||||||
|
54,
|
||||||
|
26,
|
||||||
|
stats.mana.current,
|
||||||
|
stats.mana.max,
|
||||||
|
RGB::named(rltk::BLUE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
);
|
||||||
|
// Draw AC
|
||||||
|
let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*skills);
|
||||||
|
let mut armour_ac_bonus = 0;
|
||||||
|
let equipped = ecs.read_storage::<Equipped>();
|
||||||
|
let ac = ecs.read_storage::<ArmourClassBonus>();
|
||||||
|
let player_entity = ecs.fetch::<Entity>();
|
||||||
|
for (wielded, ac) in (&equipped, &ac).join() {
|
||||||
|
if wielded.owner == *player_entity {
|
||||||
|
armour_ac_bonus += ac.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let armour_class = stats.bac - attributes.dexterity.bonus - skill_ac_bonus - armour_ac_bonus;
|
||||||
|
ctx.print_color(30, 53, RGB::named(rltk::PINK), RGB::named(rltk::BLACK), "AC");
|
||||||
|
ctx.print_color(32, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), armour_class);
|
||||||
|
// Draw level
|
||||||
|
ctx.print_color(
|
||||||
|
30,
|
||||||
|
54,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
format!("XP{}/{}", stats.level, stats.xp),
|
||||||
|
);
|
||||||
|
// Draw attributes
|
||||||
|
ctx.print_color(36, 53, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "STR");
|
||||||
|
ctx.print_color(39, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.strength.base);
|
||||||
|
ctx.print_color(43, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "DEX");
|
||||||
|
ctx.print_color(46, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.dexterity.base);
|
||||||
|
ctx.print_color(50, 53, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "CON");
|
||||||
|
ctx.print_color(53, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.constitution.base);
|
||||||
|
ctx.print_color(36, 54, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "INT");
|
||||||
|
ctx.print_color(39, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.intelligence.base);
|
||||||
|
ctx.print_color(43, 54, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "WIS");
|
||||||
|
ctx.print_color(46, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.wisdom.base);
|
||||||
|
ctx.print_color(50, 54, RGB::named(rltk::PURPLE), RGB::named(rltk::BLACK), "CHA");
|
||||||
|
ctx.print_color(53, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.charisma.base);
|
||||||
|
// Draw hunger
|
||||||
match hunger.state {
|
match hunger.state {
|
||||||
HungerState::Satiated => {
|
HungerState::Satiated => {
|
||||||
ctx.print_color_right(70, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "Satiated")
|
ctx.print_color_right(70, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "Satiated")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog, CombatStats, Confusion, Consumable, Cursed, Destructible, Digger, Equippable, Equipped, HungerClock,
|
gamelog, Confusion, Consumable, Cursed, Destructible, Digger, Equippable, Equipped, HungerClock, HungerState,
|
||||||
HungerState, InBackpack, InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Position, ProvidesHealing,
|
InBackpack, InflictsDamage, MagicMapper, Map, Name, ParticleBuilder, Point, Pools, Position, ProvidesHealing,
|
||||||
ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, TileType, Viewshed, Wand, WantsToDropItem,
|
ProvidesNutrition, RandomNumberGenerator, RunState, SufferDamage, TileType, Viewshed, Wand, WantsToDropItem,
|
||||||
WantsToPickupItem, WantsToRemoveItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME,
|
WantsToPickupItem, WantsToRemoveItem, WantsToUseItem, AOE, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME,
|
||||||
};
|
};
|
||||||
|
|
@ -60,7 +60,7 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
ReadStorage<'a, ProvidesHealing>,
|
ReadStorage<'a, ProvidesHealing>,
|
||||||
ReadStorage<'a, ProvidesNutrition>,
|
ReadStorage<'a, ProvidesNutrition>,
|
||||||
WriteStorage<'a, HungerClock>,
|
WriteStorage<'a, HungerClock>,
|
||||||
WriteStorage<'a, CombatStats>,
|
WriteStorage<'a, Pools>,
|
||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
WriteExpect<'a, ParticleBuilder>,
|
WriteExpect<'a, ParticleBuilder>,
|
||||||
ReadStorage<'a, Position>,
|
ReadStorage<'a, Position>,
|
||||||
|
|
@ -268,7 +268,8 @@ impl<'a> System<'a> for ItemUseSystem {
|
||||||
for target in targets.iter() {
|
for target in targets.iter() {
|
||||||
let stats = combat_stats.get_mut(*target);
|
let stats = combat_stats.get_mut(*target);
|
||||||
if let Some(stats) = stats {
|
if let Some(stats) = stats {
|
||||||
stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount);
|
stats.hit_points.current =
|
||||||
|
i32::min(stats.hit_points.max, stats.hit_points.current + heal.amount);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog::Logger::new().append("Quaffing, you recover some vigour.").log();
|
gamelog::Logger::new().append("Quaffing, you recover some vigour.").log();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/main.rs
21
src/main.rs
|
|
@ -35,6 +35,7 @@ mod inventory_system;
|
||||||
use inventory_system::*;
|
use inventory_system::*;
|
||||||
mod particle_system;
|
mod particle_system;
|
||||||
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME};
|
use particle_system::{ParticleBuilder, DEFAULT_PARTICLE_LIFETIME, LONG_PARTICLE_LIFETIME};
|
||||||
|
mod gamesystem;
|
||||||
mod random_table;
|
mod random_table;
|
||||||
mod rex_assets;
|
mod rex_assets;
|
||||||
|
|
||||||
|
|
@ -212,10 +213,10 @@ impl State {
|
||||||
.append("recover some of your strength")
|
.append("recover some of your strength")
|
||||||
.period()
|
.period()
|
||||||
.log();
|
.log();
|
||||||
let mut player_health_store = self.ecs.write_storage::<CombatStats>();
|
let mut pools = self.ecs.write_storage::<Pools>();
|
||||||
let player_health = player_health_store.get_mut(*player_entity);
|
let stats = pools.get_mut(*player_entity);
|
||||||
if let Some(player_health) = player_health {
|
if let Some(stats) = stats {
|
||||||
player_health.hp = i32::max(player_health.hp, player_health.max_hp / 2);
|
stats.hit_points.current = i32::max(stats.hit_points.current, stats.hit_points.max / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,16 +525,18 @@ fn main() -> rltk::BError {
|
||||||
gs.ecs.register::<BlocksTile>();
|
gs.ecs.register::<BlocksTile>();
|
||||||
gs.ecs.register::<BlocksVisibility>();
|
gs.ecs.register::<BlocksVisibility>();
|
||||||
gs.ecs.register::<Door>();
|
gs.ecs.register::<Door>();
|
||||||
gs.ecs.register::<CombatStats>();
|
gs.ecs.register::<Pools>();
|
||||||
gs.ecs.register::<Attributes>();
|
gs.ecs.register::<Attributes>();
|
||||||
|
gs.ecs.register::<Skills>();
|
||||||
gs.ecs.register::<HungerClock>();
|
gs.ecs.register::<HungerClock>();
|
||||||
gs.ecs.register::<WantsToMelee>();
|
gs.ecs.register::<WantsToMelee>();
|
||||||
gs.ecs.register::<SufferDamage>();
|
gs.ecs.register::<SufferDamage>();
|
||||||
gs.ecs.register::<Item>();
|
gs.ecs.register::<Item>();
|
||||||
gs.ecs.register::<Equippable>();
|
gs.ecs.register::<Equippable>();
|
||||||
gs.ecs.register::<Equipped>();
|
gs.ecs.register::<Equipped>();
|
||||||
gs.ecs.register::<MeleePowerBonus>();
|
gs.ecs.register::<MeleeWeapon>();
|
||||||
gs.ecs.register::<DefenceBonus>();
|
gs.ecs.register::<NaturalAttacks>();
|
||||||
|
gs.ecs.register::<ArmourClassBonus>();
|
||||||
gs.ecs.register::<Cursed>();
|
gs.ecs.register::<Cursed>();
|
||||||
gs.ecs.register::<ProvidesHealing>();
|
gs.ecs.register::<ProvidesHealing>();
|
||||||
gs.ecs.register::<InflictsDamage>();
|
gs.ecs.register::<InflictsDamage>();
|
||||||
|
|
@ -562,11 +565,11 @@ fn main() -> rltk::BError {
|
||||||
|
|
||||||
raws::load_raws();
|
raws::load_raws();
|
||||||
|
|
||||||
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
gs.ecs.insert(rltk::RandomNumberGenerator::new());
|
||||||
gs.ecs.insert(Map::new(1, 64, 64, 0));
|
gs.ecs.insert(Map::new(1, 64, 64, 0));
|
||||||
gs.ecs.insert(Point::new(0, 0));
|
gs.ecs.insert(Point::new(0, 0));
|
||||||
|
let player_entity = spawner::player(&mut gs.ecs, 0, 0);
|
||||||
gs.ecs.insert(player_entity);
|
gs.ecs.insert(player_entity);
|
||||||
gs.ecs.insert(rltk::RandomNumberGenerator::new());
|
|
||||||
gs.ecs.insert(RunState::MapGeneration {});
|
gs.ecs.insert(RunState::MapGeneration {});
|
||||||
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
gs.ecs.insert(particle_system::ParticleBuilder::new());
|
||||||
gs.ecs.insert(rex_assets::RexAssets::new());
|
gs.ecs.insert(rex_assets::RexAssets::new());
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pub const GOBLINS_4X4: PrefabVault =
|
||||||
PrefabVault { template: GOBLINS_4X4_V, width: 4, height: 4, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
PrefabVault { template: GOBLINS_4X4_V, width: 4, height: 4, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
||||||
const GOBLINS_4X4_V: &str = "
|
const GOBLINS_4X4_V: &str = "
|
||||||
#^
|
#^
|
||||||
#G#
|
#$#
|
||||||
#g#
|
#g#
|
||||||
^g^
|
^g^
|
||||||
";
|
";
|
||||||
|
|
@ -78,13 +78,13 @@ pub const GOBLINS2_4X4: PrefabVault =
|
||||||
PrefabVault { template: GOBLINS2_4X4_V, width: 4, height: 4, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
PrefabVault { template: GOBLINS2_4X4_V, width: 4, height: 4, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
||||||
const GOBLINS2_4X4_V: &str = "
|
const GOBLINS2_4X4_V: &str = "
|
||||||
#^#g
|
#^#g
|
||||||
G# #
|
$# #
|
||||||
g#
|
g#
|
||||||
# g^
|
# g^
|
||||||
";
|
";
|
||||||
|
|
||||||
pub const GOBLINS_5X5: PrefabVault =
|
pub const GOBLINS_5X5: PrefabVault =
|
||||||
PrefabVault { template: GOBLINS_5X5_V, width: 5, height: 5, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
PrefabVault { template: GOBLINS_5X5_V, width: 5, height: 5, first_id: 3, last_id: 100, can_flip: Flipping::Both };
|
||||||
const GOBLINS_5X5_V: &str = "
|
const GOBLINS_5X5_V: &str = "
|
||||||
^#g
|
^#g
|
||||||
G#?#^
|
G#?#^
|
||||||
|
|
@ -94,7 +94,7 @@ G#?#^
|
||||||
";
|
";
|
||||||
|
|
||||||
pub const GOBLINS_6X6: PrefabVault =
|
pub const GOBLINS_6X6: PrefabVault =
|
||||||
PrefabVault { template: GOBLINS_6X6_V, width: 6, height: 6, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
PrefabVault { template: GOBLINS_6X6_V, width: 6, height: 6, first_id: 5, last_id: 100, can_flip: Flipping::Both };
|
||||||
const GOBLINS_6X6_V: &str = "
|
const GOBLINS_6X6_V: &str = "
|
||||||
#
|
#
|
||||||
#^#g
|
#^#g
|
||||||
|
|
@ -157,7 +157,7 @@ const HOUSE_TRAP_7X7_V: &str = "
|
||||||
";
|
";
|
||||||
|
|
||||||
pub const ORC_HOUSE_8X8: PrefabVault =
|
pub const ORC_HOUSE_8X8: PrefabVault =
|
||||||
PrefabVault { template: ORC_HOUSE_8X8_V, width: 8, height: 8, first_id: 0, last_id: 100, can_flip: Flipping::Both };
|
PrefabVault { template: ORC_HOUSE_8X8_V, width: 8, height: 8, first_id: 2, last_id: 100, can_flip: Flipping::Both };
|
||||||
const ORC_HOUSE_8X8_V: &str = "
|
const ORC_HOUSE_8X8_V: &str = "
|
||||||
|
|
||||||
######
|
######
|
||||||
|
|
|
||||||
|
|
@ -233,14 +233,8 @@ impl TownBuilder {
|
||||||
build_data: &mut BuilderMap,
|
build_data: &mut BuilderMap,
|
||||||
rng: &mut rltk::RandomNumberGenerator,
|
rng: &mut rltk::RandomNumberGenerator,
|
||||||
) {
|
) {
|
||||||
for y in building.1..building.1 + building.3 {
|
let mut to_place: Vec<&str> = vec!["rat", "rat", "rat"];
|
||||||
for x in building.0..building.0 + building.2 {
|
self.random_building_spawn(building, build_data, rng, &mut to_place, 0);
|
||||||
let idx = build_data.map.xy_idx(x, y);
|
|
||||||
if build_data.map.tiles[idx] == TileType::WoodFloor && idx != 0 && rng.roll_dice(1, 3) == 1 {
|
|
||||||
build_data.spawn_list.push((idx, "rat".to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grass_layer(&mut self, build_data: &mut BuilderMap) {
|
fn grass_layer(&mut self, build_data: &mut BuilderMap) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog, CombatStats, DefenceBonus, Equipped, HungerClock, HungerState, MeleePowerBonus, Name, ParticleBuilder,
|
gamelog, gamesystem, ArmourClassBonus, Attributes, EquipmentSlot, Equipped, HungerClock, HungerState, MeleeWeapon,
|
||||||
Position, SufferDamage, WantsToMelee,
|
Name, NaturalAttacks, ParticleBuilder, Pools, Position, Skill, Skills, SufferDamage, WantsToMelee, WeaponAttribute,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
|
@ -12,14 +12,18 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteStorage<'a, WantsToMelee>,
|
WriteStorage<'a, WantsToMelee>,
|
||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadStorage<'a, CombatStats>,
|
ReadStorage<'a, Attributes>,
|
||||||
|
ReadStorage<'a, Skills>,
|
||||||
|
ReadStorage<'a, Pools>,
|
||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
WriteExpect<'a, ParticleBuilder>,
|
WriteExpect<'a, ParticleBuilder>,
|
||||||
ReadStorage<'a, Position>,
|
ReadStorage<'a, Position>,
|
||||||
ReadStorage<'a, Equipped>,
|
ReadStorage<'a, Equipped>,
|
||||||
ReadStorage<'a, DefenceBonus>,
|
ReadStorage<'a, MeleeWeapon>,
|
||||||
ReadStorage<'a, MeleePowerBonus>,
|
ReadStorage<'a, NaturalAttacks>,
|
||||||
|
ReadStorage<'a, ArmourClassBonus>,
|
||||||
ReadStorage<'a, HungerClock>,
|
ReadStorage<'a, HungerClock>,
|
||||||
|
WriteExpect<'a, rltk::RandomNumberGenerator>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
|
@ -28,58 +32,146 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
player_entity,
|
player_entity,
|
||||||
mut wants_melee,
|
mut wants_melee,
|
||||||
names,
|
names,
|
||||||
combat_stats,
|
attributes,
|
||||||
|
skills,
|
||||||
|
pools,
|
||||||
mut inflict_damage,
|
mut inflict_damage,
|
||||||
mut particle_builder,
|
mut particle_builder,
|
||||||
positions,
|
positions,
|
||||||
equipped,
|
equipped,
|
||||||
defence_bonuses,
|
melee_weapons,
|
||||||
melee_power_bonuses,
|
natural_attacks,
|
||||||
|
ac,
|
||||||
hunger_clock,
|
hunger_clock,
|
||||||
|
mut rng,
|
||||||
) = data;
|
) = data;
|
||||||
|
|
||||||
for (entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() {
|
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in
|
||||||
if stats.hp <= 0 {
|
(&entities, &wants_melee, &names, &attributes, &skills, &pools).join()
|
||||||
|
{
|
||||||
|
let target_pools = pools.get(wants_melee.target).unwrap();
|
||||||
|
let target_attributes = attributes.get(wants_melee.target).unwrap();
|
||||||
|
let target_skills = skills.get(wants_melee.target).unwrap();
|
||||||
|
|
||||||
|
if attacker_pools.hit_points.current <= 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let target_stats = combat_stats.get(wants_melee.target).unwrap();
|
if target_pools.hit_points.current <= 0 {
|
||||||
if target_stats.hp <= 0 {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_name = names.get(wants_melee.target).unwrap();
|
let target_name = names.get(wants_melee.target).unwrap();
|
||||||
|
|
||||||
let mut offensive_bonus = 0;
|
let mut weapon_info = MeleeWeapon {
|
||||||
for (_item_entity, power_bonus, equipped_by) in (&entities, &melee_power_bonuses, &equipped).join() {
|
attribute: WeaponAttribute::Strength,
|
||||||
if equipped_by.owner == entity {
|
hit_bonus: 0,
|
||||||
offensive_bonus += power_bonus.amount;
|
damage_n_dice: 1,
|
||||||
|
damage_die_type: 4,
|
||||||
|
damage_bonus: 0,
|
||||||
|
};
|
||||||
|
let mut attack_verb = "hits";
|
||||||
|
|
||||||
|
if let Some(nat) = natural_attacks.get(entity) {
|
||||||
|
rltk::console::log("Natural attack found");
|
||||||
|
if !nat.attacks.is_empty() {
|
||||||
|
let attack_index = if nat.attacks.len() == 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
rng.roll_dice(1, nat.attacks.len() as i32) as usize - 1
|
||||||
|
};
|
||||||
|
weapon_info.hit_bonus = nat.attacks[attack_index].hit_bonus;
|
||||||
|
weapon_info.damage_n_dice = nat.attacks[attack_index].damage_n_dice;
|
||||||
|
weapon_info.damage_die_type = nat.attacks[attack_index].damage_die_type;
|
||||||
|
weapon_info.damage_bonus = nat.attacks[attack_index].damage_bonus;
|
||||||
|
attack_verb = &nat.attacks[attack_index].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut defensive_bonus = 0;
|
|
||||||
for (_item_entity, defence_bonus, equipped_by) in (&entities, &defence_bonuses, &equipped).join() {
|
for (wielded, melee) in (&equipped, &melee_weapons).join() {
|
||||||
if equipped_by.owner == wants_melee.target {
|
if wielded.owner == entity && wielded.slot == EquipmentSlot::Melee {
|
||||||
defensive_bonus += defence_bonus.amount;
|
weapon_info = melee.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all offensive bonuses
|
||||||
|
let d20 = rng.roll_dice(1, 20);
|
||||||
|
let attribute_hit_bonus = attacker_attributes.strength.bonus;
|
||||||
|
let skill_hit_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
|
||||||
|
let weapon_hit_bonus = weapon_info.hit_bonus;
|
||||||
|
let mut status_hit_bonus = 0;
|
||||||
let hc = hunger_clock.get(entity);
|
let hc = hunger_clock.get(entity);
|
||||||
if let Some(hc) = hc {
|
if let Some(hc) = hc {
|
||||||
match hc.state {
|
match hc.state {
|
||||||
HungerState::Satiated => {
|
HungerState::Satiated => {
|
||||||
offensive_bonus += 1;
|
status_hit_bonus += 1;
|
||||||
}
|
}
|
||||||
HungerState::Weak => {
|
HungerState::Weak => {
|
||||||
offensive_bonus -= 1;
|
status_hit_bonus -= 1;
|
||||||
}
|
}
|
||||||
HungerState::Fainting => {
|
HungerState::Fainting => {
|
||||||
offensive_bonus -= 1;
|
status_hit_bonus -= 2;
|
||||||
defensive_bonus -= 1;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let damage = i32::max(0, (stats.power + offensive_bonus) - (target_stats.defence + defensive_bonus));
|
let modified_hit_roll = d20 - attribute_hit_bonus - skill_hit_bonus - weapon_hit_bonus - status_hit_bonus;
|
||||||
|
|
||||||
if damage == 0 {
|
// Get armour class
|
||||||
|
let bac = target_pools.bac;
|
||||||
|
let attribute_ac_bonus = target_attributes.dexterity.bonus;
|
||||||
|
let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*target_skills);
|
||||||
|
let mut armour_ac_bonus = 0;
|
||||||
|
for (wielded, ac) in (&equipped, &ac).join() {
|
||||||
|
if wielded.owner == wants_melee.target {
|
||||||
|
armour_ac_bonus += ac.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let armour_class = bac - attribute_ac_bonus - skill_ac_bonus - armour_ac_bonus;
|
||||||
|
|
||||||
|
let target_number = 10 + armour_class + attacker_pools.level;
|
||||||
|
|
||||||
|
if d20 != 1 && (d20 == 20 || modified_hit_roll < target_number) {
|
||||||
|
// Target hit!
|
||||||
|
let base_damage = rng.roll_dice(weapon_info.damage_n_dice, weapon_info.damage_die_type);
|
||||||
|
let attribute_damage_bonus = attacker_attributes.strength.bonus;
|
||||||
|
let skill_damage_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
|
||||||
|
let weapon_damage_bonus = weapon_info.damage_bonus;
|
||||||
|
let damage =
|
||||||
|
i32::max(0, base_damage + attribute_damage_bonus + skill_damage_bonus + weapon_damage_bonus);
|
||||||
|
|
||||||
|
let pos = positions.get(wants_melee.target);
|
||||||
|
if let Some(pos) = pos {
|
||||||
|
particle_builder.damage_taken(pos.x, pos.y)
|
||||||
|
}
|
||||||
|
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage);
|
||||||
|
if entity == *player_entity {
|
||||||
|
gamelog::Logger::new() // You hit the <name>.
|
||||||
|
.append("You hit the")
|
||||||
|
.npc_name_n(&target_name.name)
|
||||||
|
.period()
|
||||||
|
.log();
|
||||||
|
} else if wants_melee.target == *player_entity {
|
||||||
|
gamelog::Logger::new() // <name> hits you!
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&name.name)
|
||||||
|
.append(attack_verb)
|
||||||
|
.append("you!")
|
||||||
|
.log();
|
||||||
|
} else {
|
||||||
|
gamelog::Logger::new() // <name> misses the <target>.
|
||||||
|
.append("The")
|
||||||
|
.npc_name(&name.name)
|
||||||
|
.append(attack_verb)
|
||||||
|
.append("the")
|
||||||
|
.npc_name_n(&target_name.name)
|
||||||
|
.period()
|
||||||
|
.log();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pos = positions.get(wants_melee.target);
|
||||||
|
if let Some(pos) = pos {
|
||||||
|
particle_builder.attack_miss(pos.x, pos.y)
|
||||||
|
}
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog::Logger::new() // You miss.
|
gamelog::Logger::new() // You miss.
|
||||||
.append("You miss.")
|
.append("You miss.")
|
||||||
|
|
@ -101,35 +193,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
||||||
.period()
|
.period()
|
||||||
.log();
|
.log();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if entity == *player_entity {
|
|
||||||
gamelog::Logger::new() // You hit the <name>.
|
|
||||||
.append("You hit the")
|
|
||||||
.npc_name_n(&target_name.name)
|
|
||||||
.period()
|
|
||||||
.log();
|
|
||||||
} else if wants_melee.target == *player_entity {
|
|
||||||
gamelog::Logger::new() // <name> hits you!
|
|
||||||
.append("The")
|
|
||||||
.npc_name(&name.name)
|
|
||||||
.colour(rltk::WHITE)
|
|
||||||
.append("hits you!")
|
|
||||||
.log();
|
|
||||||
} else {
|
|
||||||
gamelog::Logger::new() // <name> misses the <target>.
|
|
||||||
.append("The")
|
|
||||||
.npc_name(&name.name)
|
|
||||||
.colour(rltk::WHITE)
|
|
||||||
.append("hits the")
|
|
||||||
.npc_name_n(&target_name.name)
|
|
||||||
.period()
|
|
||||||
.log();
|
|
||||||
}
|
|
||||||
let pos = positions.get(wants_melee.target);
|
|
||||||
if let Some(pos) = pos {
|
|
||||||
particle_builder.damage_taken(pos.x, pos.y)
|
|
||||||
}
|
|
||||||
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,17 @@ impl ParticleBuilder {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attack_miss(&mut self, x: i32, y: i32) {
|
||||||
|
self.request(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
rltk::RGB::named(rltk::CYAN),
|
||||||
|
rltk::RGB::named(rltk::BLACK),
|
||||||
|
rltk::to_cp437('‼'),
|
||||||
|
DEFAULT_PARTICLE_LIFETIME,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trap_triggered(&mut self, x: i32, y: i32) {
|
pub fn trap_triggered(&mut self, x: i32, y: i32) {
|
||||||
self.request(
|
self.request(
|
||||||
x,
|
x,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
gamelog, BlocksTile, BlocksVisibility, Bystander, CombatStats, Door, EntityMoved, Hidden, HungerClock, HungerState,
|
gamelog, Attributes, BlocksTile, BlocksVisibility, Bystander, Door, EntityMoved, Hidden, HungerClock, HungerState,
|
||||||
Item, Map, Monster, Name, ParticleBuilder, Player, Position, Renderable, RunState, State, SufferDamage, Telepath,
|
Item, Map, Monster, Name, ParticleBuilder, Player, Pools, Position, Renderable, RunState, State, SufferDamage,
|
||||||
TileType, Viewshed, WantsToMelee, WantsToPickupItem,
|
Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem,
|
||||||
};
|
};
|
||||||
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
|
use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -11,6 +11,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
let mut positions = ecs.write_storage::<Position>();
|
||||||
let mut players = ecs.write_storage::<Player>();
|
let mut players = ecs.write_storage::<Player>();
|
||||||
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
||||||
|
let attributes = ecs.read_storage::<Attributes>();
|
||||||
let map = ecs.fetch::<Map>();
|
let map = ecs.fetch::<Map>();
|
||||||
|
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
|
|
@ -24,7 +25,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let mut result = RunState::AwaitingInput;
|
let mut result = RunState::AwaitingInput;
|
||||||
let mut door_pos: Option<Point> = None;
|
let mut door_pos: Option<Point> = None;
|
||||||
|
|
||||||
for (_entity, _player, pos) in (&entities, &mut players, &mut positions).join() {
|
for (_entity, _player, pos, attributes) in (&entities, &mut players, &mut positions, &attributes).join() {
|
||||||
let delta_x = i;
|
let delta_x = i;
|
||||||
let delta_y = j;
|
let delta_y = j;
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
if let Some(name) = names.get(*potential_target) {
|
if let Some(name) = names.get(*potential_target) {
|
||||||
gamelog::Logger::new().append("The").item_name(&name.name).append("is blocked.").log();
|
gamelog::Logger::new().append("The").item_name(&name.name).append("is blocked.").log();
|
||||||
}
|
}
|
||||||
} else if rng.roll_dice(1, 6) == 1 {
|
} else if rng.roll_dice(1, 6) + attributes.strength.bonus < 5 {
|
||||||
if let Some(name) = names.get(*potential_target) {
|
if let Some(name) = names.get(*potential_target) {
|
||||||
gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log();
|
gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log();
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +92,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
let mut positions = ecs.write_storage::<Position>();
|
||||||
let mut players = ecs.write_storage::<Player>();
|
let mut players = ecs.write_storage::<Player>();
|
||||||
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
||||||
|
let attributes = ecs.read_storage::<Attributes>();
|
||||||
let map = ecs.fetch::<Map>();
|
let map = ecs.fetch::<Map>();
|
||||||
|
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
|
|
@ -104,7 +106,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let mut result = RunState::AwaitingInput;
|
let mut result = RunState::AwaitingInput;
|
||||||
let mut door_pos: Option<Point> = None;
|
let mut door_pos: Option<Point> = None;
|
||||||
|
|
||||||
for (_entity, _player, pos) in (&entities, &mut players, &mut positions).join() {
|
for (_entity, _player, pos, attributes) in (&entities, &mut players, &mut positions, &attributes).join() {
|
||||||
let delta_x = i;
|
let delta_x = i;
|
||||||
let delta_y = j;
|
let delta_y = j;
|
||||||
|
|
||||||
|
|
@ -122,7 +124,7 @@ pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let door = doors.get_mut(*potential_target);
|
let door = doors.get_mut(*potential_target);
|
||||||
if let Some(door) = door {
|
if let Some(door) = door {
|
||||||
if door.open == false {
|
if door.open == false {
|
||||||
if rng.roll_dice(1, 6) == 1 {
|
if rng.roll_dice(1, 6) + attributes.strength.bonus < 5 {
|
||||||
if let Some(name) = names.get(*potential_target) {
|
if let Some(name) = names.get(*potential_target) {
|
||||||
gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log();
|
gamelog::Logger::new().append("The").item_name(&name.name).append("resists!").log();
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +168,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let mut positions = ecs.write_storage::<Position>();
|
let mut positions = ecs.write_storage::<Position>();
|
||||||
let mut players = ecs.write_storage::<Player>();
|
let mut players = ecs.write_storage::<Player>();
|
||||||
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
let mut viewsheds = ecs.write_storage::<Viewshed>();
|
||||||
|
let attributes = ecs.read_storage::<Attributes>();
|
||||||
let map = ecs.fetch::<Map>();
|
let map = ecs.fetch::<Map>();
|
||||||
|
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
|
|
@ -173,7 +176,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
let names = ecs.read_storage::<Name>();
|
let names = ecs.read_storage::<Name>();
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
||||||
|
|
||||||
for (entity, _player, pos) in (&entities, &mut players, &mut positions).join() {
|
for (entity, _player, pos, attributes) in (&entities, &mut players, &mut positions, &attributes).join() {
|
||||||
let delta_x = i;
|
let delta_x = i;
|
||||||
let delta_y = j;
|
let delta_y = j;
|
||||||
|
|
||||||
|
|
@ -210,8 +213,8 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState {
|
||||||
if door.open == false {
|
if door.open == false {
|
||||||
let mut particle_builder = ecs.write_resource::<ParticleBuilder>();
|
let mut particle_builder = ecs.write_resource::<ParticleBuilder>();
|
||||||
particle_builder.kick(pos.x + delta_x, pos.y + delta_y);
|
particle_builder.kick(pos.x + delta_x, pos.y + delta_y);
|
||||||
// 33% chance of breaking it down.
|
// 33% chance of breaking it down + str
|
||||||
if rng.roll_dice(1, 3) == 1 {
|
if rng.roll_dice(1, 6) + attributes.strength.bonus > 4 {
|
||||||
gamelog::Logger::new()
|
gamelog::Logger::new()
|
||||||
.append("As you kick the")
|
.append("As you kick the")
|
||||||
.item_name_n(target_name)
|
.item_name_n(target_name)
|
||||||
|
|
@ -273,7 +276,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
|
||||||
let mut telepaths = ecs.write_storage::<Telepath>();
|
let mut telepaths = ecs.write_storage::<Telepath>();
|
||||||
let mut entity_moved = ecs.write_storage::<EntityMoved>();
|
let mut entity_moved = ecs.write_storage::<EntityMoved>();
|
||||||
let friendlies = ecs.read_storage::<Bystander>();
|
let friendlies = ecs.read_storage::<Bystander>();
|
||||||
let combat_stats = ecs.read_storage::<CombatStats>();
|
let pools = ecs.read_storage::<Pools>();
|
||||||
let map = ecs.fetch::<Map>();
|
let map = ecs.fetch::<Map>();
|
||||||
|
|
||||||
let entities = ecs.entities();
|
let entities = ecs.entities();
|
||||||
|
|
@ -304,7 +307,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool {
|
||||||
ppos.x = pos.x;
|
ppos.x = pos.x;
|
||||||
ppos.y = pos.y;
|
ppos.y = pos.y;
|
||||||
} else {
|
} else {
|
||||||
let target = combat_stats.get(*potential_target);
|
let target = pools.get(*potential_target);
|
||||||
if let Some(_target) = target {
|
if let Some(_target) = target {
|
||||||
wants_to_melee
|
wants_to_melee
|
||||||
.insert(entity, WantsToMelee { target: *potential_target })
|
.insert(entity, WantsToMelee { target: *potential_target })
|
||||||
|
|
@ -538,12 +541,12 @@ fn skip_turn(ecs: &mut World) -> bool {
|
||||||
|
|
||||||
let mut did_heal = false;
|
let mut did_heal = false;
|
||||||
if can_heal {
|
if can_heal {
|
||||||
let mut health_components = ecs.write_storage::<CombatStats>();
|
let mut health_components = ecs.write_storage::<Pools>();
|
||||||
let player_hp = health_components.get_mut(*player_entity).unwrap();
|
let pools = health_components.get_mut(*player_entity).unwrap();
|
||||||
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
||||||
let roll = rng.roll_dice(1, 6);
|
let roll = rng.roll_dice(1, 6);
|
||||||
if (roll == 6) && player_hp.hp < player_hp.max_hp {
|
if (roll == 6) && pools.hit_points.current < pools.hit_points.max {
|
||||||
player_hp.hp += 1;
|
pools.hit_points.current += 1;
|
||||||
did_heal = true;
|
did_heal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use super::Renderable;
|
use super::Renderable;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Mob {
|
pub struct Mob {
|
||||||
|
|
@ -7,16 +8,28 @@ pub struct Mob {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub renderable: Option<Renderable>,
|
pub renderable: Option<Renderable>,
|
||||||
pub flags: Option<Vec<String>>,
|
pub flags: Option<Vec<String>>,
|
||||||
pub stats: MobStats,
|
pub level: Option<i32>,
|
||||||
|
pub bac: Option<i32>,
|
||||||
|
pub attacks: Option<Vec<NaturalAttack>>,
|
||||||
|
pub attributes: Option<MobAttributes>,
|
||||||
|
pub skills: Option<HashMap<String, i32>>,
|
||||||
pub vision_range: i32,
|
pub vision_range: i32,
|
||||||
pub ai: String,
|
|
||||||
pub quips: Option<Vec<String>>,
|
pub quips: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct MobStats {
|
pub struct MobAttributes {
|
||||||
pub max_hp: i32,
|
pub str: Option<i32>,
|
||||||
pub hp: i32,
|
pub dex: Option<i32>,
|
||||||
pub power: i32,
|
pub con: Option<i32>,
|
||||||
pub defence: i32,
|
pub int: Option<i32>,
|
||||||
|
pub wis: Option<i32>,
|
||||||
|
pub cha: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct NaturalAttack {
|
||||||
|
pub name: String,
|
||||||
|
pub hit_bonus: i32,
|
||||||
|
pub damage: String,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,16 @@
|
||||||
use super::Raws;
|
use super::Raws;
|
||||||
use crate::components::*;
|
use crate::components::*;
|
||||||
|
use crate::gamesystem::*;
|
||||||
use crate::random_table::RandomTable;
|
use crate::random_table::RandomTable;
|
||||||
|
use regex::Regex;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
use specs::saveload::{MarkedBuilder, SimpleMarker};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
pub enum SpawnType {
|
pub enum SpawnType {
|
||||||
AtPosition { x: i32, y: i32 },
|
AtPosition { x: i32, y: i32 },
|
||||||
|
Equipped { by: Entity },
|
||||||
|
Carried { by: Entity },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RawMaster {
|
pub struct RawMaster {
|
||||||
|
|
@ -67,36 +72,32 @@ impl RawMaster {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_named_entity(
|
pub fn spawn_named_entity(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||||
raws: &RawMaster,
|
|
||||||
new_entity: EntityBuilder,
|
|
||||||
key: &str,
|
|
||||||
pos: SpawnType,
|
|
||||||
rng: &mut rltk::RandomNumberGenerator,
|
|
||||||
) -> Option<Entity> {
|
|
||||||
if raws.item_index.contains_key(key) {
|
if raws.item_index.contains_key(key) {
|
||||||
return spawn_named_item(raws, new_entity, key, pos);
|
return spawn_named_item(raws, ecs, key, pos);
|
||||||
} else if raws.mob_index.contains_key(key) {
|
} else if raws.mob_index.contains_key(key) {
|
||||||
return spawn_named_mob(raws, new_entity, key, pos, rng);
|
return spawn_named_mob(raws, ecs, key, pos);
|
||||||
} else if raws.prop_index.contains_key(key) {
|
} else if raws.prop_index.contains_key(key) {
|
||||||
return spawn_named_prop(raws, new_entity, key, pos);
|
return spawn_named_prop(raws, ecs, key, pos);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_named_item(raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType) -> Option<Entity> {
|
pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||||
if raws.item_index.contains_key(key) {
|
if raws.item_index.contains_key(key) {
|
||||||
let item_template = &raws.raws.items[raws.item_index[key]];
|
let item_template = &raws.raws.items[raws.item_index[key]];
|
||||||
let mut eb = new_entity;
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
|
|
||||||
eb = eb.with(Name { name: item_template.name.name.clone(), plural: item_template.name.plural.clone() });
|
eb = eb.with(Name { name: item_template.name.name.clone(), plural: item_template.name.plural.clone() });
|
||||||
eb = eb.with(Item {});
|
eb = eb.with(Item {});
|
||||||
eb = spawn_position(pos, eb);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
|
||||||
if let Some(renderable) = &item_template.renderable {
|
if let Some(renderable) = &item_template.renderable {
|
||||||
eb = eb.with(get_renderable_component(renderable));
|
eb = eb.with(get_renderable_component(renderable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut weapon_type = -1;
|
||||||
|
|
||||||
if let Some(flags) = &item_template.flags {
|
if let Some(flags) = &item_template.flags {
|
||||||
for flag in flags.iter() {
|
for flag in flags.iter() {
|
||||||
match flag.as_str() {
|
match flag.as_str() {
|
||||||
|
|
@ -105,13 +106,25 @@ pub fn spawn_named_item(raws: &RawMaster, new_entity: EntityBuilder, key: &str,
|
||||||
"CURSED" => eb = eb.with(Cursed {}),
|
"CURSED" => eb = eb.with(Cursed {}),
|
||||||
"EQUIP_MELEE" => eb = eb.with(Equippable { slot: EquipmentSlot::Melee }),
|
"EQUIP_MELEE" => eb = eb.with(Equippable { slot: EquipmentSlot::Melee }),
|
||||||
"EQUIP_SHIELD" => eb = eb.with(Equippable { slot: EquipmentSlot::Shield }),
|
"EQUIP_SHIELD" => eb = eb.with(Equippable { slot: EquipmentSlot::Shield }),
|
||||||
|
"EQUIP_HEAD" => eb = eb.with(Equippable { slot: EquipmentSlot::Head }),
|
||||||
|
"EQUIP_TORSO" => eb = eb.with(Equippable { slot: EquipmentSlot::Torso }),
|
||||||
|
"EQUIP_LEGS" => eb = eb.with(Equippable { slot: EquipmentSlot::Legs }),
|
||||||
|
"EQUIP_FEET" => eb = eb.with(Equippable { slot: EquipmentSlot::Feet }),
|
||||||
|
"EQUIP_HANDS" => eb = eb.with(Equippable { slot: EquipmentSlot::Hands }),
|
||||||
|
"EQUIP_NECK" => eb = eb.with(Equippable { slot: EquipmentSlot::Neck }),
|
||||||
"WAND" => eb = eb.with(Wand { uses: 3, max_uses: 3 }),
|
"WAND" => eb = eb.with(Wand { uses: 3, max_uses: 3 }),
|
||||||
"FOOD" => eb = eb.with(ProvidesNutrition {}),
|
"FOOD" => eb = eb.with(ProvidesNutrition {}),
|
||||||
|
"STRENGTH" => weapon_type = 0,
|
||||||
|
"DEXTERITY" => weapon_type = 2,
|
||||||
|
"FINESSE" => weapon_type = 3,
|
||||||
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut base_damage = "1d4";
|
||||||
|
let mut hit_bonus = 0;
|
||||||
|
|
||||||
if let Some(effects_list) = &item_template.effects {
|
if let Some(effects_list) = &item_template.effects {
|
||||||
for effect in effects_list.iter() {
|
for effect in effects_list.iter() {
|
||||||
let effect_name = effect.0.as_str();
|
let effect_name = effect.0.as_str();
|
||||||
|
|
@ -121,8 +134,9 @@ pub fn spawn_named_item(raws: &RawMaster, new_entity: EntityBuilder, key: &str,
|
||||||
"damage" => eb = eb.with(InflictsDamage { amount: effect.1.parse::<i32>().unwrap() }),
|
"damage" => eb = eb.with(InflictsDamage { amount: effect.1.parse::<i32>().unwrap() }),
|
||||||
"aoe" => eb = eb.with(AOE { radius: effect.1.parse::<i32>().unwrap() }),
|
"aoe" => eb = eb.with(AOE { radius: effect.1.parse::<i32>().unwrap() }),
|
||||||
"confusion" => eb = eb.with(Confusion { turns: effect.1.parse::<i32>().unwrap() }),
|
"confusion" => eb = eb.with(Confusion { turns: effect.1.parse::<i32>().unwrap() }),
|
||||||
"melee_power_bonus" => eb = eb.with(MeleePowerBonus { amount: effect.1.parse::<i32>().unwrap() }),
|
"base_damage" => base_damage = effect.1,
|
||||||
"defence_bonus" => eb = eb.with(DefenceBonus { amount: effect.1.parse::<i32>().unwrap() }),
|
"hit_bonus" => hit_bonus = effect.1.parse::<i32>().unwrap(),
|
||||||
|
"ac" => eb = eb.with(ArmourClassBonus { amount: effect.1.parse::<i32>().unwrap() }),
|
||||||
"magicmapper" => eb = eb.with(MagicMapper {}),
|
"magicmapper" => eb = eb.with(MagicMapper {}),
|
||||||
"digger" => eb = eb.with(Digger {}),
|
"digger" => eb = eb.with(Digger {}),
|
||||||
_ => rltk::console::log(format!("Warning: effect {} not implemented.", effect_name)),
|
_ => rltk::console::log(format!("Warning: effect {} not implemented.", effect_name)),
|
||||||
|
|
@ -130,77 +144,158 @@ pub fn spawn_named_item(raws: &RawMaster, new_entity: EntityBuilder, key: &str,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if weapon_type != -1 {
|
||||||
|
let (n_dice, die_type, bonus) = parse_dice_string(base_damage);
|
||||||
|
let mut wpn = MeleeWeapon {
|
||||||
|
attribute: WeaponAttribute::Strength,
|
||||||
|
damage_n_dice: n_dice,
|
||||||
|
damage_die_type: die_type,
|
||||||
|
damage_bonus: bonus,
|
||||||
|
hit_bonus: hit_bonus,
|
||||||
|
};
|
||||||
|
match weapon_type {
|
||||||
|
0 => wpn.attribute = WeaponAttribute::Strength,
|
||||||
|
1 => wpn.attribute = WeaponAttribute::Dexterity,
|
||||||
|
_ => wpn.attribute = WeaponAttribute::Finesse,
|
||||||
|
}
|
||||||
|
eb = eb.with(wpn);
|
||||||
|
}
|
||||||
|
|
||||||
return Some(eb.build());
|
return Some(eb.build());
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_named_mob(
|
pub fn spawn_named_mob(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||||
raws: &RawMaster,
|
|
||||||
new_entity: EntityBuilder,
|
|
||||||
key: &str,
|
|
||||||
pos: SpawnType,
|
|
||||||
rng: &mut rltk::RandomNumberGenerator,
|
|
||||||
) -> Option<Entity> {
|
|
||||||
if raws.mob_index.contains_key(key) {
|
if raws.mob_index.contains_key(key) {
|
||||||
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
||||||
|
|
||||||
|
let mut eb;
|
||||||
// New entity with a position, name, combatstats, and viewshed
|
// New entity with a position, name, combatstats, and viewshed
|
||||||
let mut eb = new_entity;
|
eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
eb = spawn_position(pos, eb);
|
|
||||||
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
|
|
||||||
match mob_template.ai.as_ref() {
|
|
||||||
"bystander" => eb = eb.with(Bystander {}),
|
|
||||||
"melee" => eb = eb.with(Monster {}),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let rolled_hp = roll_hit_dice(rng, 1, mob_template.stats.max_hp);
|
|
||||||
eb = eb.with(CombatStats {
|
|
||||||
max_hp: rolled_hp,
|
|
||||||
hp: rolled_hp,
|
|
||||||
power: mob_template.stats.power,
|
|
||||||
defence: mob_template.stats.defence,
|
|
||||||
});
|
|
||||||
eb = eb.with(Viewshed { visible_tiles: Vec::new(), range: mob_template.vision_range, dirty: true });
|
|
||||||
|
|
||||||
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
|
eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() });
|
||||||
|
eb = eb.with(Viewshed { visible_tiles: Vec::new(), range: mob_template.vision_range, dirty: true });
|
||||||
if let Some(renderable) = &mob_template.renderable {
|
if let Some(renderable) = &mob_template.renderable {
|
||||||
eb = eb.with(get_renderable_component(renderable));
|
eb = eb.with(get_renderable_component(renderable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(flags) = &mob_template.flags {
|
if let Some(flags) = &mob_template.flags {
|
||||||
for flag in flags.iter() {
|
for flag in flags.iter() {
|
||||||
match flag.as_str() {
|
match flag.as_str() {
|
||||||
"BLOCKS_TILE" => eb = eb.with(BlocksTile {}),
|
"BLOCKS_TILE" => eb = eb.with(BlocksTile {}),
|
||||||
|
"BYSTANDER" => eb = eb.with(Bystander {}),
|
||||||
|
"MONSTER" => eb = eb.with(Monster {}),
|
||||||
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(quips) = &mob_template.quips {
|
if let Some(quips) = &mob_template.quips {
|
||||||
eb = eb.with(Quips { available: quips.clone() });
|
eb = eb.with(Quips { available: quips.clone() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup combat stats
|
||||||
|
let mut attr = Attributes {
|
||||||
|
strength: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
dexterity: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
constitution: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
intelligence: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
wisdom: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
charisma: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
||||||
|
};
|
||||||
|
let mut mob_con = 10;
|
||||||
|
let mut mob_int = 10;
|
||||||
|
if let Some(attributes) = &mob_template.attributes {
|
||||||
|
if let Some(str) = attributes.str {
|
||||||
|
attr.strength = Attribute { base: str, modifiers: 0, bonus: attr_bonus(str) };
|
||||||
|
}
|
||||||
|
if let Some(dex) = attributes.dex {
|
||||||
|
attr.strength = Attribute { base: dex, modifiers: 0, bonus: attr_bonus(dex) };
|
||||||
|
}
|
||||||
|
if let Some(con) = attributes.con {
|
||||||
|
attr.constitution = Attribute { base: con, modifiers: 0, bonus: attr_bonus(con) };
|
||||||
|
mob_con = con;
|
||||||
|
}
|
||||||
|
if let Some(int) = attributes.int {
|
||||||
|
attr.intelligence = Attribute { base: int, modifiers: 0, bonus: attr_bonus(int) };
|
||||||
|
mob_int = int;
|
||||||
|
}
|
||||||
|
if let Some(wis) = attributes.wis {
|
||||||
|
attr.wisdom = Attribute { base: wis, modifiers: 0, bonus: attr_bonus(wis) };
|
||||||
|
}
|
||||||
|
if let Some(cha) = attributes.cha {
|
||||||
|
attr.charisma = Attribute { base: cha, modifiers: 0, bonus: attr_bonus(cha) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eb = eb.with(attr);
|
||||||
|
let mob_level = if mob_template.level.is_some() { mob_template.level.unwrap() } else { 1 };
|
||||||
|
|
||||||
|
// Should really use existing RNG here
|
||||||
|
let mut rng = rltk::RandomNumberGenerator::new();
|
||||||
|
|
||||||
|
let mob_hp = npc_hp(&mut rng, mob_con, mob_level);
|
||||||
|
let mob_mana = mana_at_level(&mut rng, mob_int, mob_level);
|
||||||
|
let mob_bac = if mob_template.bac.is_some() { mob_template.bac.unwrap() } else { 10 };
|
||||||
|
|
||||||
|
let pools = Pools {
|
||||||
|
level: mob_level,
|
||||||
|
xp: 0,
|
||||||
|
bac: mob_bac,
|
||||||
|
hit_points: Pool { current: mob_hp, max: mob_hp },
|
||||||
|
mana: Pool { current: mob_mana, max: mob_mana },
|
||||||
|
};
|
||||||
|
eb = eb.with(pools);
|
||||||
|
|
||||||
|
let mut skills = Skills { skills: HashMap::new() };
|
||||||
|
skills.skills.insert(Skill::Melee, 0);
|
||||||
|
skills.skills.insert(Skill::Defence, 0);
|
||||||
|
if let Some(mobskills) = &mob_template.skills {
|
||||||
|
for sk in mobskills.iter() {
|
||||||
|
match sk.0.as_str() {
|
||||||
|
"melee" => {
|
||||||
|
skills.skills.insert(Skill::Melee, *sk.1);
|
||||||
|
}
|
||||||
|
"defence" => {
|
||||||
|
skills.skills.insert(Skill::Defence, *sk.1);
|
||||||
|
}
|
||||||
|
"magic" => {
|
||||||
|
skills.skills.insert(Skill::Magic, *sk.1);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
rltk::console::log(format!("Unknown skill referenced: [{}]", sk.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eb = eb.with(skills);
|
||||||
|
|
||||||
|
if let Some(natural_attacks) = &mob_template.attacks {
|
||||||
|
let mut natural = NaturalAttacks { attacks: Vec::new() };
|
||||||
|
for na in natural_attacks.iter() {
|
||||||
|
let (n, d, b) = parse_dice_string(&na.damage);
|
||||||
|
let attack = NaturalAttack {
|
||||||
|
name: na.name.clone(),
|
||||||
|
hit_bonus: na.hit_bonus,
|
||||||
|
damage_n_dice: n,
|
||||||
|
damage_die_type: d,
|
||||||
|
damage_bonus: b,
|
||||||
|
};
|
||||||
|
natural.attacks.push(attack);
|
||||||
|
}
|
||||||
|
eb = eb.with(natural);
|
||||||
|
}
|
||||||
|
|
||||||
return Some(eb.build());
|
return Some(eb.build());
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn roll_hit_dice(rng: &mut rltk::RandomNumberGenerator, n: i32, d: i32) -> i32 {
|
pub fn spawn_named_prop(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||||
let mut rolled_hp: i32 = 0;
|
|
||||||
|
|
||||||
for _i in 0..n {
|
|
||||||
rolled_hp += rng.roll_dice(1, d);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rolled_hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_named_prop(raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType) -> Option<Entity> {
|
|
||||||
if raws.prop_index.contains_key(key) {
|
if raws.prop_index.contains_key(key) {
|
||||||
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
||||||
|
|
||||||
let mut eb = new_entity;
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
||||||
eb = spawn_position(pos, eb);
|
eb = spawn_position(pos, eb, key, raws);
|
||||||
if let Some(renderable) = &prop_template.renderable {
|
if let Some(renderable) = &prop_template.renderable {
|
||||||
eb = eb.with(get_renderable_component(renderable));
|
eb = eb.with(get_renderable_component(renderable));
|
||||||
}
|
}
|
||||||
|
|
@ -237,12 +332,15 @@ pub fn spawn_named_prop(raws: &RawMaster, new_entity: EntityBuilder, key: &str,
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_position(pos: SpawnType, new_entity: EntityBuilder) -> EntityBuilder {
|
fn spawn_position<'a>(pos: SpawnType, new_entity: EntityBuilder<'a>, tag: &str, raws: &RawMaster) -> EntityBuilder<'a> {
|
||||||
let mut eb = new_entity;
|
let mut eb = new_entity;
|
||||||
|
|
||||||
match pos {
|
match pos {
|
||||||
SpawnType::AtPosition { x, y } => {
|
SpawnType::AtPosition { x, y } => eb = eb.with(Position { x, y }),
|
||||||
eb = eb.with(Position { x, y });
|
SpawnType::Carried { by } => eb = eb.with(InBackpack { owner: by }),
|
||||||
|
SpawnType::Equipped { by } => {
|
||||||
|
let slot = find_slot_for_equippable_item(tag, raws);
|
||||||
|
eb = eb.with(Equipped { owner: by, slot })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,3 +381,42 @@ pub fn table_by_name(raws: &RawMaster, key: &str, difficulty: i32) -> RandomTabl
|
||||||
return RandomTable::new().add("debug", 1);
|
return RandomTable::new().add("debug", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) {
|
||||||
|
lazy_static! {
|
||||||
|
static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap();
|
||||||
|
}
|
||||||
|
let mut n_dice = 1;
|
||||||
|
let mut die_type = 4;
|
||||||
|
let mut die_bonus = 0;
|
||||||
|
for cap in DICE_RE.captures_iter(dice) {
|
||||||
|
if let Some(group) = cap.get(1) {
|
||||||
|
n_dice = group.as_str().parse::<i32>().expect("Not a digit");
|
||||||
|
}
|
||||||
|
if let Some(group) = cap.get(2) {
|
||||||
|
die_type = group.as_str().parse::<i32>().expect("Not a digit");
|
||||||
|
}
|
||||||
|
if let Some(group) = cap.get(3) {
|
||||||
|
die_bonus = group.as_str().parse::<i32>().expect("Not a digit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(n_dice, die_type, die_bonus)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
||||||
|
if !raws.item_index.contains_key(tag) {
|
||||||
|
panic!("Trying to equip an unknown item: {}", tag);
|
||||||
|
}
|
||||||
|
let item_index = raws.item_index[tag];
|
||||||
|
let item = &raws.raws.items[item_index];
|
||||||
|
if let Some(flags) = &item.flags {
|
||||||
|
for flag in flags {
|
||||||
|
match flag.as_str() {
|
||||||
|
"EQUIP_MELEE" => return EquipmentSlot::Melee,
|
||||||
|
"EQUIP_SHIELD" => return EquipmentSlot::Shield,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("Trying to equip {}, but it has no slot tag.", tag);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,15 +48,14 @@ pub fn save_game(ecs: &mut World) {
|
||||||
serializer,
|
serializer,
|
||||||
data,
|
data,
|
||||||
AOE,
|
AOE,
|
||||||
|
ArmourClassBonus,
|
||||||
Attributes,
|
Attributes,
|
||||||
BlocksTile,
|
BlocksTile,
|
||||||
BlocksVisibility,
|
BlocksVisibility,
|
||||||
Bystander,
|
Bystander,
|
||||||
CombatStats,
|
|
||||||
Confusion,
|
Confusion,
|
||||||
Consumable,
|
Consumable,
|
||||||
Cursed,
|
Cursed,
|
||||||
DefenceBonus,
|
|
||||||
Destructible,
|
Destructible,
|
||||||
Digger,
|
Digger,
|
||||||
Door,
|
Door,
|
||||||
|
|
@ -70,13 +69,15 @@ pub fn save_game(ecs: &mut World) {
|
||||||
InflictsDamage,
|
InflictsDamage,
|
||||||
Item,
|
Item,
|
||||||
MagicMapper,
|
MagicMapper,
|
||||||
MeleePowerBonus,
|
MeleeWeapon,
|
||||||
Mind,
|
Mind,
|
||||||
Monster,
|
Monster,
|
||||||
|
NaturalAttacks,
|
||||||
Name,
|
Name,
|
||||||
ParticleLifetime,
|
ParticleLifetime,
|
||||||
Player,
|
Player,
|
||||||
Position,
|
Position,
|
||||||
|
Pools,
|
||||||
Prop,
|
Prop,
|
||||||
ProvidesHealing,
|
ProvidesHealing,
|
||||||
ProvidesNutrition,
|
ProvidesNutrition,
|
||||||
|
|
@ -84,6 +85,7 @@ pub fn save_game(ecs: &mut World) {
|
||||||
Ranged,
|
Ranged,
|
||||||
Renderable,
|
Renderable,
|
||||||
SingleActivation,
|
SingleActivation,
|
||||||
|
Skills,
|
||||||
SufferDamage,
|
SufferDamage,
|
||||||
Telepath,
|
Telepath,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
|
|
@ -147,15 +149,14 @@ pub fn load_game(ecs: &mut World) {
|
||||||
de,
|
de,
|
||||||
d,
|
d,
|
||||||
AOE,
|
AOE,
|
||||||
|
ArmourClassBonus,
|
||||||
Attributes,
|
Attributes,
|
||||||
BlocksTile,
|
BlocksTile,
|
||||||
BlocksVisibility,
|
BlocksVisibility,
|
||||||
Bystander,
|
Bystander,
|
||||||
CombatStats,
|
|
||||||
Confusion,
|
Confusion,
|
||||||
Consumable,
|
Consumable,
|
||||||
Cursed,
|
Cursed,
|
||||||
DefenceBonus,
|
|
||||||
Destructible,
|
Destructible,
|
||||||
Digger,
|
Digger,
|
||||||
Door,
|
Door,
|
||||||
|
|
@ -169,12 +170,14 @@ pub fn load_game(ecs: &mut World) {
|
||||||
InflictsDamage,
|
InflictsDamage,
|
||||||
Item,
|
Item,
|
||||||
MagicMapper,
|
MagicMapper,
|
||||||
MeleePowerBonus,
|
MeleeWeapon,
|
||||||
Mind,
|
Mind,
|
||||||
Monster,
|
Monster,
|
||||||
|
NaturalAttacks,
|
||||||
Name,
|
Name,
|
||||||
ParticleLifetime,
|
ParticleLifetime,
|
||||||
Player,
|
Player,
|
||||||
|
Pools,
|
||||||
Position,
|
Position,
|
||||||
Prop,
|
Prop,
|
||||||
ProvidesHealing,
|
ProvidesHealing,
|
||||||
|
|
@ -183,6 +186,7 @@ pub fn load_game(ecs: &mut World) {
|
||||||
Ranged,
|
Ranged,
|
||||||
Renderable,
|
Renderable,
|
||||||
SingleActivation,
|
SingleActivation,
|
||||||
|
Skills,
|
||||||
SufferDamage,
|
SufferDamage,
|
||||||
Telepath,
|
Telepath,
|
||||||
Viewshed,
|
Viewshed,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
random_table::RandomTable, raws, Attribute, Attributes, CombatStats, HungerClock, HungerState, Map, Name, Player,
|
gamesystem, gamesystem::attr_bonus, random_table::RandomTable, raws, Attribute, Attributes, HungerClock,
|
||||||
Position, Rect, Renderable, SerializeMe, TileType, Viewshed,
|
HungerState, Map, Name, Player, Pool, Pools, Position, Rect, Renderable, SerializeMe, Skill, Skills, TileType,
|
||||||
|
Viewshed,
|
||||||
};
|
};
|
||||||
use rltk::{RandomNumberGenerator, RGB};
|
use rltk::{RandomNumberGenerator, RGB};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
@ -9,8 +10,23 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
/// Spawns the player and returns his/her entity object.
|
/// Spawns the player and returns his/her entity object.
|
||||||
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
|
let mut skills = Skills { skills: HashMap::new() };
|
||||||
|
skills.skills.insert(Skill::Melee, 0);
|
||||||
|
skills.skills.insert(Skill::Defence, 0);
|
||||||
|
skills.skills.insert(Skill::Magic, 0);
|
||||||
|
|
||||||
|
let mut rng = ecs.write_resource::<rltk::RandomNumberGenerator>();
|
||||||
|
let str = gamesystem::roll_4d6(&mut rng);
|
||||||
|
let dex = gamesystem::roll_4d6(&mut rng);
|
||||||
|
let con = gamesystem::roll_4d6(&mut rng);
|
||||||
|
let int = gamesystem::roll_4d6(&mut rng);
|
||||||
|
let wis = gamesystem::roll_4d6(&mut rng);
|
||||||
|
let cha = gamesystem::roll_4d6(&mut rng);
|
||||||
|
std::mem::drop(rng);
|
||||||
|
|
||||||
// d8 hit die - but always maxxed at level 1, so player doesn't have to roll.
|
// d8 hit die - but always maxxed at level 1, so player doesn't have to roll.
|
||||||
ecs.create_entity()
|
let player = ecs
|
||||||
|
.create_entity()
|
||||||
.with(Position { x: player_x, y: player_y })
|
.with(Position { x: player_x, y: player_y })
|
||||||
.with(Renderable {
|
.with(Renderable {
|
||||||
glyph: rltk::to_cp437('@'),
|
glyph: rltk::to_cp437('@'),
|
||||||
|
|
@ -21,22 +37,39 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
.with(Player {})
|
.with(Player {})
|
||||||
.with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true })
|
.with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true })
|
||||||
.with(Name { name: "you".to_string(), plural: "you".to_string() })
|
.with(Name { name: "you".to_string(), plural: "you".to_string() })
|
||||||
.with(CombatStats { max_hp: 12, hp: 12, defence: 0, power: 4 })
|
|
||||||
.with(HungerClock { state: HungerState::Satiated, duration: 50 })
|
.with(HungerClock { state: HungerState::Satiated, duration: 50 })
|
||||||
.with(Attributes {
|
.with(Attributes {
|
||||||
strength: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
strength: Attribute { base: str, modifiers: 0, bonus: attr_bonus(str) },
|
||||||
dexterity: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
dexterity: Attribute { base: dex, modifiers: 0, bonus: attr_bonus(dex) },
|
||||||
constitution: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
constitution: Attribute { base: con, modifiers: 0, bonus: attr_bonus(con) },
|
||||||
intelligence: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
intelligence: Attribute { base: int, modifiers: 0, bonus: attr_bonus(int) },
|
||||||
wisdom: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
wisdom: Attribute { base: wis, modifiers: 0, bonus: attr_bonus(wis) },
|
||||||
charisma: Attribute { base: 10, modifiers: 0, bonus: 0 },
|
charisma: Attribute { base: cha, modifiers: 0, bonus: attr_bonus(cha) },
|
||||||
})
|
})
|
||||||
|
.with(Pools {
|
||||||
|
hit_points: Pool { current: 10 + attr_bonus(con), max: 10 + attr_bonus(con) },
|
||||||
|
mana: Pool { current: 2 + attr_bonus(int), max: 2 + attr_bonus(int) },
|
||||||
|
xp: 0,
|
||||||
|
level: 1,
|
||||||
|
bac: 10,
|
||||||
|
})
|
||||||
|
.with(skills)
|
||||||
.marked::<SimpleMarker<SerializeMe>>()
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
.build()
|
.build();
|
||||||
|
|
||||||
|
raws::spawn_named_entity(
|
||||||
|
&raws::RAWS.lock().unwrap(),
|
||||||
|
ecs,
|
||||||
|
"equip_dagger",
|
||||||
|
raws::SpawnType::Equipped { by: player },
|
||||||
|
);
|
||||||
|
raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, "food_apple", raws::SpawnType::Carried { by: player });
|
||||||
|
|
||||||
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consts
|
// Consts
|
||||||
const MAX_ENTITIES: i32 = 3;
|
const MAX_ENTITIES: i32 = 2;
|
||||||
|
|
||||||
/// Fills a room with stuff!
|
/// Fills a room with stuff!
|
||||||
pub fn spawn_room(map: &Map, rng: &mut RandomNumberGenerator, room: &Rect, spawn_list: &mut Vec<(usize, String)>) {
|
pub fn spawn_room(map: &Map, rng: &mut RandomNumberGenerator, room: &Rect, spawn_list: &mut Vec<(usize, String)>) {
|
||||||
|
|
@ -61,6 +94,18 @@ pub fn spawn_region(map: &Map, rng: &mut RandomNumberGenerator, area: &[usize],
|
||||||
let mut areas: Vec<usize> = Vec::from(area);
|
let mut areas: Vec<usize> = Vec::from(area);
|
||||||
let difficulty = map.difficulty;
|
let difficulty = map.difficulty;
|
||||||
|
|
||||||
|
if areas.len() == 0 {
|
||||||
|
rltk::console::log("DEBUGINFO: No areas capable of spawning mobs!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if rng.roll_dice(1, 3) == 1 {
|
||||||
|
let array_idx = if areas.len() == 1 { 0usize } else { (rng.roll_dice(1, areas.len() as i32) - 1) as usize };
|
||||||
|
let map_idx = areas[array_idx];
|
||||||
|
spawn_points.insert(map_idx, mob_table(difficulty).roll(rng));
|
||||||
|
areas.remove(array_idx);
|
||||||
|
}
|
||||||
|
|
||||||
let num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_ENTITIES + 2) - 2);
|
let num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_ENTITIES + 2) - 2);
|
||||||
if num_spawns <= 0 {
|
if num_spawns <= 0 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -70,7 +115,6 @@ pub fn spawn_region(map: &Map, rng: &mut RandomNumberGenerator, area: &[usize],
|
||||||
let category = category_table().roll(rng);
|
let category = category_table().roll(rng);
|
||||||
let spawn_table;
|
let spawn_table;
|
||||||
match category.as_ref() {
|
match category.as_ref() {
|
||||||
"mob" => spawn_table = mob_table(difficulty),
|
|
||||||
"item" => {
|
"item" => {
|
||||||
let item_category = item_category_table().roll(rng);
|
let item_category = item_category_table().roll(rng);
|
||||||
match item_category.as_ref() {
|
match item_category.as_ref() {
|
||||||
|
|
@ -104,13 +148,8 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||||
let y = (*spawn.0 / width) as i32;
|
let y = (*spawn.0 / width) as i32;
|
||||||
std::mem::drop(map);
|
std::mem::drop(map);
|
||||||
|
|
||||||
let spawn_result = raws::spawn_named_entity(
|
let spawn_result =
|
||||||
&raws::RAWS.lock().unwrap(),
|
raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, &spawn.1, raws::SpawnType::AtPosition { x, y });
|
||||||
ecs.create_entity(),
|
|
||||||
&spawn.1,
|
|
||||||
raws::SpawnType::AtPosition { x, y },
|
|
||||||
&mut rltk::RandomNumberGenerator::new(),
|
|
||||||
);
|
|
||||||
if spawn_result.is_some() {
|
if spawn_result.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -118,9 +157,9 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||||
rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", spawn.1));
|
rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", spawn.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12 mobs : 6 items : 2 food : 1 trap
|
// 3 items : 1 food : 1 trap
|
||||||
fn category_table() -> RandomTable {
|
fn category_table() -> RandomTable {
|
||||||
return RandomTable::new().add("mob", 12).add("item", 6).add("food", 2).add("trap", 1);
|
return RandomTable::new().add("item", 3).add("food", 1).add("trap", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3 scrolls : 3 potions : 1 equipment : 1 wand?
|
// 3 scrolls : 3 potions : 1 equipment : 1 wand?
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue