From b097d0f066420b1f6bfc9c1674781d84044c9b9a Mon Sep 17 00:00:00 2001 From: Vollch Date: Wed, 13 Dec 2023 16:12:29 +0300 Subject: [PATCH] refactor(port): hacksaw migrated to activity actor and jsonified (#3880) * Jsonize hacksaw activity (#50155) Co-authored-by: Saicchi Co-authored-by: Kevin Granade * Make it work * style(autofix.ci): automated formatting --------- Co-authored-by: Saicchi <47158232+Saicchi@users.noreply.github.com> Co-authored-by: Saicchi Co-authored-by: Kevin Granade Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../furniture-storage.json | 5 + .../furniture_and_terrain/terrain-doors.json | 12 + .../terrain-fences-gates.json | 24 ++ .../furniture_and_terrain/terrain-walls.json | 12 + .../terrain-windows.json | 66 ++++ data/mods/TEST_DATA/furniture.json | 33 ++ data/mods/TEST_DATA/items.json | 35 ++- data/mods/TEST_DATA/terrain.json | 26 ++ .../docs/en/mod/json/reference/json_info.md | 121 +++++++- lang/extract_json_strings.py | 10 + src/activity_actor.cpp | 151 +++++++++ src/activity_actor_definitions.h | 31 ++ src/activity_handlers.cpp | 74 ----- src/activity_handlers.h | 2 - src/iuse.cpp | 60 +--- src/mapdata.cpp | 10 + src/mapdata.h | 2 + tests/player_activities_test.cpp | 289 +++++++++++++++++- 18 files changed, 829 insertions(+), 134 deletions(-) diff --git a/data/json/furniture_and_terrain/furniture-storage.json b/data/json/furniture_and_terrain/furniture-storage.json index f2c203bb346d..ba95cb8f7066 100644 --- a/data/json/furniture_and_terrain/furniture-storage.json +++ b/data/json/furniture_and_terrain/furniture-storage.json @@ -522,6 +522,11 @@ "move_cost_mod": -1, "coverage": 70, "required_str": 8, + "hacksaw": { + "duration": "2 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 3 ] }, { "item": "steel_chunk", "count": 1 } ] + }, "flags": [ "TRANSPARENT", "FLAMMABLE_HARD", "PLACE_ITEM", "BLOCKSDOOR", "MOUNTABLE" ], "oxytorch": { "duration": "1 seconds", "byproducts": [ { "item": "steel_chunk", "count": [ 2, 6 ] } ] }, "deconstruct": { "items": [ { "item": "pipe", "count": 12 }, { "item": "sheet_metal", "count": 2 } ] }, diff --git a/data/json/furniture_and_terrain/terrain-doors.json b/data/json/furniture_and_terrain/terrain-doors.json index 38e42147ea04..2baa484ac58e 100644 --- a/data/json/furniture_and_terrain/terrain-doors.json +++ b/data/json/furniture_and_terrain/terrain-doors.json @@ -2296,6 +2296,12 @@ "move_cost": 0, "roof": "t_flat_roof", "flags": [ "TRANSPARENT", "NOITEM", "PERMEABLE", "CONNECT_TO_WALL", "THIN_OBSTACLE" ], + "hacksaw": { + "result": "t_mdoor_frame", + "duration": "15 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 12 } ] + }, "open": "t_door_bar_o", "close": "t_door_bar_locked", "oxytorch": { @@ -2358,6 +2364,12 @@ "roof": "t_flat_roof", "lockpick_result": "t_door_bar_o", "lockpick_message": "The door swings open…", + "hacksaw": { + "result": "t_mdoor_frame", + "duration": "15 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 12 } ] + }, "flags": [ "TRANSPARENT", "NOITEM", "PERMEABLE", "CONNECT_TO_WALL", "LOCKED", "THIN_OBSTACLE" ], "examine_action": "locked_object_pickable", "oxytorch": { diff --git a/data/json/furniture_and_terrain/terrain-fences-gates.json b/data/json/furniture_and_terrain/terrain-fences-gates.json index 0c1aba515658..0544a77856aa 100644 --- a/data/json/furniture_and_terrain/terrain-fences-gates.json +++ b/data/json/furniture_and_terrain/terrain-fences-gates.json @@ -80,6 +80,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 4 ] }, { "item": "wire", "count": [ 4, 16 ] } ] }, + "hacksaw": { + "result": "t_dirt", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 6 }, { "item": "steel_chunk", "count": 20 } ] + }, "bash": { "str_min": 10, "str_max": 150, @@ -107,6 +113,12 @@ "byproducts": [ { "item": "pipe", "count": [ 1, 4 ] }, { "item": "wire", "count": [ 4, 16 ] } ] }, "open": "t_chaingate_o", + "hacksaw": { + "result": "t_dirt", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 6 }, { "item": "steel_chunk", "count": 20 } ] + }, "bash": { "str_min": 10, "str_max": 150, @@ -404,6 +416,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 4 ] }, { "item": "wire", "count": [ 4, 16 ] } ] }, + "hacksaw": { + "result": "t_dirt", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 6 }, { "item": "steel_chunk", "count": 20 } ] + }, "bash": { "str_min": 10, "str_max": 150, @@ -421,6 +439,12 @@ "symbol": "#", "color": "cyan", "move_cost": 2, + "hacksaw": { + "result": "t_dirt", + "duration": "2 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 6 } ] + }, "flags": [ "TRANSPARENT", "THIN_OBSTACLE" ], "oxytorch": { "result": "t_dirt", "duration": "1 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 4 ] } ] }, "bash": { diff --git a/data/json/furniture_and_terrain/terrain-walls.json b/data/json/furniture_and_terrain/terrain-walls.json index 7bff6740c885..a878230bd786 100644 --- a/data/json/furniture_and_terrain/terrain-walls.json +++ b/data/json/furniture_and_terrain/terrain-walls.json @@ -554,6 +554,12 @@ "symbol": "#", "color": "dark_gray", "move_cost": 0, + "hacksaw": { + "result": "t_pit", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "spike", "count": 19 }, { "item": "scrap", "count": 8 } ] + }, "flags": [ "TRANSPARENT", "NOITEM", "PERMEABLE", "THIN_OBSTACLE" ], "connects_to": "WALL", "oxytorch": { @@ -1084,6 +1090,12 @@ "symbol": "\"", "color": "light_gray", "move_cost": 0, + "hacksaw": { + "result": "t_floor", + "duration": "15 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": 3 } ] + }, "flags": [ "TRANSPARENT", "NOITEM", "PERMEABLE", "CONNECT_TO_WALL", "THIN_OBSTACLE" ], "oxytorch": { "result": "t_floor", "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 2 ] } ] }, "bash": { diff --git a/data/json/furniture_and_terrain/terrain-windows.json b/data/json/furniture_and_terrain/terrain-windows.json index e0d9a7ccd949..07727f607d25 100644 --- a/data/json/furniture_and_terrain/terrain-windows.json +++ b/data/json/furniture_and_terrain/terrain-windows.json @@ -426,6 +426,12 @@ "move_cost": 0, "coverage": 95, "roof": "t_flat_roof", + "hacksaw": { + "result": "t_window_reinforced", + "duration": "5 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "rebar", "count": [ 1, 4 ] } ] + }, "flags": [ "NOITEM", "REDUCE_SCENT", "CONNECT_TO_WALL", "BLOCK_WIND" ], "oxytorch": { "result": "t_window_empty", @@ -458,6 +464,12 @@ "duration": "4 seconds", "byproducts": [ { "item": "steel_plate", "count": [ 0, 1 ] }, { "item": "sheet_metal", "count": [ 1, 3 ] } ] }, + "hacksaw": { + "result": "t_window_reinforced_noglass", + "duration": "5 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "rebar", "count": [ 1, 4 ] } ] + }, "bash": { "str_min": 18, "str_max": 40, @@ -502,6 +514,12 @@ "symbol": "#", "color": "light_gray", "move_cost": 0, + "hacksaw": { + "result": "t_window_alarm", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "rebar", "count": [ 1, 8 ] } ] + }, "delete": { "flags": [ "FLAMMABLE", "BARRICADABLE_WINDOW" ] }, "extend": { "flags": [ "ALARMED" ] }, "oxytorch": { "result": "t_window_alarm", "duration": "9 seconds", "byproducts": [ { "item": "rebar", "count": [ 1, 2 ] } ] }, @@ -517,6 +535,12 @@ "color": "light_gray", "move_cost": 0, "coverage": 95, + "hacksaw": { + "result": "t_window_domestic", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "rebar", "count": [ 1, 8 ] } ] + }, "roof": "t_flat_roof", "oxytorch": { "result": "t_window_domestic", "duration": "9 seconds", "byproducts": [ { "item": "rebar", "count": [ 1, 2 ] } ] }, "flags": [ @@ -556,6 +580,12 @@ "looks_like": "t_window_bars", "color": "light_gray", "move_cost": 0, + "hacksaw": { + "result": "t_window_domestic", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "rebar", "count": [ 1, 8 ] } ] + }, "roof": "t_flat_roof", "oxytorch": { "result": "t_window_domestic", "duration": "9 seconds", "byproducts": [ { "item": "rebar", "count": [ 1, 2 ] } ] }, "flags": [ @@ -658,6 +688,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] }, + "hacksaw": { + "result": "t_window_reinforced", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "flags": [ "TRANSPARENT", "NOITEM", @@ -701,6 +737,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] }, + "hacksaw": { + "result": "t_window_reinforced", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "flags": [ "NOITEM", "CONNECT_TO_WALL", "THIN_OBSTACLE", "BARRICADABLE_WINDOW_CURTAINS", "BLOCK_WIND", "REDUCE_SCENT", "WINDOW" ], "curtain_transform": "t_window_bars", "examine_action": "curtains", @@ -748,6 +790,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] }, + "hacksaw": { + "result": "t_window_reinforced", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "curtain_transform": "t_window_bars", "examine_action": "curtains", "close": "t_metal_grate_window_with_curtain", @@ -779,6 +827,12 @@ "color": "light_gray", "move_cost": 0, "roof": "t_flat_roof", + "hacksaw": { + "result": "t_window_reinforced_noglass", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "flags": [ "TRANSPARENT", "NOITEM", "CONNECT_TO_WALL", "BARRICADABLE_WINDOW", "THIN_OBSTACLE", "WINDOW" ], "oxytorch": { "result": "t_window_reinforced_noglass", @@ -813,6 +867,12 @@ "color": "light_gray", "move_cost": 0, "roof": "t_flat_roof", + "hacksaw": { + "result": "t_window_reinforced_noglass", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "flags": [ "NOITEM", "CONNECT_TO_WALL", "THIN_OBSTACLE", "BARRICADABLE_WINDOW_CURTAINS", "BLOCK_WIND", "REDUCE_SCENT", "WINDOW" ], "curtain_transform": "t_metal_grate_window_noglass", "examine_action": "curtains", @@ -865,6 +925,12 @@ "duration": "9 seconds", "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] }, + "hacksaw": { + "result": "t_window_reinforced_noglass", + "duration": "10 minutes", + "message": "You finish cutting the metal.", + "byproducts": [ { "item": "pipe", "count": [ 1, 12 ] }, { "item": "sheet_metal", "count": 4 } ] + }, "curtain_transform": "t_metal_grate_window_noglass", "examine_action": "curtains", "close": "t_metal_grate_window_with_curtain", diff --git a/data/mods/TEST_DATA/furniture.json b/data/mods/TEST_DATA/furniture.json index 6dd8c950540e..f40f573f3fea 100644 --- a/data/mods/TEST_DATA/furniture.json +++ b/data/mods/TEST_DATA/furniture.json @@ -42,6 +42,39 @@ "required_str": 8, "boltcut": { "duration": "80 seconds" } }, + { + "type": "furniture", + "id": "test_f_hacksaw1", + "name": "Hacksaw Test Furniture 1", + "description": "Hacksaw furniture with valid data.", + "symbol": "f", + "color": "blue", + "move_cost_mod": 1, + "required_str": 8, + "hacksaw": { "duration": "5 minutes" } + }, + { + "type": "furniture", + "id": "test_f_hacksaw2", + "name": "Hacksaw Test Furniture 2", + "description": "Hacksaw furniture with valid data.", + "symbol": "f", + "color": "blue", + "move_cost_mod": 1, + "required_str": 8, + "hacksaw": { "result": "test_f_hacksaw1", "duration": "5 minutes" } + }, + { + "type": "furniture", + "id": "test_f_hacksaw3", + "name": "Hacksaw Test Furniture 1", + "description": "Hacksaw furniture with valid data.", + "symbol": "f", + "color": "blue", + "move_cost_mod": 1, + "required_str": 8, + "hacksaw": { "duration": "80 minutes" } + }, { "type": "furniture", "id": "test_f_oxytorch1", diff --git a/data/mods/TEST_DATA/items.json b/data/mods/TEST_DATA/items.json index 783231bafdaf..f537a8481d46 100644 --- a/data/mods/TEST_DATA/items.json +++ b/data/mods/TEST_DATA/items.json @@ -590,7 +590,7 @@ "qualities": [ [ "WELD", 10 ] ], "charges_per_use": 4, "use_action": [ "OXYTORCH" ], - "magazines": [ [ "weldgas", [ "weldtank", "tinyweldtank" ] ] ] + "magazines": [ [ "weldgas", [ "weldtank", "test_weldtank" ] ] ] }, { "type": "TOOL", @@ -624,6 +624,39 @@ ] ] }, + { + "type": "TOOL", + "id": "test_hacksaw", + "name": { "str": "test hacksaw" }, + "description": "Test hacksaw.", + "weight": "1 kg", + "volume": "1 L", + "symbol": ";", + "qualities": [ [ "SAW_M", 10 ] ] + }, + { + "type": "TOOL", + "id": "test_hacksaw_elec", + "copy-from": "test_hacksaw", + "name": { "str": "test electric hacksaw" }, + "description": "Test hacksaw which uses batteries.", + "charges_per_use": 1, + "ammo": "battery", + "magazines": [ + [ + "battery", + [ + "light_minus_battery_cell", + "light_battery_cell", + "light_plus_battery_cell", + "light_atomic_battery_cell", + "light_minus_atomic_battery_cell", + "light_minus_disposable_cell", + "light_disposable_cell" + ] + ] + ] + }, { "id": "test_swat_armor", "repairs_like": "survivor_suit", diff --git a/data/mods/TEST_DATA/terrain.json b/data/mods/TEST_DATA/terrain.json index ad12ee1f4af8..9e0826f28731 100644 --- a/data/mods/TEST_DATA/terrain.json +++ b/data/mods/TEST_DATA/terrain.json @@ -90,6 +90,32 @@ "byproducts": [ { "item": "test_rock", "count": 3 }, { "item": "test_2x4", "count": [ 7, 9 ] } ] } }, + { + "type": "terrain", + "id": "test_t_hacksaw1", + "name": "Hacksaw Test Terrain 1", + "description": "Hacksaw terrain with valid data.", + "symbol": "t", + "color": "blue", + "move_cost": 2, + "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 50, "str_max": 100, "str_min_supported": 100, "bash_below": true }, + "hacksaw": { "result": "t_dirt", "duration": "10 minutes" } + }, + { + "type": "terrain", + "id": "test_t_hacksaw2", + "name": "Hacksaw Test Terrain 2", + "description": "Hacksaw terrain with valid data.", + "symbol": "t", + "color": "blue", + "move_cost": 2, + "bash": { "sound": "thump", "ter_set": "t_null", "str_min": 50, "str_max": 100, "str_min_supported": 100, "bash_below": true }, + "hacksaw": { + "result": "t_dirt", + "duration": "5 minutes", + "byproducts": [ { "item": "test_rock", "count": 3 }, { "item": "test_2x4", "count": [ 7, 9 ] } ] + } + }, { "type": "terrain", "id": "test_t_oxytorch1", diff --git a/doc/src/content/docs/en/mod/json/reference/json_info.md b/doc/src/content/docs/en/mod/json/reference/json_info.md index 38bbfcaaf559..b37979370fbd 100644 --- a/doc/src/content/docs/en/mod/json/reference/json_info.md +++ b/doc/src/content/docs/en/mod/json/reference/json_info.md @@ -2637,7 +2637,21 @@ entries. "deconstruct": "TODO", "max_volume": "1000 L", "examine_action": "workbench", - "workbench": { "multiplier": 1.1, "mass": 10000, "volume": "50L" } + "workbench": { "multiplier": 1.1, "mass": 10000, "volume": "50L" }, + "boltcut": { + "result": "f_safe_open", + "duration": "1 seconds", + "message": "The safe opens.", + "sound": "Gachunk!", + "byproducts": [{ "item": "scrap", "count": 3 }] + }, + "hacksaw": { + "result": "f_safe_open", + "duration": "12 seconds", + "message": "The safe is hacksawed open!", + "sound": "Gachunk!", + "byproducts": [{ "item": "scrap", "count": 13 }] + } } ``` @@ -2714,6 +2728,28 @@ the source. For examples: An overhead light is 120, a utility light, 240, and a } ``` +#### `hacksaw` + +(Optional) Data for using with an hacksaw. + +```cpp +"hacksaw": { + "result": "furniture_id", // (optional) furniture it will become when done, defaults to f_null + "duration": "1 seconds", // ( optional ) time required for hacksawing, default is 1 second + "message": "You finish cutting the metal.", // ( optional ) message that will be displayed when finished + "byproducts": [ // ( optional ) list of items that will be spawned when finished + { + "item": "item_id", + "count": 100 // exact amount + }, + { + "item": "item_id", + "count": [ 10, 100 ] // random number in range ( inclusive ) + } + ] +} +``` + #### `required_str` Strength required to move the furniture around. Negative values indicate an unmovable furniture. @@ -2771,7 +2807,21 @@ it for the purpose of surgery. "transforms_into": "t_tree_harvested", "harvest_season": "WINTER", "roof": "t_roof", - "examine_action": "pit" + "examine_action": "pit", + "boltcut": { + "result": "t_door_unlocked", + "duration": "1 seconds", + "message": "The door opens.", + "sound": "Gachunk!", + "byproducts": [{ "item": "scrap", "2x4": 3 }] + }, + "hacksaw": { + "result": "t_door_unlocked", + "duration": "12 seconds", + "message": "The door is hacksawed open!", + "sound": "Gachunk!", + "byproducts": [{ "item": "scrap", "2x4": 13 }] + } } ``` @@ -2804,6 +2854,28 @@ source. For examples: An overhead light is 120, a utility light, 240, and a cons (Optional) When the terrain is successfully lockpicked, this is the message that will be printed to the player. When it is missing, a generic `"The lock opens…"` message will be printed instead. +#### `oxytorch` + +(Optional) Data for using with an oxytorch. + +```cpp +oxytorch: { + "result": "terrain_id", // terrain it will become when done + "duration": "1 seconds", // ( optional ) time required for oxytorching, default is 1 second + "message": "You quickly cut the bars", // ( optional ) message that will be displayed when finished + "byproducts": [ // ( optional ) list of items that will be spawned when finished + { + "item": "item_id", + "count": 100 // exact amount + }, + { + "item": "item_id", + "count": [ 10, 100 ] // random number in range ( inclusive ) + } + ] +} +``` + #### `trap` (Optional) Id of the build-in trap of that terrain. @@ -2821,6 +2893,51 @@ A built-in trap prevents adding any other trap explicitly (by the player and thr fruits (or similar). To make this work, you also have to set one of the `harvest_*` `examine_action` functions. +#### `boltcut` + +(Optional) Data for using with an bolt cutter. + +```cpp +"boltcut": { + "result": "terrain_id", // terrain it will become when done + "duration": "1 seconds", // ( optional ) time required for bolt cutting, default is 1 second + "message": "You finish cutting the metal.", // ( optional ) message that will be displayed when finished + "sound": "Gachunk!", // ( optional ) description of the sound when finished + "byproducts": [ // ( optional ) list of items that will be spawned when finished + { + "item": "item_id", + "count": 100 // exact amount + }, + { + "item": "item_id", + "count": [ 10, 100 ] // random number in range ( inclusive ) + } + ] +} +``` + +#### `hacksaw` + +(Optional) Data for using with an hacksaw. + +```cpp +"hacksaw": { + "result": "terrain_id", // terrain it will become when done + "duration": "1 seconds", // ( optional ) time required for hacksawing, default is 1 second + "message": "You finish cutting the metal.", // ( optional ) message that will be displayed when finished + "byproducts": [ // ( optional ) list of items that will be spawned when finished + { + "item": "item_id", + "count": 100 // exact amount + }, + { + "item": "item_id", + "count": [ 10, 100 ] // random number in range ( inclusive ) + } + ] +} +``` + #### `transforms_into` (Optional) Used for various transformation of the terrain. If defined, it must be a valid terrain diff --git a/lang/extract_json_strings.py b/lang/extract_json_strings.py index bf5866c6bcf4..23f6571c4f3a 100755 --- a/lang/extract_json_strings.py +++ b/lang/extract_json_strings.py @@ -1099,6 +1099,16 @@ def extract_json(state, item): c = f"message when oxytorch cutting {name}" writestr(state, item["oxytorch"]["message"], comment=c) wrote = True + if "hacksaw" in item: + hacksaw = item["hacksaw"] + if "sound" in hacksaw: + c = f"sound of sawing {name}" + writestr(state, hacksaw["sound"], comment=c) + wrote = True + if "message" in hacksaw: + c = f"message when finished sawing {name}" + writestr(state, hacksaw["message"], comment=c) + wrote = True if "boltcut" in item: boltcut = item["boltcut"] if "sound" in boltcut: diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 97f5292fd5bb..c23c13e1596f 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -1060,6 +1060,156 @@ std::unique_ptr pickup_activity_actor::deserialize( JsonIn &jsin return actor; } +void hacksaw_activity_actor::start( player_activity &act, Character &/*who*/ ) +{ + const map &here = get_map(); + + if( here.has_furn( target ) ) { + const furn_id furn_type = here.furn( target ); + if( !furn_type->hacksaw->valid() ) { + if( !testing ) { + debugmsg( "%s hacksaw is invalid", furn_type.id().str() ); + } + act.set_to_null(); + return; + } + + act.moves_total = to_moves( furn_type->hacksaw->duration() ); + } else if( !here.ter( target )->is_null() ) { + const ter_id ter_type = here.ter( target ); + if( !ter_type->hacksaw->valid() ) { + if( !testing ) { + debugmsg( "%s hacksaw is invalid", ter_type.id().str() ); + } + act.set_to_null(); + return; + } + act.moves_total = to_moves( ter_type->hacksaw->duration() ); + } else { + if( !testing ) { + debugmsg( "hacksaw activity called on invalid terrain" ); + } + act.set_to_null(); + return; + } + + act.moves_left = act.moves_total; +} + +void hacksaw_activity_actor::do_turn( player_activity &/*act*/, Character &who ) +{ + if( tool->ammo_sufficient() ) { + tool->ammo_consume( tool->ammo_required(), tool->position() ); + sfx::play_activity_sound( "tool", "hacksaw", sfx::get_heard_volume( target ) ); + if( calendar::once_every( 1_minutes ) ) { + //~ Sound of a metal sawing tool at work! + sounds::sound( target, 15, sounds::sound_t::destructive_activity, _( "grnd grnd grnd" ) ); + } + } else { + if( who.is_avatar() ) { + who.add_msg_if_player( m_bad, _( "Your %1$s ran out of charges." ), tool->tname() ); + } else { // who.is_npc() + if( get_avatar().sees( who.pos() ) ) { + add_msg( _( "%1$s %2$s ran out of charges." ), who.disp_name( false, + true ), tool->tname() ); + } + } + who.cancel_activity(); + } +} + +void hacksaw_activity_actor::finish( player_activity &act, Character &who ) +{ + map &here = get_map(); + std::string message; + const activity_data_common *data; + + if( here.has_furn( target ) ) { + const furn_id furn_type = here.furn( target ); + if( !furn_type->hacksaw->valid() ) { + if( !testing ) { + debugmsg( "%s hacksaw is invalid", furn_type.id().str() ); + } + act.set_to_null(); + return; + } + + const furn_str_id new_furn = furn_type->hacksaw->result(); + if( !new_furn.is_valid() ) { + if( !testing ) { + debugmsg( "hacksaw furniture: %s invalid furniture", new_furn.str() ); + } + act.set_to_null(); + return; + } + + data = static_cast( &*furn_type->hacksaw ); + here.furn_set( target, new_furn ); + } else if( !here.ter( target )->is_null() ) { + const ter_id ter_type = here.ter( target ); + if( !ter_type->hacksaw->valid() ) { + if( !testing ) { + debugmsg( "%s hacksaw is invalid", ter_type.id().str() ); + } + act.set_to_null(); + return; + } + + const ter_str_id new_ter = ter_type->hacksaw->result(); + if( !new_ter.is_valid() ) { + if( !testing ) { + debugmsg( "hacksaw terrain: %s invalid terrain", new_ter.str() ); + } + act.set_to_null(); + return; + } + + data = static_cast( &*ter_type->hacksaw ); + here.ter_set( target, new_ter ); + } else { + if( !testing ) { + debugmsg( "hacksaw activity finished on invalid terrain" ); + } + act.set_to_null(); + return; + } + + for( const activity_byproduct &byproduct : data->byproducts() ) { + const int amount = byproduct.roll(); + if( byproduct.item->count_by_charges() ) { + here.add_item_or_charges( target, item::spawn( byproduct.item, calendar::turn, amount ) ); + } else { + for( int i = 0; i < amount; ++i ) { + here.add_item_or_charges( target, item::spawn( byproduct.item, calendar::turn ) ); + } + } + } + + if( !data->message().empty() ) { + who.add_msg_if_player( m_info, data->message().translated() ); + } + + act.set_to_null(); +} + +void hacksaw_activity_actor::serialize( JsonOut &jsout ) const +{ + jsout.start_object(); + jsout.member( "target", target ); + jsout.member( "tool", tool ); + jsout.end_object(); +} + +std::unique_ptr hacksaw_activity_actor::deserialize( JsonIn &jsin ) +{ + std::unique_ptr actor( new hacksaw_activity_actor( + tripoint_zero, safe_reference() ) ); + JsonObject data = jsin.get_object(); + data.read( "target", actor->target ); + data.read( "tool", actor->tool ); + return actor; +} + void boltcutting_activity_actor::start( player_activity &act, Character &/*who*/ ) { const map &here = get_map(); @@ -1775,6 +1925,7 @@ deserialize_functions = { { activity_id( "ACT_DIG_CHANNEL" ), &dig_channel_activity_actor::deserialize }, { activity_id( "ACT_DROP" ), &drop_activity_actor::deserialize }, { activity_id( "ACT_HACKING" ), &hacking_activity_actor::deserialize }, + { activity_id( "ACT_HACKSAW" ), &hacksaw_activity_actor::deserialize }, { activity_id( "ACT_LOCKPICK" ), &lockpick_activity_actor::deserialize }, { activity_id( "ACT_MIGRATION_CANCEL" ), &migration_cancel_activity_actor::deserialize }, { activity_id( "ACT_MOVE_ITEMS" ), &move_items_activity_actor::deserialize }, diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 11643c0e8a17..e47e32194f5f 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -298,6 +298,37 @@ class hacking_activity_actor : public activity_actor static std::unique_ptr deserialize( JsonIn &jsin ); }; +class hacksaw_activity_actor : public activity_actor +{ + public: + explicit hacksaw_activity_actor( const tripoint &target, + const safe_reference &tool ) : target( target ), tool( tool ) {}; + + activity_id get_type() const override { + return activity_id( "ACT_HACKSAW" ); + } + + void start( player_activity &act, Character &who ) override; + void do_turn( player_activity &/*act*/, Character &who ) override; + void finish( player_activity &act, Character &who ) override; + + void serialize( JsonOut &jsout ) const override; + static std::unique_ptr deserialize( JsonIn &jsin ); + + // debugmsg causes a backtrace when fired during cata_test + bool testing = false; // NOLINT(cata-serialize) + private: + tripoint target; + safe_reference tool; + + bool can_resume_with_internal( const activity_actor &other, + const Character &/*who*/ ) const override { + const hacksaw_activity_actor &actor = static_cast + ( other ); + return actor.target == target; + } +}; + class boltcutting_activity_actor : public activity_actor { public: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 55080373d4aa..a995e33f9f3a 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -138,7 +138,6 @@ static const activity_id ACT_FORAGE( "ACT_FORAGE" ); static const activity_id ACT_GAME( "ACT_GAME" ); static const activity_id ACT_GENERIC_GAME( "ACT_GENERIC_GAME" ); static const activity_id ACT_GUNMOD_ADD( "ACT_GUNMOD_ADD" ); -static const activity_id ACT_HACKSAW( "ACT_HACKSAW" ); static const activity_id ACT_HAIRCUT( "ACT_HAIRCUT" ); static const activity_id ACT_HAND_CRANK( "ACT_HAND_CRANK" ); static const activity_id ACT_HOTWIRE_CAR( "ACT_HOTWIRE_CAR" ); @@ -212,16 +211,11 @@ static const itype_id itype_log( "log" ); static const itype_id itype_mind_scan_robofac( "mind_scan_robofac" ); static const itype_id itype_muscle( "muscle" ); static const itype_id itype_nail( "nail" ); -static const itype_id itype_pipe( "pipe" ); static const itype_id itype_rope_30( "rope_30" ); static const itype_id itype_rope_makeshift_30( "rope_makeshift_30" ); -static const itype_id itype_scrap( "scrap" ); -static const itype_id itype_spike( "spike" ); static const itype_id itype_splinter( "splinter" ); static const itype_id itype_stick_long( "stick_long" ); -static const itype_id itype_steel_chunk( "steel_chunk" ); static const itype_id itype_vine_30( "vine_30" ); -static const itype_id itype_wire( "wire" ); static const itype_id itype_welder( "welder" ); static const itype_id itype_wool_staple( "wool_staple" ); @@ -305,7 +299,6 @@ activity_handlers::do_turn_functions = { { ACT_QUARTER, butcher_do_turn }, { ACT_DISMEMBER, butcher_do_turn }, { ACT_DISSECT, butcher_do_turn }, - { ACT_HACKSAW, hacksaw_do_turn }, { ACT_PRY_NAILS, pry_nails_do_turn }, { ACT_CHOP_TREE, chop_tree_do_turn }, { ACT_CHOP_LOGS, chop_tree_do_turn }, @@ -372,7 +365,6 @@ activity_handlers::finish_functions = { { ACT_CONSUME_FOOD_MENU, eat_menu_finish }, { ACT_CONSUME_DRINK_MENU, eat_menu_finish }, { ACT_CONSUME_MEDS_MENU, eat_menu_finish }, - { ACT_HACKSAW, hacksaw_finish }, { ACT_PRY_NAILS, pry_nails_finish }, { ACT_CHOP_TREE, chop_tree_finish }, { ACT_MILK, milk_finish }, @@ -3839,72 +3831,6 @@ void activity_handlers::eat_menu_finish( player_activity *, player * ) return; } -void activity_handlers::hacksaw_do_turn( player_activity *act, player * ) -{ - sfx::play_activity_sound( "tool", "hacksaw", sfx::get_heard_volume( act->placement ) ); - if( calendar::once_every( 1_minutes ) ) { - //~ Sound of a metal sawing tool at work! - sounds::sound( act->placement, 15, sounds::sound_t::destructive_activity, _( "grnd grnd grnd" ) ); - } -} - -void activity_handlers::hacksaw_finish( player_activity *act, player *p ) -{ - const tripoint &pos = act->placement; - map &here = get_map(); - const ter_id ter = here.ter( pos ); - - if( here.furn( pos ) == f_rack ) { - here.furn_set( pos, f_null ); - here.spawn_item( p->pos(), itype_pipe, rng( 1, 3 ) ); - here.spawn_item( p->pos(), itype_steel_chunk ); - } else if( ter == t_chainfence || ter == t_chaingate_c || ter == t_chaingate_l ) { - here.ter_set( pos, t_dirt ); - here.spawn_item( p->pos(), itype_pipe, 6 ); - here.spawn_item( p->pos(), itype_wire, 20 ); - } else if( ter == t_chainfence_posts ) { - here.ter_set( pos, t_dirt ); - here.spawn_item( p->pos(), itype_pipe, 6 ); - } else if( ter == t_window_bars_alarm ) { - here.ter_set( pos, t_window_alarm ); - here.spawn_item( p->pos(), itype_pipe, 6 ); - } else if( ter == t_window_bars ) { - here.ter_set( pos, t_window_empty ); - here.spawn_item( p->pos(), itype_pipe, 6 ); - } else if( ter == t_window_enhanced ) { - here.ter_set( pos, t_window_reinforced ); - here.spawn_item( p->pos(), itype_spike, rng( 1, 4 ) ); - } else if( ter == t_window_enhanced_noglass ) { - here.ter_set( pos, t_window_reinforced_noglass ); - here.spawn_item( p->pos(), itype_spike, rng( 1, 4 ) ); - } else if( ter == t_reb_cage ) { - here.ter_set( pos, t_pit ); - here.spawn_item( p->pos(), itype_spike, 19 ); - here.spawn_item( p->pos(), itype_scrap, 8 ); - } else if( ter == t_bars ) { - if( here.ter( pos + point_east ) == t_sewage || here.ter( pos + point_south ) - == t_sewage || - here.ter( pos + point_west ) == t_sewage || here.ter( pos + point_north ) == - t_sewage ) { - here.ter_set( pos, t_sewage ); - here.spawn_item( p->pos(), itype_pipe, 3 ); - } else { - here.ter_set( pos, t_floor ); - here.spawn_item( p->pos(), itype_pipe, 3 ); - } - } else if( ter == t_door_bar_c || ter == t_door_bar_locked ) { - here.ter_set( pos, t_mdoor_frame ); - here.spawn_item( p->pos(), itype_pipe, 12 ); - } - - p->mod_stored_nutr( 5 ); - p->mod_thirst( 5 ); - p->mod_fatigue( 10 ); - p->add_msg_if_player( m_good, _( "You finish cutting the metal." ) ); - - act->set_to_null(); -} - void activity_handlers::pry_nails_do_turn( player_activity *act, player * ) { sfx::play_activity_sound( "tool", "hammer", sfx::get_heard_volume( act->placement ) ); diff --git a/src/activity_handlers.h b/src/activity_handlers.h index bc4d2bc2905e..02f9be29a2c1 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -208,7 +208,6 @@ void cracking_do_turn( player_activity *act, player *p ); void repair_item_do_turn( player_activity *act, player *p ); void butcher_do_turn( player_activity *act, player *p ); void pry_nails_do_turn( player_activity *act, player *p ); -void hacksaw_do_turn( player_activity *act, player *p ); void chop_tree_do_turn( player_activity *act, player *p ); void jackhammer_do_turn( player_activity *act, player *p ); void find_mount_do_turn( player_activity *act, player *p ); @@ -269,7 +268,6 @@ void hand_crank_finish( player_activity *act, player *p ); void atm_finish( player_activity *act, player *p ); void eat_menu_finish( player_activity *act, player *p ); void washing_finish( player_activity *act, player *p ); -void hacksaw_finish( player_activity *act, player *p ); void pry_nails_finish( player_activity *act, player *p ); void chop_tree_finish( player_activity *act, player *p ); void chop_logs_finish( player_activity *act, player *p ); diff --git a/src/iuse.cpp b/src/iuse.cpp index 31ae6bfb1338..877cf4753866 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -133,7 +133,6 @@ static const activity_id ACT_FILL_PIT( "ACT_FILL_PIT" ); static const activity_id ACT_FISH( "ACT_FISH" ); static const activity_id ACT_GAME( "ACT_GAME" ); static const activity_id ACT_GENERIC_GAME( "ACT_GENERIC_GAME" ); -static const activity_id ACT_HACKSAW( "ACT_HACKSAW" ); static const activity_id ACT_HAIRCUT( "ACT_HAIRCUT" ); static const activity_id ACT_HAND_CRANK( "ACT_HAND_CRANK" ); static const activity_id ACT_JACKHAMMER( "ACT_JACKHAMMER" ); @@ -4956,34 +4955,18 @@ int iuse::hacksaw( player *p, item *it, bool t, const tripoint & ) p->add_msg_if_player( m_info, _( "You cannot do that while mounted." ) ); return 0; } - const std::set allowed_ter_id { - t_chainfence_posts, - t_window_enhanced, - t_window_enhanced_noglass, - t_chainfence, - t_chaingate_c, - t_chaingate_l, - t_window_bars_alarm, - t_window_bars, - t_reb_cage, - t_door_bar_c, - t_door_bar_locked, - t_bars - }; - const std::set allowed_furn_id { - f_rack - }; - const std::function f = [&allowed_ter_id, - &allowed_furn_id]( const tripoint & pnt ) { - if( pnt == g->u.pos() ) { + + map &here = get_map(); + const std::function f = + [&here, p]( const tripoint & pnt ) { + if( pnt == p->pos() ) { return false; + } else if( here.has_furn( pnt ) ) { + return here.furn( pnt )->hacksaw->valid(); + } else if( !here.ter( pnt )->is_null() ) { + return here.ter( pnt )->hacksaw->valid(); } - const ter_id ter = g->m.ter( pnt ); - const auto furn = g->m.furn( pnt ); - - const bool is_allowed = ( allowed_ter_id.find( ter ) != allowed_ter_id.end() ) || - ( allowed_furn_id.find( furn ) != allowed_furn_id.end() ); - return is_allowed; + return false; }; const std::optional pnt_ = choose_adjacent_highlight( @@ -4992,7 +4975,6 @@ int iuse::hacksaw( player *p, item *it, bool t, const tripoint & ) return 0; } const tripoint &pnt = *pnt_; - const ter_id ter = g->m.ter( pnt ); if( !f( pnt ) ) { if( pnt == p->pos() ) { p->add_msg_if_player( m_info, _( "Why would you do that?" ) ); @@ -5003,25 +4985,11 @@ int iuse::hacksaw( player *p, item *it, bool t, const tripoint & ) return 0; } - int moves; - if( ter == t_chainfence_posts || g->m.furn( pnt ) == f_rack ) { - moves = to_moves( 2_minutes ); - } else if( ter == t_window_enhanced || ter == t_window_enhanced_noglass ) { - moves = to_moves( 5_minutes ); - } else if( ter == t_chainfence || ter == t_chaingate_c || - ter == t_chaingate_l || ter == t_window_bars_alarm || ter == t_window_bars || ter == t_reb_cage ) { - moves = to_moves( 10_minutes ); - } else if( ter == t_door_bar_c || ter == t_door_bar_locked || ter == t_bars ) { - moves = to_moves( 15_minutes ); - } else { - return 0; - } - - p->assign_activity( ACT_HACKSAW, moves, static_cast( ter ), - p->get_item_position( it ) ); - p->activity->placement = pnt; + p->assign_activity( std::make_unique( std::make_unique( + pnt, safe_reference( *it ) + ) ) ); - return it->type->charges_to_use(); + return 0; } int iuse::boltcutters( player *p, item *it, bool, const tripoint & ) diff --git a/src/mapdata.cpp b/src/mapdata.cpp index e18787617609..8163e73b861d 100644 --- a/src/mapdata.cpp +++ b/src/mapdata.cpp @@ -1347,6 +1347,11 @@ void ter_t::load( const JsonObject &jo, const std::string &src ) boltcut->load( jo.get_object( "boltcut" ) ); } + hacksaw = cata::make_value(); + if( jo.has_object( "hacksaw" ) ) { + hacksaw->load( jo.get_object( "hacksaw" ) ); + } + // Not assign, because we want to overwrite individual fields optional( jo, was_loaded, "bash", bash ); deconstruct.load( jo, "deconstruct", false ); @@ -1548,6 +1553,11 @@ void furn_t::load( const JsonObject &jo, const std::string &src ) boltcut->load( jo.get_object( "boltcut" ) ); } + hacksaw = cata::make_value(); + if( jo.has_object( "hacksaw" ) ) { + hacksaw->load( jo.get_object( "hacksaw" ) ); + } + optional( jo, was_loaded, "transforms_into", transforms_into, furn_str_id::NULL_ID() ); optional( jo, was_loaded, "bash", bash ); diff --git a/src/mapdata.h b/src/mapdata.h index daeaf836ce49..a8bddeca7121 100644 --- a/src/mapdata.h +++ b/src/mapdata.h @@ -544,6 +544,7 @@ struct ter_t : map_data_common_t { translation lockpick_message; // Lockpick action: message when successfully lockpicked cata::value_ptr boltcut; // Bolt cutting action data + cata::value_ptr hacksaw; // Hacksaw action data cata::value_ptr oxytorch; // Oxytorch action data std::string trap_id_str; // String storing the id string of the trap. @@ -600,6 +601,7 @@ struct furn_t : map_data_common_t { int move_str_req = 0; //The amount of strength required to move through this furniture easily. cata::value_ptr boltcut; // Bolt cutting action data + cata::value_ptr hacksaw; // Hacksaw action data cata::value_ptr oxytorch; // Oxytorch action data cata::value_ptr workbench; diff --git a/tests/player_activities_test.cpp b/tests/player_activities_test.cpp index 04316b1d9e32..3307c7703ea2 100644 --- a/tests/player_activities_test.cpp +++ b/tests/player_activities_test.cpp @@ -15,11 +15,15 @@ static const activity_id ACT_NULL( "ACT_NULL" ); static const activity_id ACT_BOLTCUTTING( "ACT_BOLTCUTTING" ); +static const activity_id ACT_HACKSAW( "ACT_HACKSAW" ); static const activity_id ACT_OXYTORCH( "ACT_OXYTORCH" ); static const furn_str_id furn_t_test_f_boltcut1( "test_f_boltcut1" ); static const furn_str_id furn_t_test_f_boltcut2( "test_f_boltcut2" ); static const furn_str_id furn_t_test_f_boltcut3( "test_f_boltcut3" ); +static const furn_str_id furn_t_test_f_hacksaw1( "test_f_hacksaw1" ); +static const furn_str_id furn_t_test_f_hacksaw2( "test_f_hacksaw2" ); +static const furn_str_id furn_t_test_f_hacksaw3( "test_f_hacksaw3" ); static const furn_str_id furn_t_test_f_oxytorch1( "test_f_oxytorch1" ); static const furn_str_id furn_t_test_f_oxytorch2( "test_f_oxytorch2" ); static const furn_str_id furn_t_test_f_oxytorch3( "test_f_oxytorch3" ); @@ -27,8 +31,11 @@ static const furn_str_id furn_t_test_f_oxytorch3( "test_f_oxytorch3" ); static const itype_id itype_oxyacetylene( "oxyacetylene" ); static const itype_id itype_test_boltcutter( "test_boltcutter" ); static const itype_id itype_test_boltcutter_elec( "test_boltcutter_elec" ); +static const itype_id itype_test_hacksaw( "test_hacksaw" ); +static const itype_id itype_test_hacksaw_elec( "test_hacksaw_elec" ); static const itype_id itype_test_oxytorch( "test_oxytorch" ); +static const quality_id qual_SAW_M( "SAW_M" ); static const quality_id qual_WELD( "WELD" ); static const ter_str_id ter_test_t_oxytorch1( "test_t_oxytorch1" ); @@ -36,6 +43,8 @@ static const ter_str_id ter_test_t_oxytorch2( "test_t_oxytorch2" ); static const ter_str_id ter_test_t_boltcut1( "test_t_boltcut1" ); static const ter_str_id ter_test_t_boltcut2( "test_t_boltcut2" ); +static const ter_str_id ter_test_t_hacksaw1( "test_t_hacksaw1" ); +static const ter_str_id ter_test_t_hacksaw2( "test_t_hacksaw2" ); TEST_CASE( "boltcut", "[activity][boltcut]" ) { @@ -43,16 +52,16 @@ TEST_CASE( "boltcut", "[activity][boltcut]" ) avatar &dummy = get_avatar(); auto setup_dummy = [&dummy]() -> item & { - item &loc = dummy.i_add( item::spawn( itype_test_boltcutter ) ); - dummy.wield( loc ); + item &cutter = dummy.i_add( item::spawn( itype_test_boltcutter ) ); + dummy.wield( cutter ); REQUIRE( dummy.primary_weapon().typeId() == itype_test_boltcutter ); - return loc; + return cutter; }; auto setup_activity = [&dummy]( item & cutter ) -> void { - std::unique_ptr act = std::make_unique( + auto act = std::make_unique( tripoint_zero, safe_reference( cutter ) ); act->testing = true; @@ -297,24 +306,286 @@ TEST_CASE( "boltcut", "[activity][boltcut]" ) } } +TEST_CASE( "hacksaw", "[activity][hacksaw]" ) +{ + map &mp = get_map(); + avatar &dummy = get_avatar(); + + auto setup_dummy = [&dummy]() -> item & { + item &saw = dummy.i_add( item::spawn( itype_test_hacksaw ) ); + dummy.wield( saw ); + + REQUIRE( dummy.primary_weapon().typeId() == itype_test_hacksaw ); + REQUIRE( dummy.max_quality( qual_SAW_M ) == 10 ); + + return saw; + }; + + auto setup_activity = [&dummy]( item & saw ) -> void { + auto act = std::make_unique( + tripoint_zero, safe_reference( saw ) + ); + act->testing = true; + dummy.assign_activity( std::make_unique( std::move( act ) ) ); + }; + + SECTION( "hacksaw start checks" ) { + GIVEN( "a tripoint with nothing" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, t_null ); + REQUIRE( mp.ter( tripoint_zero ) == t_null ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + THEN( "hacksaw activity can't start" ) { + CHECK( dummy.activity->id() == ACT_NULL ); + } + } + + GIVEN( "a tripoint with invalid terrain" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, t_dirt ); + REQUIRE( mp.ter( tripoint_zero ) == t_dirt ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + THEN( "hacksaw activity can't start" ) { + CHECK( dummy.activity->id() == ACT_NULL ); + } + } + + GIVEN( "a tripoint with valid terrain" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, ter_test_t_hacksaw1 ); + REQUIRE( mp.ter( tripoint_zero ) == ter_test_t_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + THEN( "hacksaw activity can start" ) { + CHECK( dummy.activity->id() == ACT_HACKSAW ); + } + } + + GIVEN( "a tripoint with valid furniture" ) { + clear_map(); + clear_avatar(); + + mp.furn_set( tripoint_zero, furn_t_test_f_hacksaw1 ); + REQUIRE( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + THEN( "hacksaw activity can start" ) { + CHECK( dummy.activity->id() == ACT_HACKSAW ); + } + } + + GIVEN( "a tripoint with valid terrain" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, ter_test_t_hacksaw1 ); + REQUIRE( mp.ter( tripoint_zero ) == ter_test_t_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + + WHEN( "terrain has a duration of 10 minutes" ) { + REQUIRE( ter_test_t_hacksaw1->hacksaw->duration() == 10_minutes ); + THEN( "moves_left is equal to 10 minutes" ) { + CHECK( dummy.activity->moves_left == to_moves( 10_minutes ) ); + } + } + } + + GIVEN( "a tripoint with valid furniture" ) { + clear_map(); + clear_avatar(); + + mp.furn_set( tripoint_zero, furn_t_test_f_hacksaw1 ); + REQUIRE( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + + WHEN( "furniture has a duration of 5 minutes" ) { + REQUIRE( furn_t_test_f_hacksaw1->hacksaw->duration() == 5_minutes ); + THEN( "moves_left is equal to 5 minutes" ) { + CHECK( dummy.activity->moves_left == to_moves( 5_minutes ) ); + } + } + } + } + + SECTION( "hacksaw turn checks" ) { + GIVEN( "player is in mid activity" ) { + clear_map(); + clear_avatar(); + + mp.furn_set( tripoint_zero, furn_t_test_f_hacksaw3 ); + REQUIRE( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw3 ); + + item &hacksaw_elec = dummy.i_add( item::spawn( itype_test_hacksaw_elec, + calendar::start_of_cataclysm, 1 ) ); + dummy.wield( hacksaw_elec ); + + REQUIRE( dummy.primary_weapon().typeId() == itype_test_hacksaw_elec ); + REQUIRE( dummy.max_quality( qual_SAW_M ) == 10 ); + + setup_activity( hacksaw_elec ); + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + process_activity( dummy ); + + WHEN( "player runs out of charges" ) { + REQUIRE( dummy.activity->id() == ACT_NULL ); + + THEN( "player recharges with fuel" ) { + hacksaw_elec.ammo_set( hacksaw_elec.ammo_default(), -1 ); + + AND_THEN( "player can resume the activity" ) { + setup_activity( hacksaw_elec ); + dummy.moves = dummy.get_speed(); + dummy.activity->do_turn( dummy ); + CHECK( dummy.activity->id() == ACT_HACKSAW ); + CHECK( dummy.activity->moves_left < to_moves( furn_t_test_f_hacksaw3->hacksaw->duration() ) ); + } + } + } + } + } + + SECTION( "hacksaw finish checks" ) { + GIVEN( "a tripoint with valid terrain" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, ter_test_t_hacksaw1 ); + REQUIRE( mp.ter( tripoint_zero ) == ter_test_t_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + process_activity( dummy ); + REQUIRE( dummy.activity->id() == ACT_NULL ); + + THEN( "terrain gets converted to new terrain type" ) { + CHECK( mp.ter( tripoint_zero ) == t_dirt ); + } + } + + GIVEN( "a tripoint with valid furniture" ) { + clear_map(); + clear_avatar(); + + mp.furn_set( tripoint_zero, furn_t_test_f_hacksaw1 ); + REQUIRE( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw1 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + process_activity( dummy ); + REQUIRE( dummy.activity->id() == ACT_NULL ); + + THEN( "furniture gets converted to new furniture type" ) { + CHECK( mp.furn( tripoint_zero ) == f_null ); + } + } + + GIVEN( "a tripoint with valid furniture" ) { + clear_map(); + clear_avatar(); + + mp.furn_set( tripoint_zero, furn_t_test_f_hacksaw2 ); + REQUIRE( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw2 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + process_activity( dummy ); + REQUIRE( dummy.activity->id() == ACT_NULL ); + + THEN( "furniture gets converted to new furniture type" ) { + CHECK( mp.furn( tripoint_zero ) == furn_t_test_f_hacksaw1 ); + } + } + + + GIVEN( "a tripoint with a valid furniture with byproducts" ) { + clear_map(); + clear_avatar(); + + mp.ter_set( tripoint_zero, ter_test_t_hacksaw2 ); + REQUIRE( mp.ter( tripoint_zero ) == ter_test_t_hacksaw2 ); + + item &hacksaw = setup_dummy(); + setup_activity( hacksaw ); + + REQUIRE( ter_test_t_hacksaw2->hacksaw->byproducts().size() == 2 ); + + REQUIRE( dummy.activity->id() == ACT_HACKSAW ); + process_activity( dummy ); + REQUIRE( dummy.activity->id() == ACT_NULL ); + + const itype_id test_amount( "test_rock" ); + const itype_id test_random( "test_2x4" ); + + WHEN( "hacksaw acitivy finishes" ) { + CHECK( dummy.activity->id() == ACT_NULL ); + + THEN( "player receives the items" ) { + int count_amount = 0; + int count_random = 0; + for( const auto &it : get_map().i_at( tripoint_zero ) ) { + // can't use switch here + const itype_id it_id = it->typeId(); + if( it_id == test_amount ) { + count_amount += it->charges; + } else if( it_id == test_random ) { + count_random += 1; + } + } + + CHECK( count_amount == 3 ); + CHECK( ( 7 <= count_random && count_random <= 9 ) ); + } + } + } + } +} + TEST_CASE( "oxytorch", "[activity][oxytorch]" ) { map &mp = get_map(); avatar &dummy = get_avatar(); auto setup_dummy = [&dummy]() -> item & { - item &loc = dummy.i_add( item::spawn( itype_test_oxytorch ) ); - loc.ammo_set( itype_oxyacetylene, -1 ); - dummy.wield( loc ); + item &torch = dummy.i_add( item::spawn( itype_test_oxytorch ) ); + torch.ammo_set( itype_oxyacetylene, -1 ); + dummy.wield( torch ); REQUIRE( dummy.primary_weapon().typeId() == itype_test_oxytorch ); REQUIRE( dummy.max_quality( qual_WELD ) == 10 ); - return loc; + return torch; }; auto setup_activity = [&dummy]( item & torch ) -> void { - std::unique_ptr act = std::make_unique( + auto act = std::make_unique( tripoint_zero, safe_reference( torch ) ); act->testing = true;