diff --git a/data/area/kharidian_desert/al_kharid/al_kharid.areas.toml b/data/area/kharidian_desert/al_kharid/al_kharid.areas.toml index ba4d5bd885..59b9472dd7 100644 --- a/data/area/kharidian_desert/al_kharid/al_kharid.areas.toml +++ b/data/area/kharidian_desert/al_kharid/al_kharid.areas.toml @@ -6,14 +6,10 @@ tags = ["bank"] [zekes_scimitar_shop] x = [3285, 3290] y = [3187, 3192] -tags = ["shop"] -items = ["bronze_scimitar", "iron_scimitar", "steel_scimitar", "mithril_scimitar"] [al_kharid_kebab_shop] x = [3271, 3275] y = [3179, 3183] -type = "range" -tags = ["cooking", "spaces_2"] [al_kharid_multi_area] x = [3264, 3327] @@ -34,6 +30,10 @@ y = [3121, 3125] x = [3291, 3308] y = [3281, 3320] +[al_kharid_mine_north] +x = [3291, 3308] +y = [3309, 3320] + [greater_al_kharid] x = [3264,3264,3391,3391,3423,3423,3455,3455] y = [3136,3327,3327,3263,3263,3182,3182,3136] diff --git a/data/area/kharidian_desert/al_kharid/al_kharid.bots.toml b/data/area/kharidian_desert/al_kharid/al_kharid.bots.toml new file mode 100644 index 0000000000..9257e6438d --- /dev/null +++ b/data/area/kharidian_desert/al_kharid/al_kharid.bots.toml @@ -0,0 +1,35 @@ +# Mining +[al_kharid_iron_mining] +template = "iron_ore_template" +capacity = 2 +fields = { location = "al_kharid_mine" } + +[al_kharid_coal_mining] +template = "coal_template" +capacity = 1 +fields = { location = "al_kharid_mine" } + +[al_kharid_silver_mining] +template = "silver_ore_template" +capacity = 2 +fields = { location = "al_kharid_mine" } + +[al_kharid_north_copper_mining] +template = "copper_ore_template" +capacity = 1 +fields = { location = "al_kharid_mine_north" } + +[al_kharid_north_silver_mining] +template = "silver_ore_template" +capacity = 1 +fields = { location = "al_kharid_mine_north" } + +[al_kharid_north_iron_mining] +template = "iron_ore_template" +capacity = 2 +fields = { location = "al_kharid_mine_north" } + +[al_kharid_north_mithril_mining] +template = "mithril_ore_template" +capacity = 2 +fields = { location = "al_kharid_mine_north" } diff --git a/data/area/kharidian_desert/al_kharid/al_kharid.npcs.toml b/data/area/kharidian_desert/al_kharid/al_kharid.npcs.toml index b7f7ab7d18..bcf6e7dbc2 100644 --- a/data/area/kharidian_desert/al_kharid/al_kharid.npcs.toml +++ b/data/area/kharidian_desert/al_kharid/al_kharid.npcs.toml @@ -18,6 +18,7 @@ examine = "Likes you more, the more you spend." id = 541 categories = ["human"] shop = "zekes_superior_scimitars" +area = "zekes_scimitar_shop" wander_range = 3 collision = "indoors" examine = "Sells superior scimitars." diff --git a/data/area/misthalin/draynor/draynor.areas.toml b/data/area/misthalin/draynor/draynor.areas.toml index ec7fa65a1e..c643e90bb3 100644 --- a/data/area/misthalin/draynor/draynor.areas.toml +++ b/data/area/misthalin/draynor/draynor.areas.toml @@ -11,31 +11,18 @@ hint = "around a village between a great tower and a vampyre's manor." [draynor_willow_trees] x = [3083, 3090] y = [3227, 3238] -tags = ["trees"] -trees = ["willow"] -levels = "30-45" -spaces = 6 [draynor_oak_trees] x = [3098, 3103] y = [3241, 3253] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [draynor_west_oak_trees] x = [3068, 3072] y = [3248, 3260] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [draynor_north_trees] x = [3075, 3085] y = [3265, 3274] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [draynor_bank] x = [3088, 3097] @@ -45,9 +32,6 @@ tags = ["bank"] [draynor_fishing_area] x = [3085, 3086] y = [3227, 3231] -tags = ["fish"] -type = "lure_bait" -spaces = 5 [draynor_village_multi_area] x = [3112, 3136, 3136, 3104, 3104, 3112] diff --git a/data/area/misthalin/draynor/draynor.bots.toml b/data/area/misthalin/draynor/draynor.bots.toml new file mode 100644 index 0000000000..d8ff9ddfcb --- /dev/null +++ b/data/area/misthalin/draynor/draynor.bots.toml @@ -0,0 +1,20 @@ +[draynor_east_trees] +template = "normal_tree_template" +fields = { location = "draynor_oak_trees" } + +[draynor_east_oak_trees] +template = "oak_tree_template" +fields = { location = "draynor_oak_trees" } + +[draynor_north_trees] +template = "normal_tree_template" +fields = { location = "draynor_north_trees" } + +[draynor_west_oak_trees] +template = "oak_tree_template" +fields = { location = "draynor_west_oak_trees" } + +[draynor_willow_trees] +template = "willow_tree_template" +capacity = 8 +fields = { location = "draynor_willow_trees" } diff --git a/data/area/misthalin/lumbridge/lumbridge.areas.toml b/data/area/misthalin/lumbridge/lumbridge.areas.toml index 28b42433ed..d07859e8ca 100644 --- a/data/area/misthalin/lumbridge/lumbridge.areas.toml +++ b/data/area/misthalin/lumbridge/lumbridge.areas.toml @@ -5,51 +5,30 @@ y = [3136, 3136, 3336, 3336, 3346, 3349, 3347, 3347, 3337, 3333, 3333, 3330, 333 [lumbridge_north_trees] x = [3220, 3234] y = [3244, 3249] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [lumbridge_west_yew_tree] x = [3164, 3168] y = [3218, 3222] -tags = ["trees"] -trees = ["tree", "yew"] -levels = "60-68" [lumbridge_north_west_yew_tree] x = [3183, 3187] y = [3225, 3229] -tags = ["trees"] -trees = ["tree", "yew"] -levels = "60-68" [lumbridge_west_trees] x = [3186, 3197] y = [3238, 3250] -tags = ["trees"] -trees = ["tree", "oak"] -spaces = 2 -levels = "0-30" [lumbridge_east_trees] x = [3260, 3267] y = [3214, 3226] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [lumbridge_cow_trees] x = [3254, 3264] y = [3249, 3254] -tags = ["trees"] -trees = ["tree", "oak"] -levels = "0-30" [bobs_axe_shop] x = [3227, 3233] y = [3201, 3205] -tags = ["shop"] -items = ["bronze_pickaxe", "bronze_hatchet", "iron_hatchet", "steel_hatchet", "iron_battleaxe", "steel_battleaxe", "mithril_battleaxe"] [lumbridge_castle_bank] x = [3207, 3210] @@ -66,64 +45,34 @@ tags = ["high"] [lumbridge_swamp_east_copper_mine] x = [3227, 3231] y = [3143, 3149] -tags = ["mine"] -rocks = ["copper"] -levels = "1-15" -spaces = 2 [lumbridge_swamp_east_tin_mine] x = [3222, 3226] y = [3145, 3149] -tags = ["mine"] -rocks = ["tin"] -levels = "1-15" -spaces = 2 [lumbridge_swamp_west_coal_mine] x = [3143, 3147] y = [3148, 3154] -tags = ["mine", "coal"] -rocks = ["coal"] -levels = "30-40" -spaces = 2 [lumbridge_swamp_west_mithril_mine] x = [3143, 3149] y = [3143, 3147] -tags = ["mine"] -rocks = ["mithril"] -levels = "55-70" [lumbridge_swamp_west_adamantite_mine] x = [3147, 3149] y = [3145, 3149] -tags = ["mine"] -rocks = ["adamantite"] -levels = "70-85" [lumbridge_east_cow_field] x = [3253, 3253, 3251, 3251, 3249, 3246, 3244, 3244, 3240, 3240, 3242, 3242, 3240, 3240, 3241, 3256, 3257, 3260, 3261, 3263, 3265, 3265, 3266, 3266, 3265, 3265, 3266, 3266, 3265, 3265, 3264] y = [3255, 3272, 3274, 3276, 3278, 3278, 3280, 3281, 3285, 3286, 3288, 3294, 3296, 3297, 3298, 3298, 3299, 3299, 3298, 3298, 3296, 3293, 3292, 3286, 3285, 3276, 3275, 3272, 3271, 3256, 3255] -tags = ["combat_training"] -npcs = ["cows"] -spaces = 7 -levels = "10-20" [lumbridge_east_goblin_settlement] x = [3262, 3259, 3253, 3251, 3251, 3249, 3245, 3242, 3242, 3240, 3240, 3239, 3239, 3252, 3252, 3264, 3264, 3265, 3265, 3266, 3266, 3267, 3267, 3266, 3266] y = [3215, 3218, 3222, 3223, 3227, 3228, 3232, 3235, 3239, 3241, 3249, 3251, 3256, 3256, 3254, 3254, 3247, 3246, 3238, 3237, 3226, 3225, 3220, 3219, 3215] -tags = ["combat_training"] -npcs = ["goblins", "giant_spiders"] -spaces = 6 -levels = "5-15" [lumbridge_chicken_pen] x = [3225, 3225, 3237, 3237] y = [3287, 3301, 3301, 3287] -tags = ["combat_training"] -npcs = ["chickens"] -spaces = 5 -levels = "1-10" [lumbridge_castle_courtyard] x = [3218, 3224, 3224, 3218] @@ -136,29 +85,18 @@ y = [3239, 3243] [lumbridge_swamp_fishing_area] x = [3240, 3247] y = [3142, 3157] -tags = ["fish"] -type = "small_net_bait" -spaces = 8 [lumbridge_river_fishing_area] x = [3238, 3240] y = [3241, 3255] -tags = ["fish"] -type = "lure_bait" -spaces = 3 [hanks_fishing_shop] x = [3191, 3198] y = [3251, 3255] -tags = ["shop"] -items = ["small_fishing_net", "fishing_rod", "fly_fishing_rod", "crayfish_cage", "fishing_bait", "feather"] [lumbridge_south_river_fishing_area] x = [3258, 3259] y = [3203, 3207] -tags = ["fish"] -type = "crayfish" -spaces = 2 [lumbridge_teleport] x = [3221, 3222] @@ -172,32 +110,23 @@ y = [3250, 3256] [lumbridge_swamp_giant_rats] x = [3212, 3232] y = [3173, 3190] -tags = ["combat_training"] -npcs = ["giant_rats"] -spaces = 2 -levels = "10-14" [lumbridge_firemaking_spot] -x = [3219, 3219] -y = [3247, 3248] -tags = ["fire_making", "spaces_2"] +x = [3220, 3221] +y = [3257, 3259] [lumbridge_kitchen] x = [3205, 3212] y = [3212, 3217] -type = "range" -tags = ["cooking", "spaces_2"] +level = 0 [lumbridge_south_cooking_range] x = [3230, 3237] y = [3195, 3198] -type = "range" -tags = ["cooking", "spaces_2"] [lumbridge_furnace] x = [3222, 3229] y = [3252, 3257] -tags = ["smithing", "smelting", "spaces_2"] [freds_farmhouse] x = [3184, 3192] @@ -207,4 +136,12 @@ y = [3270, 3275] x = [3136, 3263] y = [3136, 3327] tags = ["penguin_area"] -hint = "in the south of the kingdom of Misthalin." \ No newline at end of file +hint = "in the south of the kingdom of Misthalin." + +[lumbridge_thieving_area] +x = [3220, 3225] +y = [3236, 3242] + +[lumbridge_west_cow_field] +x = [3156, 3202] +y = [3316, 3341] diff --git a/data/area/misthalin/lumbridge/lumbridge.bots.toml b/data/area/misthalin/lumbridge/lumbridge.bots.toml new file mode 100644 index 0000000000..27b53d3544 --- /dev/null +++ b/data/area/misthalin/lumbridge/lumbridge.bots.toml @@ -0,0 +1,265 @@ +# Combat +[kill_freds_chickens_attack] +template = "kill_chickens" +capacity = 2 +fields = { skill = "attack", location = "lumbridge_chicken_pen", style = "style1" } + +[kill_freds_chickens_strength] +template = "kill_chickens" +capacity = 2 +fields = { skill = "strength", location = "lumbridge_chicken_pen", style = "style2" } + +[kill_freds_chickens_defence] +template = "kill_chickens" +capacity = 2 +fields = { skill = "defence", location = "lumbridge_chicken_pen", style = "style4" } + +[kill_millies_cows_attack] +template = "kill_cows" +capacity = 2 +fields = { skill = "attack", location = "lumbridge_east_cow_field", style = "style1" } + +[kill_millies_cows_strength] +template = "kill_cows" +capacity = 2 +fields = { skill = "strength", location = "lumbridge_east_cow_field", style = "style2" } + +[kill_millies_cows_defence] +template = "kill_cows" +capacity = 2 +fields = { skill = "defence", location = "lumbridge_east_cow_field", style = "style4" } + +[kill_bills_cows_attack] +template = "kill_cows" +capacity = 2 +fields = { skill = "attack", location = "lumbridge_west_cow_field", style = "style1" } + +[kill_bills_cows_strength] +template = "kill_cows" +capacity = 2 +fields = { skill = "strength", location = "lumbridge_west_cow_field", style = "style2" } + +[kill_bills_cows_defence] +template = "kill_cows" +capacity = 2 +fields = { skill = "defence", location = "lumbridge_west_cow_field", style = "style4" } + +# Tutors +[magic_tutor] +capacity = 2 +requires = [ + { skill = { id = "magic", min = 1, max = 5 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +setup = [ + { inventory = [ + { id = "air_rune", min = 30 }, + { id = "mind_rune", min = 30 }, + ] }, +] +actions = [ + { interface = { option = "Autocast", id = "modern_spellbook:wind_strike", success = { variable = { id = "autocast", min = 0, default = -1 } } } }, + { npc = { option = "Attack", id = "magic_dummy", success = { inventory = { id = "empty", min = 28 } } } } +] +produces = [ + { skill = "magic" } +] + +[melee_tutor_attack] +capacity = 1 +requires = [ + { skill = { id = "attack", min = 1, max = 5 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +setup = [ + { equipment = { weapon = { id = "training_sword" }, shield = { id = "training_shield" } } }, +] +actions = [ + { interface = { option = "Select", id = "combat_styles:style1" } }, + { npc = { option = "Attack", id = "melee_dummy", success = { skill = { id = "attack", min = 5 } } } } +] +produces = [ + { skill = "attack" } +] + +[melee_tutor_strength] +capacity = 1 +requires = [ + { skill = { id = "strength", min = 1, max = 5 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +setup = [ + { equipment = { weapon = { id = "training_sword" }, shield = { id = "training_shield" } } }, +] +actions = [ + { interface = { option = "Select", id = "combat_styles:style2" } }, + { npc = { option = "Attack", id = "melee_dummy", success = { skill = { id = "strength", min = 5 } } } } +] +produces = [ + { skill = "strength" } +] + +[ranged_tutor] +capacity = 1 +requires = [ + { skill = { id = "ranged", min = 1, max = 5 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +setup = [ + { equipment = { weapon = { id = "training_bow" }, ammo = { id = "training_arrows", min = 10 } } }, +] +actions = [ + { interface = { option = "Select", id = "combat_styles:style1" } }, + { object = { option = "Shoot-at", id = "archery_target", success = { queue = { id = "archery" } } } }, + { restart = { wait_if = [{ queue = { id = "archery" } }, { mode = { id = "player_on_object" } }], success = { equipment = { ammo = { id = "empty" } } } } } +] +produces = [ + { skill = "ranged" } +] + +# Thieving +[lumbridge_pickpocketing] +template = "pickpocketting_template" +capacity = 2 +fields = { location = "lumbridge_thieving_area", npc = "lumbridge_man*,lumbridge_woman*" } +requires = [ + { skill = { id = "thieving", min = 1, max = 10 } } +] + +# Fishing +[lumbridge_crayfish] +template = "fish_crayfish" +capacity = 4 +fields = { location = "lumbridge_south_river_fishing_area", spot = "fishing_spot_crayfish_lumbridge" } +requires = [ + { skill = { id = "fishing", min = 1, max = 10 } } +] + +[lumbridge_net_fishing] +template = "fish_small_net" +capacity = 5 +fields = { location = "lumbridge_swamp_fishing_area", spot = "fishing_spot_small_net_bait_lumbridge" } +requires = [ + { skill = { id = "fishing", min = 1, max = 15 } } +] + +[lumbridge_bait_fishing] +template = "fish_bait" +capacity = 3 +fields = { location = "lumbridge_river_fishing_area", spot = "fishing_spot_lure_bait_lumbridge" } +requires = [ + { skill = { id = "fishing", min = 25, max = 35 } } +] + +# Fletching +[lumbridge_fletch_arrow_shafts] +template = "fletching_arrow_shafts_template" +capacity = 2 +fields = { location = "lumbridge_castle_bank", logs = "logs" } +requires = [ + { skill = { id = "fletching", min = 1, max = 10 } } +] + +[lumbridge_fletch_shortbows] +template = "fletching_shortbow_template" +capacity = 2 +fields = { location = "lumbridge_castle_bank", logs = "logs" } +requires = [ + { skill = { id = "fletching", min = 5, max = 15 } } +] +produces = [ + { item = "shortbow_u" } +] + +[lumbridge_fletch_longbows] +template = "fletching_longbow_template" +fields = { location = "lumbridge_castle_bank", logs = "logs" } +requires = [ + { skill = { id = "fletching", min = 10, max = 20 } } +] +produces = [ + { item = "longbow_u" } +] + +# Woodcutting +[lumbridge_north_tree_cutting] +template = "normal_tree_template" +fields = { location = "lumbridge_north_trees" } + +[lumbridge_cow_tree_cutting] +template = "normal_tree_template" +fields = { location = "lumbridge_cow_trees" } + +[lumbridge_west_tree_cutting] +template = "normal_tree_template" +fields = { location = "lumbridge_west_trees" } + +[lumbridge_east_tree_cutting] +template = "normal_tree_template" +fields = { location = "lumbridge_east_trees" } + +[lumbridge_oak_tree_cutting] +template = "oak_tree_template" +fields = { location = "lumbridge_north_trees" } + +[lumbridge_castle_yew_tree_cutting] +template = "yew_tree_template" +fields = { location = "lumbridge_north_west_yew_tree" } + +[lumbridge_west_yew_tree_cutting] +template = "yew_tree_template" +fields = { location = "lumbridge_west_yew_tree" } + +# Mining +[lumbridge_copper_mining] +template = "copper_ore_template" +capacity = 4 +fields = { location = "lumbridge_swamp_east_copper_mine" } + +[lumbridge_tin_mining] +template = "tin_ore_template" +capacity = 4 +fields = { location = "lumbridge_swamp_east_tin_mine" } + +[lumbridge_coal_mining] +template = "coal_template" +capacity = 4 +fields = { location = "lumbridge_swamp_west_coal_mine" } + +[lumbridge_mithril_mining] +template = "mithril_ore_template" +capacity = 2 +fields = { location = "lumbridge_swamp_west_mithril_mine" } + +# Firemaking +[lumbridge_firemaking_logs] +template = "firemaking" +capacity = 2 +fields = { logs = "logs", location = "lumbridge_firemaking_spot" } +requires = [ + { skill = { id = "firemaking", min = 1, max = 15 } }, +] + +[lumbridge_firemaking_oak_logs] +template = "firemaking" +capacity = 1 +fields = { logs = "oak_logs", location = "lumbridge_firemaking_spot" } +requires = [ + { skill = { id = "firemaking", min = 15, max = 25 } }, +] + +# Cooking +[lumbridge_cook_chicken] +template = "cooking_template" +capacity = 4 +fields = { raw = "raw_chicken", cooked = "chicken", level = 1, location = "lumbridge_kitchen", obj = "cooking_range_lumbridge_castle", burnt = "burnt_chicken" } + +[lumbridge_cook_crayfish] +template = "cooking_template" +capacity = 4 +fields = { raw = "raw_crayfish", cooked = "crayfish", level = 1, location = "lumbridge_kitchen", obj = "cooking_range_lumbridge_castle", burnt = "burnt_crayfish" } + +[lumbridge_cook_beef] +template = "beef_template" +capacity = 4 +fields = { location = "lumbridge_kitchen", obj = "cooking_range_lumbridge_castle" } diff --git a/data/area/misthalin/lumbridge/lumbridge.npcs.toml b/data/area/misthalin/lumbridge/lumbridge.npcs.toml index 9bfb7ff42e..20c73faf6a 100644 --- a/data/area/misthalin/lumbridge/lumbridge.npcs.toml +++ b/data/area/misthalin/lumbridge/lumbridge.npcs.toml @@ -9,6 +9,7 @@ examine = "Servant of the Duke of Lumbridge." id = 519 categories = ["human"] shop = "bobs_brilliant_axes" +area = "bobs_axe_shop" collision = "indoors" examine = "An expert on axes." @@ -16,6 +17,7 @@ examine = "An expert on axes." id = 520 categories = ["human"] shop = "lumbridge_general_store" +area = "lumbridge_general_store" wander_range = 2 collision = "indoors" examine = "Sells stuff." @@ -24,6 +26,7 @@ examine = "Sells stuff." id = 521 categories = ["human"] shop = "lumbridge_general_store" +area = "lumbridge_general_store" wander_range = 2 collision = "indoors" examine = "Helps sell stuff." @@ -32,6 +35,7 @@ examine = "Helps sell stuff." id = 8864 categories = ["human"] shop = "lumbridge_fishing_supplies" +area = "hanks_fishing_shop" collision = "indoors" examine = "A product of a consumerist society." diff --git a/data/area/misthalin/lumbridge/lumbridge.setups.toml b/data/area/misthalin/lumbridge/lumbridge.setups.toml new file mode 100644 index 0000000000..e118f1d284 --- /dev/null +++ b/data/area/misthalin/lumbridge/lumbridge.setups.toml @@ -0,0 +1,71 @@ +[mikasi_runes] +requires = [ + { clock = { id = "claimed_tutor_consumables", max = 0, seconds = true } }, + { bank = [ + { id = "air_rune", max = 0 }, + { id = "mind_rune", max = 0 }, + ] }, + { area = { id = "lumbridge_combat_tutors" } } +] +actions = [ + { npc = { option = "Talk-to", id = "mikasi", delay = 5, success = { interface_open = { id = "dialogue_npc_chat3" } } } }, + { continue = { id = "dialogue_npc_chat3:continue", success = { interface_open = { id = "dialogue_multi4" } } } }, + { continue = { id = "dialogue_multi4:line3", success = { interface_open = { id = "dialogue_obj_box" } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "air_rune", min = 30 } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "mind_rune", min = 30 } } } }, +] +produces = [ + { item = "air_rune" }, + { item = "mind_rune" } +] + +[harlan_sword] +requires = [ + { clock = { id = "claimed_tutor_consumables", max = 0, seconds = true } }, + { owns = { id = "training_sword", max = 0 } }, + { owns = { id = "training_shield", max = 0 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +actions = [ + { npc = { option = "Talk-to", id = "harlan", delay = 5, success = { interface_open = { id = "dialogue_npc_chat2" } } } }, + { continue = { id = "dialogue_npc_chat2:continue", success = { interface_open = { id = "dialogue_multi5" } } } }, + { continue = { id = "dialogue_multi5:line4", success = { interface_open = { id = "dialogue_chat1" } } } }, + { continue = { id = "dialogue_chat1:continue", success = { interface_open = { id = "dialogue_obj_box" } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "training_sword", min = 1 } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "training_shield", min = 1 } } } }, +] +produces = [ + { item = "training_sword" }, + { item = "training_shield" } +] + +[nemarti_bow] +requires = [ + { clock = { id = "claimed_tutor_consumables", max = 0, seconds = true } }, + { owns = { id = "training_bow", max = 0 } }, + { owns = { id = "training_arrows", max = 0 } }, + { area = { id = "lumbridge_combat_tutors" } } +] +actions = [ + { npc = { option = "Talk-to", id = "nemarti", delay = 5, success = { interface_open = { id = "dialogue_npc_chat2" } } } }, + { continue = { id = "dialogue_npc_chat2:continue", success = { interface_open = { id = "dialogue_multi4" } } } }, + { continue = { id = "dialogue_multi4:line3", success = { interface_open = { id = "dialogue_obj_box" } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "training_bow", min = 1 } } } }, + { continue = { id = "dialogue_obj_box:continue", success = { inventory = { id = "training_arrows", min = 30 } } } }, +] +produces = [ + { item = "training_bow" }, + { item = "training_arrows" } +] + +[pick_up_lumbridge_swamp_fishing_net] +requires = [ + { inventory = { id = "empty", min = 1 } }, + { area = { id = "lumbridge_swamp_fishing_area" } } +] +actions = [ + { floor_item = { option = "Take", id = "small_fishing_net", delay = 10, success = { inventory = { id = "small_fishing_net", min = 1 } } } }, +] +produces = [ + { item = "small_fishing_net" }, +] \ No newline at end of file diff --git a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.item-spawns.toml b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.item-spawns.toml index d262c90272..ffc137d6b6 100644 --- a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.item-spawns.toml +++ b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.item-spawns.toml @@ -19,6 +19,5 @@ spawns = [ { id = "swamp_tar", x = 3193, y = 3181, delay = 100, members = true }, # 12849 { id = "small_fishing_net", x = 3244, y = 3157, delay = 10 }, - { id = "small_fishing_net", x = 3244, y = 3160, delay = 10 }, { id = "leather_gloves", x = 3206, y = 3147, delay = 100 }, ] \ No newline at end of file diff --git a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npc-spawns.toml b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npc-spawns.toml index 2ac3e386e6..8db96feb6a 100644 --- a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npc-spawns.toml +++ b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npc-spawns.toml @@ -39,10 +39,10 @@ spawns = [ { id = "giant_spider", x = 3187, y = 3184 }, { id = "giant_spider", x = 3193, y = 3193 }, { id = "giant_spider", x = 3195, y = 3187 }, - { id = "archer_lumbridge", x = 3183, y = 3147 }, - { id = "warrior_lumbridge", x = 3186, y = 3145 }, - { id = "monk_lumbridge", x = 3185, y = 3146 }, - { id = "wizard_lumbridge", x = 3187, y = 3147 }, + { id = "archer_lumbridge", x = 3148, y = 3208 }, + { id = "warrior_lumbridge", x = 3150, y = 3208 }, + { id = "monk_lumbridge", x = 3152, y = 3204 }, + { id = "wizard_lumbridge", x = 3149, y = 3204 }, { id = "giant_rat_dark", x = 3201, y = 3174, members = true }, { id = "giant_rat_dark", x = 3227, y = 3193, members = true }, { id = "giant_rat_dark", x = 3232, y = 3170, members = true }, diff --git a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npcs.toml b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npcs.toml index 346bc68b66..c91940ae57 100644 --- a/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npcs.toml +++ b/data/area/misthalin/lumbridge/swamp/lumbridge_swamp.npcs.toml @@ -18,18 +18,22 @@ id = 5890 [archer_lumbridge] id = 649 +wander_range = 4 examine = "She looks quite experienced." [warrior_lumbridge] id = 650 +wander_range = 4 examine = "He looks big and dumb." [monk_lumbridge] id = 651 +wander_range = 4 examine = "He looks holy." [wizard_lumbridge] id = 652 +wander_range = 4 examine = "He looks kind of puny..." [sergeant_mossfists_2] diff --git a/data/area/misthalin/varrock/varrock.areas.toml b/data/area/misthalin/varrock/varrock.areas.toml index 689a88697c..db92889c14 100644 --- a/data/area/misthalin/varrock/varrock.areas.toml +++ b/data/area/misthalin/varrock/varrock.areas.toml @@ -16,10 +16,10 @@ tags = ["multi_combat"] [varrock_south_east_mine] x = [3281, 3290] y = [3360, 3371] -tags = ["mine"] -rocks = ["copper", "tin", "iron"] -levels = "1-45" -spaces = 4 + +[varrock_south_west_mine] +x = [3171, 3184] +y = [3364, 3380] [varrock_east_bank] x = [3250, 3257] @@ -55,3 +55,7 @@ x = [3136, 3391] y = [3328, 3519] tags = ["penguin_area"] hint = "in the north of the kingdom of Misthalin." + +[varrock_sword_shop] +x = [3202, 3209] +y = [3395, 3403] diff --git a/data/area/misthalin/varrock/varrock.bots.toml b/data/area/misthalin/varrock/varrock.bots.toml new file mode 100644 index 0000000000..518093de1f --- /dev/null +++ b/data/area/misthalin/varrock/varrock.bots.toml @@ -0,0 +1,35 @@ +# Mining +[varrock_copper_mining] +template = "copper_ore_template" +capacity = 4 +fields = { location = "varrock_south_east_mine" } + +[varrock_tin_mining] +template = "tin_ore_template" +capacity = 4 +fields = { location = "varrock_south_east_mine" } + +[varrock_iron_mining] +template = "iron_ore_template" +capacity = 2 +fields = { location = "varrock_south_east_mine" } + +[varrock_clay_mining] +template = "clay_template" +capacity = 2 +fields = { location = "varrock_south_west_mine" } + +[varrock_east_tin_mining] +template = "tin_ore_template" +capacity = 4 +fields = { location = "varrock_south_west_mine" } + +[varrock_silver_mining] +template = "silver_ore_template" +capacity = 3 +fields = { location = "varrock_south_west_mine" } + +[varrock_east_iron_mining] +template = "iron_ore_template" +capacity = 2 +fields = { location = "varrock_south_west_mine" } diff --git a/data/area/misthalin/varrock/varrock.npcs.toml b/data/area/misthalin/varrock/varrock.npcs.toml index f445aa4752..24f790a83e 100644 --- a/data/area/misthalin/varrock/varrock.npcs.toml +++ b/data/area/misthalin/varrock/varrock.npcs.toml @@ -221,6 +221,7 @@ examine = "She looks happy." [dealga] id = 11475 shop = "dealgas_scimitar_emporium" +area = "varrock_sword_shop" collision = "indoors" examine = "A shrewd-looking monkey salesman." @@ -644,11 +645,13 @@ examine = "Sells arrows." [sword_shopkeeper_varrock] id = 551 shop = "varrock_sword_shop" +area = "varrock_sword_shop" examine = "Ironically, makes a living from swords." [sword_shop_assistant_varrock] id = 552 shop = "varrock_sword_shop" +area = "varrock_sword_shop" examine = "Helps the shopkeeper sell swords." [cook_blue_moon_inn] diff --git a/data/bot/al_kharid.nav-edges.toml b/data/bot/al_kharid.nav-edges.toml new file mode 100644 index 0000000000..88d8b8aa61 --- /dev/null +++ b/data/bot/al_kharid.nav-edges.toml @@ -0,0 +1,30 @@ +edges = [ + { from = { x = 3283, y = 3331 }, to = { x = 3284, y = 3313 } }, # al_kharid_mine_north_entrance_to_west_path + { from = { x = 3284, y = 3313 }, to = { x = 3287, y = 3294 } }, # al_kharid_mine_west_path_to_south + { from = { x = 3287, y = 3294 }, to = { x = 3298, y = 3280 } }, # al_kharid_mine_west_path_to_entrance + { from = { x = 3298, y = 3280 }, to = { x = 3299, y = 3294 } }, + { from = { x = 3299, y = 3294 }, to = { x = 3300, y = 3311 } }, + { from = { x = 3298, y = 3280 }, to = { x = 3299, y = 3263 } }, # al_kharid_mine_entrance_to_mine_south + { from = { x = 3299, y = 3263 }, to = { x = 3294, y = 3242 } }, # al_kharid_mine_south_to_north_path + { from = { x = 3294, y = 3242 }, to = { x = 3278, y = 3228 } }, # al_kharid_north_path_to_crossroad + { from = { x = 3278, y = 3228 }, to = { x = 3268, y = 3228 } }, # al_kharid_crossroad_to_tollgate_north + { from = { x = 3278, y = 3228 }, to = { x = 3268, y = 3227 } }, # al_kharid_crossroad_to_tollgate + { from = { x = 3278, y = 3228 }, to = { x = 3280, y = 3216 } }, # al_kharid_crossroad_to_glider + { from = { x = 3267, y = 3228 }, to = { x = 3268, y = 3228 }, cost = 1, actions = [{ object = { option = "Pay-toll(10gp)", id = "toll_gate_al_kharid_north", x = 3268, y = 3228, success = { tile = { x = 3268, y = 3228 } } } }], requires = [{ inventory = [{ id = "coins", amount = 10 }] }] }, # lumbridge_tollgate_north_to_al_kharid_tollgate + { from = { x = 3268, y = 3228 }, to = { x = 3267, y = 3228 }, cost = 1, actions = [{ object = { option = "Pay-toll(10gp)", id = "toll_gate_al_kharid_north", x = 3268, y = 3228, success = { tile = { x = 3267, y = 3228 } } } }], requires = [{ inventory = [{ id = "coins", amount = 10 }] }] }, # al_kharid_tollgate_north_to_lumbridge_tollgate + { from = { x = 3267, y = 3227 }, to = { x = 3268, y = 3227 }, cost = 1, actions = [{ object = { option = "Pay-toll(10gp)", id = "toll_gate_al_kharid_south", x = 3268, y = 3227, success = { tile = { x = 3268, y = 3227 } } } }], requires = [{ inventory = [{ id = "coins", amount = 10 }] }] }, # lumbridge_tollgate_to_al_kharid + { from = { x = 3268, y = 3227 }, to = { x = 3267, y = 3227 }, cost = 1, actions = [{ object = { option = "Pay-toll(10gp)", id = "toll_gate_al_kharid_south", x = 3268, y = 3227, success = { tile = { x = 3267, y = 3227 } } } }], requires = [{ inventory = [{ id = "coins", amount = 10 }] }] }, # al_kharid_tollgate_to_lumbridge + { from = { x = 3268, y = 3227 }, to = { x = 3280, y = 3216 } }, # al_kharid_tollgate_to_glider + { from = { x = 3268, y = 3228 }, to = { x = 3280, y = 3216 } }, # al_kharid_tollgate_north_to_glider + { from = { x = 3280, y = 3216 }, to = { x = 3292, y = 3215 } }, # al_kharid_glider_to_musician + { from = { x = 3292, y = 3215 }, to = { x = 3300, y = 3198 } }, # al_kharid_musician_to_silk_path + { from = { x = 3300, y = 3198 }, to = { x = 3288, y = 3189 } }, # al_kharid_silk_path_to_scimitar_shop + { from = { x = 3280, y = 3216 }, to = { x = 3280, y = 3200 } }, # al_kharid_glider_to_west_shortcut + { from = { x = 3280, y = 3200 }, to = { x = 3288, y = 3189 } }, # al_kharid_west_shortcut_to_scimitar_shop + { from = { x = 3288, y = 3189 }, to = { x = 3282, y = 3185 } }, # al_kharid_scimitar_shop_to_furnace_entrance + { from = { x = 3280, y = 3200 }, to = { x = 3282, y = 3185 } }, # al_kharid_west_shortcut_to_furnace_to_entrance + { from = { x = 3282, y = 3185 }, to = { x = 3278, y = 3177 } }, # al_kharid_furnace_entrance_to_bank_crossroads + { from = { x = 3278, y = 3177 }, to = { x = 3276, y = 3168 } }, # al_kharid_bank_crossroad_to_entrance + { from = { x = 3278, y = 3177 }, to = { x = 3274, y = 3180 } }, # al_kharid_bank_crossroad_to_kebab_shop + { from = { x = 3276, y = 3168 }, to = { x = 3270, y = 3167 } }, # al_kharid_bank_entrance_to_bank +] \ No newline at end of file diff --git a/data/bot/combat.templates.toml b/data/bot/combat.templates.toml new file mode 100644 index 0000000000..8506fee067 --- /dev/null +++ b/data/bot/combat.templates.toml @@ -0,0 +1,40 @@ +[kill_chickens] +requires = [ + { skill = { id = "$skill", min = 1, max = 5 } }, +] +setup = [ + { equipment = { weapon = { id = "bronze_sword,bronze_dagger,bronze_scimitar" } } }, + { area = { id = "$location" } }, + { inventory = { id = "empty", min = 27 } }, +] +actions = [ + { interface = { option = "Select", id = "combat_styles:$style" } }, + { npc = { option = "Attack", id = "chicken*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "feather" }, + { item = "bones" }, + { item = "raw_chicken" }, + { skill = "$skill" } +] + +[kill_cows] +requires = [ + { combat_level = { min = 10 } }, + { skill = { id = "$skill", min = 5, max = 10 } }, +] +setup = [ + { equipment = { weapon = { id = "iron_sword,iron_dagger,iron_scimitar" } } }, + { area = { id = "$location" } }, + { inventory = { id = "empty", min = 27 } }, +] +actions = [ + { interface = { option = "Select", id = "combat_styles:style1" } }, + { npc = { option = "Attack", id = "cow*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "bones" }, + { item = "cowhide" }, + { item = "raw_beef" }, + { skill = "$skill" } +] \ No newline at end of file diff --git a/data/bot/cooking.templates.toml b/data/bot/cooking.templates.toml new file mode 100644 index 0000000000..9f18e36655 --- /dev/null +++ b/data/bot/cooking.templates.toml @@ -0,0 +1,43 @@ +[cooking_template] +requires = [ + { bank = { id = "$raw", amount = 28 } }, + { skill = { id = "cooking", min = "$level" } }, +] +setup = [ + { inventory = { id = "$raw", min = 28 } }, + { area = { id = "$location" } }, +] +actions = [ + { item_on_object = { id = "$raw", object = "$obj", success = { interface_open = { id = "dialogue_skill_creation" } } } }, + { interface = { option = "All", id = "skill_creation_amount:all" } }, + { continue = { id = "dialogue_skill_creation:choice1" } }, + { restart = { wait_if = { queue = { id = "cooking" } }, success = { inventory = { id = "$raw", max = 0 } } } } +] +produces = [ + { skill = "cooking" }, + { item = "$cooked" }, + { item = "$burnt" } +] + +# Beef has a different popup so can't use the normal template +[beef_template] +requires = [ + { bank = { id = "raw_beef", amount = 28 } }, + { skill = { id = "cooking", min = 1 } }, +] +setup = [ + { inventory = { id = "raw_beef", min = 28 } }, + { area = { id = "$location" } }, +] +actions = [ + { item_on_object = { id = "raw_beef", object = "$obj", success = { interface_open = { id = "dialogue_multi2" } } } }, + { continue = { option = "Continue", id = "dialogue_multi2:line2", success = { interface_open = { id = "dialogue_skill_creation" } } } }, + { interface = { option = "All", id = "skill_creation_amount:all" } }, + { continue = { id = "dialogue_skill_creation:choice1" } }, + { restart = { wait_if = { queue = { id = "cooking" } }, success = { inventory = { id = "raw_beef", max = 0 } } } } +] +produces = [ + { skill = "cooking" }, + { item = "beef" }, + { item = "burnt_meat" } +] diff --git a/data/bot/draynor.nav-edges.toml b/data/bot/draynor.nav-edges.toml new file mode 100644 index 0000000000..56a78a2ccd --- /dev/null +++ b/data/bot/draynor.nav-edges.toml @@ -0,0 +1,20 @@ +edges = [ + { from = { x = 3138, y = 3227 }, to = { x = 3119, y = 3228 } }, # draynor_east_to_jail_path_south + { from = { x = 3119, y = 3228 }, to = { x = 3105, y = 3238 } }, # draynor_path_south_to_west + { from = { x = 3105, y = 3238 }, to = { x = 3104, y = 3248 } }, # draynor_path_west_to_bank_crossroad + { from = { x = 3104, y = 3248 }, to = { x = 3093, y = 3245 } }, # draynor_bank_crossroad_to_bank + { from = { x = 3093, y = 3245 }, to = { x = 3079, y = 3249 } }, # draynor_bank_to_stalls + { from = { x = 3093, y = 3245 }, to = { x = 3099, y = 3246 } }, # draynor_bank_to_trees + { from = { x = 3104, y = 3248 }, to = { x = 3099, y = 3246 } }, # draynor_bank_crossroad_to_trees + { from = { x = 3079, y = 3249 }, to = { x = 3071, y = 3266 } }, # draynor_stalls_to_pigsty + { from = { x = 3079, y = 3249 }, to = { x = 3086, y = 3237 } }, # draynor_stall_to_willow_trees + { from = { x = 3079, y = 3249 }, to = { x = 3072, y = 3250 } }, # draynor_stalls_to_west_trees + { from = { x = 3079, y = 3249 }, to = { x = 3079, y = 3265 } }, # draynor_stalls_to_north_trees + { from = { x = 3093, y = 3245 }, to = { x = 3086, y = 3237 } }, # draynor_bank_to_willow_trees + { from = { x = 3086, y = 3237 }, to = { x = 3097, y = 3235 } }, # draynor_willow_trees_to_south + { from = { x = 3086, y = 3237 }, to = { x = 3086, y = 3231 } }, # draynor_willow_trees_to_fishing_spot + { from = { x = 3086, y = 3231 }, to = { x = 3097, y = 3235 } }, # draynor_fishing_spot_to_south + { from = { x = 3105, y = 3238 }, to = { x = 3097, y = 3235 } }, # draynor_jail_path_west_to_south + { from = { x = 3138, y = 3227 }, to = { x = 3153, y = 3216 } }, # draynor_east_path_to_swamp_north_wall + { from = { x = 3268, y = 3331 }, to = { x = 3283, y = 3331 } }, # varrock_al_kharid_crossroad_to_north_entrance +] \ No newline at end of file diff --git a/data/bot/firemaking.templates.toml b/data/bot/firemaking.templates.toml new file mode 100644 index 0000000000..af10753254 --- /dev/null +++ b/data/bot/firemaking.templates.toml @@ -0,0 +1,14 @@ +[firemaking] +requires = [ + { owns = { id = "$logs", min = 54 }} +] +setup = [ + { area = { id = "$location" } }, + { inventory = [{ id = "$logs", min = 27 }, { id = "tinderbox", min = 1 }] }, +] +actions = [ + { firemaking = { id = "$logs", area = "$location" } }, +] +produces = [ + { skill = "firemaking" } +] \ No newline at end of file diff --git a/data/bot/fishing.templates.toml b/data/bot/fishing.templates.toml new file mode 100644 index 0000000000..54ed9ba3c9 --- /dev/null +++ b/data/bot/fishing.templates.toml @@ -0,0 +1,51 @@ +[fish_crayfish] +requires = [ + { skill = { id = "fishing", min = 1 } }, +] +setup = [ + { inventory = [ + { id = "crayfish_cage" }, + { id = "empty", min = 27 } + ] }, + { area = { id = "$location" } }, +] +actions = [ + { npc = { option = "Cage", id = "$spot", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "raw_crayfish" }, + { skill = "fishing" } +] + +[fish_small_net] +setup = [ + { inventory = [ + { id = "small_fishing_net" }, + { id = "empty", min = 27 } + ] }, + { area = { id = "$location" } }, +] +actions = [ + { npc = { option = "Net", id = "$spot", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "raw_shrimp" }, + { item = "raw_anchovies" }, + { skill = "fishing" } +] + +[fish_bait] +setup = [ + { inventory = [ + { id = "fishing_rod" }, + { id = "feather", min = 52 }, + { id = "empty", min = 25 } + ] }, + { area = { id = "$location" } }, +] +actions = [ + { npc = { option = "Bait", id = "$spot", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { skill = "fishing" }, +] diff --git a/data/bot/fletching.templates.toml b/data/bot/fletching.templates.toml new file mode 100644 index 0000000000..039e0c19b6 --- /dev/null +++ b/data/bot/fletching.templates.toml @@ -0,0 +1,54 @@ +[fletching_arrow_shafts_template] +requires = [ + { bank = { id = "$logs", min = 27 } } +] +setup = [ + { area = { id = "$location" } }, + { inventory = [{ id = "knife" }, { id = "$logs", min = 27 }] }, +] +actions = [ + { item_on_item = { id = "knife", on = "$logs", success = { interface_open = { id = "dialogue_skill_creation" } } } }, + { interface = { option = "All", id = "skill_creation_amount:all" } }, + { continue = { id = "dialogue_skill_creation:choice1" } }, + { restart = { wait_if = [{ queue = { id = "fletching" } }], success = { inventory = { id = "empty", min = 26 } } } } +] +produces = [ + { item = "arrow_shafts" }, + { skill = "fletching" } +] + +[fletching_shortbow_template] +requires = [ + { bank = { id = "$logs", min = 27 } } +] +setup = [ + { area = { id = "$location" } }, + { inventory = [{ id = "knife" }, { id = "$logs", min = 27 }] }, +] +actions = [ + { item_on_item = { id = "knife", on = "$logs" } }, + { interface = { option = "All", id = "skill_creation_amount:all" } }, + { continue = { id = "dialogue_skill_creation:choice2", success = { queue = { id = "fletching" } } } }, + { restart = { success = { inventory = { id = "empty", min = 26 } } } } +] +produces = [ + { skill = "fletching" } +] + +[fletching_longbow_template] +requires = [ + { bank = { id = "$logs", min = 27 } } +] +setup = [ + { area = { id = "$location" } }, + { inventory = [{ id = "knife" }, { id = "$logs", min = 27 }] }, +] +actions = [ + { item_on_item = { id = "knife", on = "$logs", success = { queue = { id = "fletching_make_dialog" } } } }, + { interface = { option = "All", id = "skill_creation_amount:all" } }, + { continue = { id = "dialogue_skill_creation:choice3", success = { queue = { id = "fletching" } } } }, + { restart = { success = { inventory = { id = "empty", min = 26 } } } } +] +produces = [ + { skill = "fletching" } +] diff --git a/data/bot/lumbridge.nav-edges.toml b/data/bot/lumbridge.nav-edges.toml new file mode 100644 index 0000000000..b87efe7d63 --- /dev/null +++ b/data/bot/lumbridge.nav-edges.toml @@ -0,0 +1,142 @@ +edges = [ + { from = { x = 3236, y = 3218 }, to = { x = 3236, y = 3205 } }, # lumbridge_gate_south_to_village + { from = { x = 3236, y = 3218 }, to = { x = 3243, y = 3209 } }, # lumbridge_gate_south_to_church + { from = { x = 3236, y = 3205 }, to = { x = 3243, y = 3209 } }, # lumbridge_south_village_to_church + { from = { x = 3236, y = 3218 }, to = { x = 3250, y = 3212 } }, # lumbridge_gate_south_to_behind_church + { from = { x = 3250, y = 3212 }, to = { x = 3258, y = 3206 } }, # lumbridge_behind_church_to_church_fishing_spot + { from = { x = 3236, y = 3205 }, to = { x = 3231, y = 3203 }, cost = 5, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3234, y = 3203, success = { object = { id = "door_720_opened", x = 3233, y = 3203 } } } }, { tile = { x = 3231, y = 3203 } }] }, # lumbridge_south_village_to_bobs_axes + { from = { x = 3231, y = 3203 }, to = { x = 3236, y = 3205 }, cost = 5, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3234, y = 3203, success = { object = { id = "door_720_opened", x = 3233, y = 3203 } } } }, { tile = { x = 3236, y = 3205 } }] }, # lumbridge_bobs_axes_to_south_village + { from = { x = 3236, y = 3205 }, to = { x = 3231, y = 3197 }, cost = 5, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3235, y = 3199, success = { object = { id = "door_720_opened", x = 3235, y = 3198 } } } }, { tile = { x = 3231, y = 3197 } }] }, # lumbridge_south_village_to_south_range_house + { from = { x = 3231, y = 3197 }, to = { x = 3236, y = 3205 }, cost = 5, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3235, y = 3199, success = { object = { id = "door_720_opened", x = 3235, y = 3198 } } } }, { tile = { x = 3236, y = 3205 } }] }, # lumbridge_south_range_house_to_south_village + { from = { x = 3236, y = 3205 }, to = { x = 3244, y = 3190 } }, # lumbridge_south_village_to_graveyard_exit + { from = { x = 3244, y = 3190 }, to = { x = 3253, y = 3200 } }, # lumbridge_graveyard_exit_to_behind_graveyard + { from = { x = 3250, y = 3212 }, to = { x = 3253, y = 3200 } }, # lumbridge_behind_church_to_behind_graveyard + { from = { x = 3258, y = 3206 }, to = { x = 3253, y = 3200 } }, # lumbridge_church_fishing_spot_to_behind_graveyard + { from = { x = 3205, y = 3209, level = 2 }, to = { x = 3205, y = 3209, level = 1 }, cost = 1, actions = [{ object = { option = "Climb-down", id = "lumbridge_staircase_top", x = 3204, y = 3207, success = { tile = { level = 1 } } } }] }, # lumbridge_castle_2nd_floor_south_stairs_to_1st_floor + { from = { x = 3208, y = 3219, level = 2 }, to = { x = 3205, y = 3209, level = 2 } }, # lumbridge_castle_2nd_floor_bank_to_south_stairs + { from = { x = 3205, y = 3209 }, to = { x = 3205, y = 3209, level = 1 }, cost = 1, actions = [{ object = { option = "Climb-up", id = "lumbridge_staircase", x = 3204, y = 3207, success = { tile = { level = 1 } } } }] }, # lumbridge_castle_ground_floor_south_stairs_to_1st_floor + { from = { x = 3205, y = 3209 }, to = { x = 3208, y = 3210 } }, # lumbridge_castle_ground_floor_south_stairs_to_kitchen_corridor + { from = { x = 3208, y = 3210 }, to = { x = 3215, y = 3216 } }, # lumbridge_castle_kitchen_corridor_to_castle_south_entrance + { from = { x = 3208, y = 3210 }, to = { x = 3211, y = 3214 } }, # lumbridge_castle_kitchen_corridor_to_kitchen + { from = { x = 3215, y = 3216 }, to = { x = 3222, y = 3218 } }, # lumbridge_castle_south_entrance_to_courtyard_south + { from = { x = 3205, y = 3209, level = 1 }, to = { x = 3205, y = 3209 }, cost = 1, actions = [{ object = { option = "Climb-down", id = "lumbridge_castle_staircase_south_middle", x = 3204, y = 3207, success = { tile = { level = 0 } } } }] }, # lumbridge_castle_1st_floor_south_stairs_to_ground_floor + { from = { x = 3205, y = 3209, level = 1 }, to = { x = 3205, y = 3209, level = 2 }, cost = 1, actions = [{ object = { option = "Climb-up", id = "lumbridge_castle_staircase_south_middle", x = 3204, y = 3207, success = { tile = { level = 2 } } } }] }, # lumbridge_castle_1st_floor_south_stairs_to_2nd_floor + { from = { x = 3209, y = 3205 }, to = { x = 3199, y = 3218 } }, # lumbridge_castle_grounds_south_to_tower_west + { from = { x = 3199, y = 3218 }, to = { x = 3184, y = 3225 } }, # lumbridge_castle_tower_west_to_yew_trees + { from = { x = 3199, y = 3218 }, to = { x = 3195, y = 3236 } }, # lumbridge_castle_tower_west_to_tree_patch + { from = { x = 3184, y = 3225 }, to = { x = 3168, y = 3221 } }, # lumbridge_castle_yew_trees_to_yew_trees_west + { from = { x = 3184, y = 3225 }, to = { x = 3195, y = 3236 } }, # lumbridge_castle_yew_trees_to_tree_patch + { from = { x = 3226, y = 3214 }, to = { x = 3227, y = 3214 }, cost = 3, actions = [{ object = { option = "Open", id = "door_627_closed", x = 3226, y = 3214, success = { object = { id = "door_627_opened", x = 3227, y = 3214 } } } }] }, # lumbridge_south_tower_to_ground_floor + { from = { x = 3227, y = 3214 }, to = { x = 3229, y = 3214, level = 1 }, cost = 1, actions = [{ object = { option = "Climb-up", id = "36768", x = 3229, y = 3213, success = { tile = { level = 1 } } } }] }, # lumbridge_south_tower_ground_floor_to_1st_floor + { from = { x = 3229, y = 3214, level = 1 }, to = { x = 3229, y = 3214, level = 2 }, cost = 1, actions = [{ object = { option = "Climb-up", id = "36769", x = 3229, y = 3213, success = { tile = { level = 2 } } } }] }, # lumbridge_south_tower_1st_floor_to_2nd_floor + { from = { x = 3229, y = 3214, level = 1 }, to = { x = 3229, y = 3214 }, cost = 1, actions = [{ object = { option = "Climb-down", id = "36769", x = 3229, y = 3213, success = { tile = { level = 0 } } } }] }, # lumbridge_south_tower_1st_floor_to_ground_floor + { from = { x = 3229, y = 3214, level = 2 }, to = { x = 3229, y = 3214, level = 1 }, cost = 1, actions = [{ object = { option = "Climb-down", id = "36770", x = 3229, y = 3213, success = { tile = { level = 1 } } } }] }, # lumbridge_south_tower_2nd_floor_to_1st_floor + { from = { x = 3236, y = 3219 }, to = { x = 3236, y = 3225 } }, # lumbridge_gate_north_to_bridge_west + { from = { x = 3236, y = 3225 }, to = { x = 3230, y = 3232 } }, # lumbridge_bridge_west_to_unstable_house + { from = { x = 3236, y = 3225 }, to = { x = 3253, y = 3225 } }, # lumbridge_bridge_west_to_bridge_east + { from = { x = 3253, y = 3225 }, to = { x = 3263, y = 3222 } }, # lumbridge_bridge_east_to_trees_east + { from = { x = 3253, y = 3225 }, to = { x = 3260, y = 3228 } }, # lumbridge_bridge_east_to_east_crossroad + { from = { x = 3260, y = 3228 }, to = { x = 3263, y = 3222 } }, # lumbridge_east_crossroad_to_trees_east + { from = { x = 3260, y = 3228 }, to = { x = 3259, y = 3239 } }, + { from = { x = 3259, y = 3239 }, to = { x = 3258, y = 3250 } }, + { from = { x = 3259, y = 3239 }, to = { x = 3256, y = 3247 } }, + { from = { x = 3256, y = 3247 }, to = { x = 3251, y = 3252 } }, + { from = { x = 3235, y = 3261 }, to = { x = 3250, y = 3266 } }, # lumbridge_bridge_north_to_cow_entrance + { from = { x = 3251, y = 3252 }, to = { x = 3250, y = 3266 } }, # lumbridge_goblin_path_north_to_cow_entrance + { from = { x = 3251, y = 3252 }, to = { x = 3258, y = 3250 } }, + { from = { x = 3250, y = 3266 }, to = { x = 3240, y = 3280 } }, # lumbridge_cow_entrance_to_cow_path + { from = { x = 3240, y = 3280 }, to = { x = 3238, y = 3295 } }, # lumbridge_cow_path_to_chicken_entrance + { from = { x = 3238, y = 3295 }, to = { x = 3235, y = 3295 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_235_closed", x = 3237, y = 3295, success = { object = { id = "gate_235_opened", x = 3236, y = 3295 } } } }, { tile = { x = 3235, y = 3295 } }] }, # lumbridge_chicken_entrance_to_chicken_pen + { from = { x = 3235, y = 3295 }, to = { x = 3238, y = 3295 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_237_closed", x = 3237, y = 3296, success = { object = { id = "gate_235_opened", x = 3236, y = 3295 } } } }, { tile = { x = 3238, y = 3295 } }] }, # lumbridge_chicken_pen_to_chicken_entrance + { from = { x = 3250, y = 3266 }, to = { x = 3255, y = 3266 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_241_closed", x = 3252, y = 3266, success = { object = { id = "gate_239_opened", x = 3253, y = 3267 } } } }, { tile = { x = 3255, y = 3266 } }] }, # lumbridge_cow_entrance_to_cow_field + { from = { x = 3255, y = 3266 }, to = { x = 3250, y = 3266 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_239_closed", x = 3252, y = 3267, success = { object = { id = "gate_239_opened", x = 3253, y = 3267 } } } }, { tile = { x = 3250, y = 3266 } }] }, # lumbridge_cow_field_to_cow_entrance + { from = { x = 3244, y = 3190 }, to = { x = 3241, y = 3176 } }, # lumbridge_graveyard_exit_to_swamp_path + { from = { x = 3241, y = 3176 }, to = { x = 3239, y = 3160 } }, # lumbridge_swamp_path_to_swamp_cross_roads + { from = { x = 3244, y = 3190 }, to = { x = 3231, y = 3188 } }, # lumbridge_graveyard_exit_to_rats_north_east + { from = { x = 3244, y = 3190 }, to = { x = 3231, y = 3181 } }, # lumbridge_graveyard_exit_to_rats_south_east + { from = { x = 3241, y = 3176 }, to = { x = 3231, y = 3188 } }, # lumbridge_swamp_path_to_rats_north_east + { from = { x = 3241, y = 3176 }, to = { x = 3231, y = 3181 } }, # lumbridge_swamp_path_to_rats_south_east + { from = { x = 3231, y = 3188 }, to = { x = 3231, y = 3181 } }, # lumbridge_swamp_rats_north_east_to_south_east + { from = { x = 3239, y = 3160 }, to = { x = 3228, y = 3148 } }, # lumbridge_swamp_cross_roads_to_copper_mine + { from = { x = 3228, y = 3148 }, to = { x = 3225, y = 3147 } }, # lumbridge_swamp_copper_mine_to_tin_mine + { from = { x = 3228, y = 3148 }, to = { x = 3221, y = 3156 } }, # lumbridge_swamp_copper_mine_to_east_mine + { from = { x = 3239, y = 3160 }, to = { x = 3221, y = 3156 } }, # lumbridge_swamp_cross_roads_to_east_mine + { from = { x = 3239, y = 3160 }, to = { x = 3243, y = 3155 } }, # lumbridge_swamp_cross_roads_to_fishing_spot + { from = { x = 3221, y = 3156 }, to = { x = 3201, y = 3155 } }, # lumbridge_swamp_east_mine_to_urhney_house + { from = { x = 3201, y = 3155 }, to = { x = 3181, y = 3153 } }, # lumbridge_swamp_urhney_house_to_water_altar + { from = { x = 3181, y = 3153 }, to = { x = 3161, y = 3151 } }, # lumbridge_swamp_water_altar_to_south + { from = { x = 3161, y = 3151 }, to = { x = 3148, y = 3149 } }, # lumbridge_swamp_south_to_west_mine + { from = { x = 3148, y = 3149 }, to = { x = 3146, y = 3147 } }, # lumbridge_swamp_west_mine_to_mithril_mine + { from = { x = 3148, y = 3149 }, to = { x = 3146, y = 3150 } }, # lumbridge_swamp_west_mine_to_coal_mine + { from = { x = 3148, y = 3149 }, to = { x = 3146, y = 3167 } }, # lumbridge_swamp_west_mine_to_west_1 + { from = { x = 3146, y = 3167 }, to = { x = 3143, y = 3186 } }, # lumbridge_swamp_west_1_to_2 + { from = { x = 3143, y = 3186 }, to = { x = 3142, y = 3203 } }, # lumbridge_swamp_west_2_to_west_camp + { from = { x = 3142, y = 3203 }, to = { x = 3136, y = 3219 } }, # lumbridge_swamp_west_camp_to_west_wall + { from = { x = 3142, y = 3203 }, to = { x = 3153, y = 3216 } }, # lumbridge_swamp_west_camp_to_north_wall + { from = { x = 3136, y = 3219 }, to = { x = 3153, y = 3216 } }, # lumbridge_swamp_west_wall_to_north_wall + { from = { x = 3136, y = 3219 }, to = { x = 3138, y = 3227 } }, # lumbridge_swamp_west_wall_to_draynor_east_path + { from = { x = 3136, y = 3219 }, to = { x = 3119, y = 3228 } }, # lumbridge_swamp_west_wall_to_draynor_jail_path_south + { from = { x = 3222, y = 3218 }, to = { x = 3226, y = 3214 } }, # lumbridge_courtyard_south_to_tower_door + { from = { x = 3222, y = 3218 }, to = { x = 3222, y = 3219 } }, # lumbridge_courtyard_south_to_north + { from = { x = 3222, y = 3218 }, to = { x = 3236, y = 3218 } }, # lumbridge_courtyard_south_to_gate_north + { from = { x = 3222, y = 3219 }, to = { x = 3236, y = 3219 } }, # lumbridge_courtyard_north_to_gate_north + { from = { x = 3236, y = 3218 }, to = { x = 3236, y = 3219 } }, # lumbridge_courtyard_gate_south_to_gate_north + { from = { x = 3222, y = 3218 }, to = { x = 3209, y = 3205 } }, # lumbridge_courtyard_south_to_grounds_south + { from = { x = 3230, y = 3232 }, to = { x = 3222, y = 3241 } }, # lumbridge_unstable_house_to_general_store_east + { from = { x = 3222, y = 3241 }, to = { x = 3217, y = 3241 }, cost = 6, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3219, y = 3241, success = { object = { id = "door_720_opened", x = 3218, y = 3241 } } } }, { tile = { x = 3217, y = 3241 } }] }, # lumbridge_general_store_east_to_general_store + { from = { x = 3217, y = 3241 }, to = { x = 3222, y = 3241 }, cost = 6, actions = [{ object = { option = "Open", id = "door_720_closed", x = 3219, y = 3241, success = { object = { id = "door_720_opened", x = 3218, y = 3241 } } } }, { tile = { x = 3222, y = 3241 } }] }, # lumbridge_general_store_to_general_store_east + { from = { x = 3222, y = 3241 }, to = { x = 3226, y = 3245 } }, # lumbridge_general_store_east_to_trees_west + { from = { x = 3226, y = 3245 }, to = { x = 3235, y = 3261 } }, # lumbridge_west_village_trees_to_bridge_north + { from = { x = 3222, y = 3241 }, to = { x = 3204, y = 3247 } }, # lumbridge_general_store_east_to_task_building + { from = { x = 3217, y = 3241 }, to = { x = 3204, y = 3247 } }, # lumbridge_general_store_to_task_building + { from = { x = 3204, y = 3247 }, to = { x = 3194, y = 3247 } }, # lumbridge_task_building_to_fishing_shop_entrance + { from = { x = 3194, y = 3247 }, to = { x = 3172, y = 3239 } }, # lumbridge_fishing_shop_entrance_to_west_path + { from = { x = 3194, y = 3247 }, to = { x = 3195, y = 3236 } }, # lumbridge_fishing_shop_entrance_to_tree_patch + { from = { x = 3194, y = 3247 }, to = { x = 3195, y = 3251 } }, # lumbridge_fishing_shop_entrance_to_fishing_shop + { from = { x = 3172, y = 3239 }, to = { x = 3157, y = 3234 } }, # lumbridge_west_path_to_ham_path + { from = { x = 3172, y = 3239 }, to = { x = 3184, y = 3225 } }, # lumbridge_west_path_to_yew_trees + { from = { x = 3172, y = 3239 }, to = { x = 3168, y = 3221 } }, # lumbridge_west_path_to_yew_trees_west + { from = { x = 3168, y = 3221 }, to = { x = 3153, y = 3216 } }, # lumbridge_yew_trees_west_to_swamp_north_wall + { from = { x = 3157, y = 3234 }, to = { x = 3138, y = 3227 } }, # lumbridge_ham_path_to_draynor_east_path + { from = { x = 3222, y = 3241 }, to = { x = 3219, y = 3247 } }, # lumbridge_general_store_east_to_west_crossroad + { from = { x = 3219, y = 3247 }, to = { x = 3212, y = 3247 } }, # lumbridge_west_crossroad_to_combat_hall_east_entrance + { from = { x = 3212, y = 3247 }, to = { x = 3204, y = 3247 } }, # lumbridge_combat_hall_east_entrance_to_task_building + { from = { x = 3212, y = 3247 }, to = { x = 3212, y = 3250 } }, # lumbridge_combat_hall_entrance_to_east + { from = { x = 3204, y = 3247 }, to = { x = 3204, y = 3250 } }, # lumbridge_task_building_to_combat_hall_west + { from = { x = 3226, y = 3245 }, to = { x = 3225, y = 3252 } }, # lumbridge_village_trees_west_to_smiths_south + { from = { x = 3225, y = 3252 }, to = { x = 3222, y = 3255 } }, # lumbridge_smiths_south_to_west + { from = { x = 3222, y = 3255 }, to = { x = 3218, y = 3255 } }, # lumbridge_smiths_west_to_smiths_crossroad + { from = { x = 3218, y = 3255 }, to = { x = 3219, y = 3247 } }, # lumbridge_smiths_crossroad_to_west_crossroad + { from = { x = 3218, y = 3255 }, to = { x = 3223, y = 3260 } }, + { from = { x = 3223, y = 3260 }, to = { x = 3235, y = 3261 } }, + { from = { x = 3218, y = 3255 }, to = { x = 3217, y = 3268 } }, + { from = { x = 3218, y = 3255 }, to = { x = 3220, y = 3258 } }, + { from = { x = 3217, y = 3268 }, to = { x = 3213, y = 3277 } }, + { from = { x = 3213, y = 3277 }, to = { x = 3199, y = 3279 } }, + { from = { x = 3199, y = 3279 }, to = { x = 3190, y = 3283 } }, + { from = { x = 3190, y = 3283 }, to = { x = 3190, y = 3294 } }, + { from = { x = 3190, y = 3294 }, to = { x = 3186, y = 3307 } }, + { from = { x = 3186, y = 3307 }, to = { x = 3177, y = 3315 } }, + { from = { x = 3177, y = 3315 }, to = { x = 3177, y = 3316 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_235_closed", x = 3177, y = 3316, success = { object = { id = "gate_235_opened", x = 3177, y = 3315 } } } }] }, + { from = { x = 3177, y = 3316 }, to = { x = 3177, y = 3315 }, cost = 0, actions = [{ object = { option = "Open", id = "gate_235_closed", x = 3177, y = 3316, success = { object = { id = "gate_235_opened", x = 3177, y = 3315 } } } }] }, + { from = { x = 3177, y = 3316 }, to = { x = 3179, y = 3326 } }, + { from = { x = 3179, y = 3326 }, to = { x = 3178, y = 3334 } }, + { from = { x = 3178, y = 3334 }, to = { x = 3177, y = 3345 } }, + { from = { x = 3177, y = 3345 }, to = { x = 3177, y = 3357 } }, + { from = { x = 3177, y = 3357 }, to = { x = 3178, y = 3364 } }, + { from = { x = 3178, y = 3364 }, to = { x = 3184, y = 3370 } }, + { from = { x = 3225, y = 3252 }, to = { x = 3219, y = 3247 } }, # lumbridge_smiths_south_to_west_crossroad + { from = { x = 3253, y = 3225 }, to = { x = 3245, y = 3238 } }, # lumbridge_bridge_east_to_goblins_river + { from = { x = 3253, y = 3225 }, to = { x = 3254, y = 3239 } }, # lumbridge_bridge_east_to_goblins + { from = { x = 3254, y = 3239 }, to = { x = 3258, y = 3250 } }, # lumbridge_goblins_to_trees_east + { from = { x = 3254, y = 3239 }, to = { x = 3245, y = 3238 } }, # lumbridge_goblins_to_river + { from = { x = 3254, y = 3239 }, to = { x = 3251, y = 3252 } }, # lumbridge_goblins_to_path_north + { from = { x = 3251, y = 3252 }, to = { x = 3235, y = 3261 } }, # lumbridge_goblins_path_north_to_bridge_north + { from = { x = 3235, y = 3261 }, to = { x = 3240, y = 3249 } }, # lumbridge_bridge_north_to_goblin_fishing_spot + { from = { x = 3251, y = 3252 }, to = { x = 3240, y = 3249 } }, # lumbridge_goblin_path_north_to_fishing_spot + { from = { x = 3240, y = 3249 }, to = { x = 3245, y = 3238 } }, # lumbridge_goblin_fishing_spot_to_river + { from = { x = 3260, y = 3228 }, to = { x = 3254, y = 3239 } }, # lumbridge_east_crossroad_to_goblins + { from = { x = 3260, y = 3228 }, to = { x = 3267, y = 3227 } }, # lumbridge_east_crossroad_to_tollgate + { from = { x = 3260, y = 3228 }, to = { x = 3267, y = 3228 } }, # lumbridge_east_crossroad_to_tollgate_north + { from = { x = 3260, y = 3228 }, to = { x = 3263, y = 3222 } }, # lumbridge_east_crossroad_to_trees_east +] diff --git a/data/bot/mining.templates.toml b/data/bot/mining.templates.toml new file mode 100644 index 0000000000..8512fa59d6 --- /dev/null +++ b/data/bot/mining.templates.toml @@ -0,0 +1,127 @@ +[copper_ore_template] +requires = [ + { skill = { id = "mining", min = 1, max = 15 } }, +] +setup = [ + { inventory = [{ id = "steel_pickaxe,iron_pickaxe,bronze_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "copper_rocks*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "copper_ore" }, + { skill = "mining" } +] + +[tin_ore_template] +requires = [ + { skill = { id = "mining", min = 1, max = 15 } }, +] +setup = [ + { inventory = [{ id = "steel_pickaxe,iron_pickaxe,bronze_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "tin_rocks*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "tin_ore" }, + { skill = "mining" } +] + +[clay_template] +requires = [ + { skill = { id = "mining", min = 1, max = 15 } }, +] +setup = [ + { inventory = [{ id = "steel_pickaxe,iron_pickaxe,bronze_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "clay_rocks*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "clay" }, + { skill = "mining" } +] + +[iron_ore_template] +requires = [ + { skill = { id = "mining", min = 15, max = 40 } }, +] +setup = [ + { inventory = [{ id = "adamant_pickaxe,mithril_pickaxe,steel_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "iron_rocks*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "iron_ore" }, + { skill = "mining" } +] + +[silver_ore_template] +requires = [ + { skill = { id = "mining", min = 20, max = 40 } }, +] +setup = [ + { inventory = [{ id = "adamant_pickaxe,mithril_pickaxe,steel_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "silver_rocks*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "silver_ore" }, + { skill = "mining" } +] + +[coal_template] +requires = [ + { skill = { id = "mining", min = 30, max = 55 } }, +] +setup = [ + { inventory = [{ id = "rune_pickaxe,adamant_pickaxe,mithril_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "coal_rocks*", delay = 10, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "coal" }, + { skill = "mining" } +] + +[gold_ore_template] +requires = [ + { skill = { id = "mining", min = 40, max = 60 } }, +] +setup = [ + { inventory = [{ id = "rune_pickaxe,adamant_pickaxe,mithril_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "gold_rocks*", delay = 15, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "gold_ore" }, + { skill = "mining" } +] + +[mithril_ore_template] +requires = [ + { skill = { id = "mining", min = 55, max = 70 } }, +] +setup = [ + { inventory = [{ id = "dragon_pickaxe,rune_pickaxe,adamant_pickaxe", usable = true }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Mine", id = "mithril_rocks*", delay = 15, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "mithril_ore" }, + { skill = "mining" } +] diff --git a/data/bot/prayer.bots.toml b/data/bot/prayer.bots.toml new file mode 100644 index 0000000000..d0aff6244f --- /dev/null +++ b/data/bot/prayer.bots.toml @@ -0,0 +1,31 @@ +[bury_bones_in_bank] +capacity = 5 +requires = [ + { bank = { id = "bones", min = 28 } }, +] +setup = [ + { inventory = [{ id = "bones", min = 28 }] }, +] +actions = [ + { interface = { option = "Bury", id = "inventory:inventory:bones" } }, + { restart = { wait_if = [{ variable = { id = "bone_delay", min = 0, default = -1 } }], success = { inventory = { id = "empty", min = 28 } } } } +] +produces = [ + { skill = "prayer" } +] + +[bury_bones] +capacity = 5 +requires = [ + { inventory = [{ id = "bones", min = 28 }] }, +] +setup = [ + { inventory = [{ id = "bones", min = 28 }] }, +] +actions = [ + { interface = { option = "Bury", id = "inventory:inventory:bones" } }, + { restart = { wait_if = [{ variable = { id = "bone_delay", min = 0, default = -1 } }], success = { inventory = { id = "empty", min = 28 } } } } +] +produces = [ + { skill = "prayer" } +] diff --git a/data/bot/teleport.shortcuts.toml b/data/bot/teleport.shortcuts.toml new file mode 100644 index 0000000000..8a968cea77 --- /dev/null +++ b/data/bot/teleport.shortcuts.toml @@ -0,0 +1,85 @@ +#[ring_of_duelling_equipped] +#type = "shortcut" +#weight = 10 +#requires = [ +# { equips = "ring_of_duelling_*" } +#] +#actions = [ +# { option = "Castle Wars", interface = "equipment:inventory" }, +#] +#produces = [ +# { location = "castle_wars_teleport" } +#] +# +#[ring_of_duelling] +#type = "shortcut" +#weight = 8 +#requires = [ +# { carries = "ring_of_duelling_*" } +#] +#actions = [ +# { option = "Rub", interface = "inventory:inventory" }, +# { option = "Select", interface = "choice:line1" }, +#] +#produces = [ +# { location = "castle_wars_teleport" } +#] + +[teleport_varrock] +weight = 125 +requires = [ + { skill = { id = "magic", min = 25 } }, + { inventory = [ + { id = "fire_rune", min = 1 }, + { id = "air_rune", min = 3 }, + { id = "law_rune", min = 1 } + ] }, + { variable = { id = "spellbook_config", equals = 0, default = 0 } }, +] +actions = [ + { interface = { option = "Cast", id = "modern_spellbook:varrock_teleport" } }, + { wait = { ticks = 5 } }, +] +produces = [ + { area = "varrock_teleport" } +] + +[teleport_varrock_via_bank] +weight = 150 +requires = [ + { skill = { id = "magic", min = 25 } }, + { variable = { id = "spellbook_config", equals = 0, default = 0 }}, + { bank = [ + { id = "fire_rune", min = 1 }, + { id = "air_rune", min = 3 }, + { id = "law_rune", min = 1 }, + ]}, +] +actions = [ + { go_to = { nearest = "bank" } }, + { object = { option = "Use-quickly", id = "bank_booth*", success = { interface_open = { id = "bank" } } } }, + { interface = { option = "Withdraw-1", id = "bank:inventory:fire_rune" } }, + { interface = { option = "Withdraw-X", id = "bank:inventory:air_rune" } }, + { enter = { int = 3 } }, + { interface = { option = "Withdraw-1", id = "bank:inventory:law_rune" } }, + { interface = { option = "Cast", id = "modern_spellbook:varrock_teleport" } }, + { wait = { ticks = 5 } }, +# { wait = { location = "varrock_teleport" } }, +] +produces = [ + { area = "varrock_teleport" } +] + +[teleport_lumbridge] +weight = 200 +requires = [ + { variable = { id = "spellbook_config", equals = 0, default = 0 } }, + { clock = { id = "home_teleport_timeout", max = 0 } }, +] +actions = [ + { interface = { option = "Cast", id = "modern_spellbook:lumbridge_home_teleport" } }, + { wait = { ticks = 25 } }, +] +produces = [ + { area = "lumbridge_teleport" } +] diff --git a/data/bot/thieving.templates.toml b/data/bot/thieving.templates.toml new file mode 100644 index 0000000000..88c3644e73 --- /dev/null +++ b/data/bot/thieving.templates.toml @@ -0,0 +1,20 @@ +[pickpocketting_template] +requires = [ + { skill = { id = "constitution", min = 100 } }, + { skill = { id = "thieving", min = 1, max = 10 } }, +] +setup = [ + { inventory = { id = "empty", min = 1 } }, + { area = { id = "$location" } }, +] +actions = [ + { npc = { option = "Pickpocket", id = "$npc" } }, + { restart = { wait_if = [ + { variable = { id = "delay", min = 0, default = -1 } }, + { clock = { id = "stunned", min = 0 } } + ], success = { skill = { id = "constitution", max = 20 } } } } +] +produces = [ + { item = "coins" }, + { skill = "thieving" } +] \ No newline at end of file diff --git a/data/bot/varrock.nav-edges.toml b/data/bot/varrock.nav-edges.toml new file mode 100644 index 0000000000..699881b4ec --- /dev/null +++ b/data/bot/varrock.nav-edges.toml @@ -0,0 +1,64 @@ +edges = [ + { from = { x = 3213, y = 3428 }, to = { x = 3223, y = 3429 } }, # varrock_teleport_to_centre_east + { from = { x = 3213, y = 3428 }, to = { x = 3200, y = 3429 } }, # varrock_teleport_to_centre_west + { from = { x = 3213, y = 3428 }, to = { x = 3211, y = 3420 } }, # varrock_teleport_to_centre_south + { from = { x = 3200, y = 3429 }, to = { x = 3186, y = 3430 } }, # varrock_centre_west_to_west_bank_south_entrance + { from = { x = 3186, y = 3430 }, to = { x = 3186, y = 3435 } }, # varrock_west_bank_south_entrance_to_bank_south + { from = { x = 3186, y = 3430 }, to = { x = 3171, y = 3429 } }, # varrock_west_bank_south_entrance_to_romeo_crossroad + { from = { x = 3171, y = 3429 }, to = { x = 3162, y = 3420 } }, # varrock_west_romeo_crossroads_to_oak_tress + { from = { x = 3162, y = 3420 }, to = { x = 3150, y = 3416 } }, # varrock_west_oak_trees_to_cat_house + { from = { x = 3150, y = 3416 }, to = { x = 3135, y = 3416 } }, # varrock_west_cat_house_to_barbarian_path + { from = { x = 3135, y = 3416 }, to = { x = 3128, y = 3407 } }, # varrock_west_barbarian_path_to_air_altar_ruins + { from = { x = 3128, y = 3407 }, to = { x = 2841, y = 4830 }, cost = 3, actions = [{ object = { option = "null", id = "air_altar_ruins", x = 3126, y = 3404, success = { tile = { x = 2841, y = 4830 } } } }] }, # varrock_west_air_altar_ruins_to_air_altar + { from = { x = 2841, y = 4830 }, to = { x = 2841, y = 4829 } }, # varrock_west_air_altar_to_exit + { from = { x = 2841, y = 4829 }, to = { x = 3128, y = 3407 }, cost = 3, actions = [{ object = { option = "Enter", id = "air_altar_portal", x = 2841, y = 4828, success = { tile = { x = 3128, y = 3407 } } } }] }, # varrock_west_air_altar_exit_to_ruins + { from = { x = 3304, y = 3474 }, to = { x = 2655, y = 4831 }, cost = 3, actions = [{ object = { option = "Enter", id = "earth_altar_ruins", x = 3305, y = 3473, success = { tile = { x = 2655, y = 4831 } } } }] }, # varrock_east_earth_altar_ruins_to_altar + { from = { x = 2655, y = 4831 }, to = { x = 2655, y = 4830 } }, # varrock_east_earth_altar_to_exit + { from = { x = 2655, y = 4830 }, to = { x = 3304, y = 3474 }, cost = 3, actions = [{ object = { option = "Enter", id = "earth_altar_portal", x = 2655, y = 4829, success = { tile = { x = 3304, y = 3474 } } } }] }, # varrock_east_earth_altar_exit_to_ruins + { from = { x = 3254, y = 3422 }, to = { x = 3265, y = 3428 } }, # varrock_east_bank_to_dirt_crossroad + { from = { x = 3265, y = 3428 }, to = { x = 3280, y = 3428 } }, # varrock_east_dirt_crossroad_to_east_crossroad + { from = { x = 3280, y = 3428 }, to = { x = 3287, y = 3414 } }, # varrock_east_crossroad_to_east_path_south + { from = { x = 3287, y = 3414 }, to = { x = 3291, y = 3399 } }, # varrock_east_path_to_south + { from = { x = 3291, y = 3399 }, to = { x = 3293, y = 3384 } }, # varrock_east_path_south_to_east_border + { from = { x = 3280, y = 3428 }, to = { x = 3286, y = 3442 } }, # varrock_east_crossroad_to_path_north + { from = { x = 3286, y = 3442 }, to = { x = 3287, y = 3457 } }, # varrock_east_path_to_north + { from = { x = 3287, y = 3457 }, to = { x = 3296, y = 3462 } }, # varrock_east_path_north_to_sawmill_crossroad + { from = { x = 3296, y = 3462 }, to = { x = 3304, y = 3474 } }, # varrock_east_sawmill_crossroad_to_eath_altar_ruins + { from = { x = 3265, y = 3428 }, to = { x = 3246, y = 3429 } }, # varrock_east_dirt_crossroad_to_bank_crossroad + { from = { x = 3246, y = 3429 }, to = { x = 3254, y = 3422 } }, # varrock_east_bank_crossroad_to_east_bank + { from = { x = 3230, y = 3430 }, to = { x = 3246, y = 3429 } }, # varrock_east_armour_shop_to_bank_crossroad + { from = { x = 3230, y = 3430 }, to = { x = 3223, y = 3429 } }, # varrock_east_armour_shop_to_centre_east + { from = { x = 3238, y = 3295 }, to = { x = 3238, y = 3304 } }, # lumbridge_chicken_entrance_to_varrock_south_split + { from = { x = 3238, y = 3304 }, to = { x = 3251, y = 3319 } }, # varrock_south_split_to_al_kharid_path_west + { from = { x = 3238, y = 3304 }, to = { x = 3238, y = 3320 } }, # varrock_south_split_to_al_kharid_path_west + { from = { x = 3238, y = 3320 }, to = { x = 3240, y = 3336 } }, # varrock_south_split_to_al_kharid_path_west + { from = { x = 3251, y = 3319 }, to = { x = 3268, y = 3331 } }, # varrock_al_kharid_path_west_to_crossroads + { from = { x = 3283, y = 3331 }, to = { x = 3295, y = 3334 } }, # al_kharid_north_entrance_to_varrock_north_west + { from = { x = 3295, y = 3334 }, to = { x = 3304, y = 3335 } }, # varrock_al_kharid_north_west_to_crossroad + { from = { x = 3295, y = 3334 }, to = { x = 3299, y = 3346 } }, # varrock_al_kharid_north_west_to_south_east_path + { from = { x = 3304, y = 3335 }, to = { x = 3299, y = 3346 } }, # varrock_al_kharid_north_west_crossroad_to_south_east_path + { from = { x = 3299, y = 3346 }, to = { x = 3298, y = 3359 } }, # varrock_south_east_path_to_east_mine_path_south + { from = { x = 3298, y = 3359 }, to = { x = 3295, y = 3372 } }, # varrock_south_east_mine_south_to_path + { from = { x = 3295, y = 3372 }, to = { x = 3286, y = 3371 } }, # varrock_south_east_mine_path_to_east + { from = { x = 3286, y = 3371 }, to = { x = 3293, y = 3384 } }, # varrock_south_east_mine_to_border + { from = { x = 3295, y = 3372 }, to = { x = 3293, y = 3384 } }, # varrock_south_east_mine_path_to_border + { from = { x = 3211, y = 3420 }, to = { x = 3210, y = 3407 } }, # varrock_centre_south_to_dirt_crossroads + { from = { x = 3210, y = 3407 }, to = { x = 3211, y = 3395 } }, # varrock_south_dirt_crossroads_to_blue_moon_inn + { from = { x = 3210, y = 3407 }, to = { x = 3209, y = 3399 } }, + { from = { x = 3210, y = 3407 }, to = { x = 3209, y = 3399 } }, + { from = { x = 3209, y = 3399 }, to = { x = 3207, y = 3399 }, cost = 1, actions = [{ object = { option = "Open", id = "door_444_closed", x = 3209, y = 3399, success = { object = { id = "door_444_opened", x = 3208, y = 3399 } } } }, { tile = { x = 3207, y = 3399 } }] }, + { from = { x = 3211, y = 3395 }, to = { x = 3209, y = 3399 } }, # varrock_sword_shop + { from = { x = 3211, y = 3395 }, to = { x = 3211, y = 3381 } }, # varrock_blue_moon_inn_to_south_border + { from = { x = 3211, y = 3381 }, to = { x = 3214, y = 3367 } }, # varrock_south_border_to_dark_mages + { from = { x = 3214, y = 3367 }, to = { x = 3226, y = 3352 } }, # varrock_south_dark_mages_to_south + { from = { x = 3214, y = 3367 }, to = { x = 3206, y = 3376 } }, + { from = { x = 3211, y = 3381 }, to = { x = 3206, y = 3376 } }, + { from = { x = 3206, y = 3376 }, to = { x = 3197, y = 3373 } }, + { from = { x = 3197, y = 3373 }, to = { x = 3191, y = 3367 } }, + { from = { x = 3197, y = 3373 }, to = { x = 3184, y = 3370 } }, + { from = { x = 3191, y = 3367 }, to = { x = 3184, y = 3370 } }, + { from = { x = 3226, y = 3352 }, to = { x = 3228, y = 3337 } }, # varrock_dark_mages_to_south_fields_path + { from = { x = 3228, y = 3337 }, to = { x = 3240, y = 3336 } }, # varrock_south_fields_path_to_stile + { from = { x = 3240, y = 3336 }, to = { x = 3254, y = 3333 } }, # varrock_south_stile_to_path + { from = { x = 3254, y = 3333 }, to = { x = 3268, y = 3331 } }, # varrock_south_stile_to_al_kharid_crossroad +] \ No newline at end of file diff --git a/data/bot/walking.setups.toml b/data/bot/walking.setups.toml new file mode 100644 index 0000000000..8b8be2acb0 --- /dev/null +++ b/data/bot/walking.setups.toml @@ -0,0 +1,23 @@ +#[run_to_varrock] +#type = "activity" +#capacity = 1 +#setup = [ +# { variable = "movement", value = "run" } +#] +#actions = [ +# { go_to = "varrock_teleport" } +#] +#produces = [ +# { location = "varrock_teleport" } +#] + +[toggle_run] +requires = [ + { variable = { id = "movement", equals = "walk", default = "walk" } } +] +actions = [ + { interface = { option = "Turn Run mode on", id = "energy_orb:run_background" } } +] +produces = [ + { variable = "movement" } +] diff --git a/data/bot/woodcutting.templates.toml b/data/bot/woodcutting.templates.toml new file mode 100644 index 0000000000..d67587e148 --- /dev/null +++ b/data/bot/woodcutting.templates.toml @@ -0,0 +1,63 @@ +[normal_tree_template] +requires = [ + { skill = { id = "woodcutting", min = 1, max = 15 } }, +] +setup = [ + { inventory = [{ id = "steel_hatchet,iron_hatchet,bronze_hatchet" }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Chop down", id = "tree*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "logs" }, + { skill = "woodcutting" } +] + +[oak_tree_template] +requires = [ + { skill = { id = "woodcutting", min = 15, max = 30 } }, +] +setup = [ + { inventory = [{ id = "mithril_hatchet,steel_hatchet" }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Chop down", id = "oak*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "oak_logs" }, + { skill = "woodcutting" } +] + +[willow_tree_template] +requires = [ + { skill = { id = "woodcutting", min = 30, max = 60 } }, +] +setup = [ + { inventory = [{ id = "rune_hatchet,adamant_hatchet,mithril_hatchet,steel_hatchet" }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Chop down", id = "willow*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "willow_logs" }, + { skill = "woodcutting" } +] + +[yew_tree_template] +requires = [ + { skill = { id = "woodcutting", min = 60, max = 75 } }, +] +setup = [ + { inventory = [{ id = "dragon_hatchet,rune_hatchet,adamant_hatchet" }, { id = "empty", min = 27 }] }, + { area = { id = "$location" } }, +] +actions = [ + { object = { option = "Chop down", id = "yew*", delay = 5, success = { inventory = { id = "empty", max = 0 } } } }, +] +produces = [ + { item = "yew_logs" }, + { skill = "woodcutting" } +] diff --git a/data/bot_names.txt b/data/bot_names.txt new file mode 100644 index 0000000000..bee8541a41 --- /dev/null +++ b/data/bot_names.txt @@ -0,0 +1,3052 @@ +Aang +AarrWoof +Abe +Abra +Abrams +AbyssAce +AccelAce +Accident +AccurateA +AceAdam +ActivePass +ActualAce +Adaki +Adam +Admiral Flag +AdmiralAce +Adorable +Aedrin +Aeon +Aerodactyl +Aether +Afiko +AfternoonA +Agent Zero +AgentZero +AgileeAce +Aidene +Aika +AimAssist +AimBotBob +AirBorne +Aisha +Aiwei +Alakazam +Alan +Alex +AlignedSkew +AllBrawn +Alles +AlligatorC +AlloAce +AlmostThere +AlosaurAce +AlpacaAl +Alpha +Alpha Wolf +AlphaTest +AlphaWolf1 +Alphamind +AlpineAce +AlwaysNot +Amak +Amber Light +Amethyst +Amit +Amon +AmplifiedA +Amy +AnacondaA +Anais +AncientMod +Anezka +Angel +Angel Wings +AngelfishA +AngoraAce +AngryAnvil +AngulatedA +AnkyloAce +AnnihilateA +AnniversarA +AnonymousA +Anselm +AntFly +Anzih +ApatosaurA +ApexHunter +Apollo +Appa +ApplePie +AprilFool +Aptitude +Aqua +Aquarius H2O +ArabianTort +Aragon +Arbok +Arby +ArcAngel +Arcane +Arcanine +Arche +ArcheoA +Archer +Archer Aim +Archer Mark +Archie +Archmother +Archpreist +Arctic +Arctic panda +ArcticAce +ArcticHippo +ArcticSeal +Ares +Argue +Aries Ram +ArnoldArno +ArowanaAce +Aroyo +Arpy +Arr +ArrowStorm +ArsonistA +Arthur +Articuno +Artillery +Arwen +Aryan +Ash +AshMaker +Ashe +Asher +AsphaltAce +Aspho +AssassinEdge +Assire +Astra +Astro +Astro Boy +Astrofire +Athena +Auckes +AudaciousA +AugustAce +Aura +Aureus +Aurora +Austin +AutoMatic +Avatar +AvenueAce +Awthi +Axe +Axe Throw +AxeMan +AxisAngel +AxleAce +Axy +Azn +Azula +Azulon +Azure Sky +Babylon +BackendBob +Bactrian +Baited +BakersDozn +Bakshi +BalanceBob +BalancedSkw +BalancedTip +Balin +Ballet +BananaBob +BananasB +Bandit Mask +Bao +Barbarian +Barbski +Bard Song +BarelyNo +Bark +Bark beetle +BaronBob +Baroness +BarraBob +BarryonixB +Bartok +BasalBill +Basalt +BaseLine99 +BashBob +BasicBob +BasiclyBob +Basilisk Eye +BaskinShrk +BasmatiB +BasssBob +BastionBoss +Battle Axe +Battlecraft +Baxter +Baylan +Bazz +BeaconBob +BeamingBob +Bearie +BeatBob +Bebop +Beedrill +BehemothBoss +Beifong +Belee +BellsHinge +Bellsprout +Bemoan +Ben +BendableStf +BengalBob +BentUnbent +BergBerger +Beri +Bernard +BerryBest +Berserker +Beta Strike +BetaBlaze +BetaTester +BettaBob +Bewail +Bian Whip +Bicker +Biervat +BigBrain +BigChungus +BigOof +Bilbo +Billy +Binary Code +BinaryBeast +BioChem +Birdie +Birthday99 +BiryaniB +BisqueBob +Black Sun +BlackBerry +BlackBird +BlackOps99 +Blade Dance +Blade Unit +BladeRunner +BlankBob +BlankFull +BlasterBob +Blastoise +Blaze +Blaze King +BlazeBoy +BlazeFury +Blessed +BlessedDmnd +BlistorBlue +BlitzKrieg +BlockheadB +Blonde +Blood Moon +Blood Wings +Blubber +Blue +Blue Falcon +BlueBarry +BlueBird99 +Blueberry +BluegillB +BluffBob +BluntBlade +Bo Staff +Bob +BobTail +BoilingBob +Bojio +BolaThrower +BoldBrian +Bolin +BoltBrian +BombSquad +Bomber Run +Bonaro +BoneheadB +BonfireB +BonkersBen +BonkersBob +Boom +BoostedBob +Boqin +Borderland +BorealBob +Boromir +Bosco +BottomBob +BoulengBoul +BoulevardB +BouncyRock +BoundUnbound +BowMaster +BowSprit +BowWow +BowfinBob +Bowie Knife +BoxTurtle +BoysenB +BrachioB +Bradley +Brady +BradyBrady +BrakeCheck +BranchBob +BrandedBob +BraveBen +BraveChickn +Brawl +BreachBob +Break +BreakerBob +BreamBob +Brebis +Breem +Breeze +BreezeBlue +Bregalad +Brick Wall +BrightDim +BrightEye +BrittleMall +Broadsword +BrokenHero +BrokenWheel +BrontoB +Bronze Medal +BrothBob +Brouhaha +BrownRice +Brutal +Brutalblaze +Buccaneer +BudgieBob +BuffPlease +BuffaloYak +Buffering +Bug Smasher +BugAbuser +BuildBob +Bulbasaur +BullFrog +BullShark +BullsEye99 +Bumble Fly +Bumi +BumpySmooth +BunBoy +BunchBanana +BunkerBeast +BunnySnail +BurmeseStar +BurnerBob +BurstBrian +BussinBob +Busy +ButteBill +Buttercup +Butterfly +Butterfree +Bydou +Byte Boss +ByteBandit +CPU King +Cacl +CadenceC +Caedes +Cafi +CaimanGator +Calculated +CalculatedL +CaliRoll +Calico +CalicoKid +CalmStorm +CamShaftC +CamelCarl +CanTorGiant +CanaryC +Cancer Crab +Candle +CannonKing +CantDie +Canteloupe +Canyon +CanyonCarl +Capricorn +Captain Hook +CaptainC +CarchCarl +CarettCaret +CarnoCarl +Carp +CarpCarl +Carpet +CaseCarl +CashmerC +Casual +CasualCarl +Cat Owl +CatNip +Caterpie +Caterpillar +CatfishC +Catgator +Cavalry Rush +Cavil +Cecil +Cele +Celebration +Celeste +Celestial +Centaur Run +CenterCore +CenteredOff +CentralC +Centurion +Cerberus Dog +Chain Whip +ChainBreaker +Chan +ChancellorC +Chansey +Chaos +ChaosOrder +CharCoal +ChargeChad +Charizard +Charmander +CharmedCurs +Charmeleon +Charmy +ChasmCharlie +ChassisC +CheatEngine +CheetahSloth +CheloChelyon +CherryChad +ChersChersi +ChiChampion +ChickenTurt +Chickpea +ChiefChad +Chikori +ChillCharli +ChillDay +ChillPill +ChillyChili +ChillyWilly +Chimera Mix +ChimeraCore +Chipette +ChopperChad +Chorab +ChowderC +Chris +Chronos +ChryChrys +Ciaran +CichlidCarl +Cicis +CinderSoul +CipherCode +Cipmunk +CircleLine +CirclingC +CircuitC +Cirtus +CitadelCore +Clanker +Clarko +ClaspedFist +ClawedCat +Claymore Hit +ClearCarl +Cleaver Chop +Clefable +Clefairy +ClementyneC +ClemmClemm +Cleric Heal +ClickHeads +ClientSide +CliffCarl +Clonas +Cloud +Cloud Nine +CloudArchC +CloudCompC +CloudNine +ClownCarl +Cloyster +ClutchKing +Cobalt Blue +CobraStrike +CockatooC +CodCarl +Code Master +CodeCrusher +ColdFeet +Collin +Colonel Rank +ColonelCarl +ColossusC +Comet Tail +CommanderC +CompileCarl +Complain +CompletelyNo +CompyCarl +ConcreteC +Conduit +ConquerorC +ConsommeC +ControlFreak +CooCooKid +CoolBeans +CoolCat +CoolDude +Copege +Copper Wire +CoreCarl +CoreyCore +Corn +CornerKing +CornishRex +Corporal Hit +Corsair Sail +CoryhtoCory +CourageC +CouscousC +CovertCore +CozyCharlie +CrackCarl +CrackerCarl +CragKing +Cranberry +CrankCase +CrappieCar +CrawlSprint +Crazy +CrazyCarl +CreatureFeat +CreviceC +Crimson Tide +CrimsonWolf +Crinfrid +CriticalC +CrocodileD +CrookedStrt +CrossHair +CrossbowKid +CrowLook +CrucialCal +CrusherC +Crutek +Cruz +Cry +CryoCrunch +Crypton +CrystalClear +CtrlAltWin +Cubone +CucumberC +Cue +CuoraCuora +CurrentCarl +Cursed Apple +CursedCharm +CurvedFlat +Cushi +Cutlass Raid +CutlassCap +CutterCole +Cuttlefish +Cyber Punk +Cyber Wolf +CyberNinja +CyberPunk +CyclemCycle +CycloneC +Cyclops Eye +Cynthia +DBAdmin +DabOnEm +Dagger Stab +DaggerDuke +Dagonet +DakotaRaptr +DaleDan +Dalton +Dalum +DamnedBlss +Dampen +Dandelion +Dando +DangerSafe +Dans +Dao Saber +Darae +DaringDave +Dark +Dark Viper +DarkLight99 +DarkOps +DarkPhoenix +Darkness +Darth +DashDot +Data +Data Miner +DataDemon +DateDave +Davina +Dawn +DawnDan +DayOff +DayOneD +DazzlingD +Dead Eye +DeadAlive +DeadCenter +Dean +Death Star +DeathDealer +Debate +DebugDan +DecemberD +Decimation +DecimatorD +DeepDan +DefaultDan +DefenderDX +Definitely5 +DeinoDemo +DeiroDeirc +DellDave +Delos +Delta Force +DeltaForce +Delusion +Demeter +DemoDay +DemoDisc +DemoExpert +DemolisherD +Demolition +Demon +Demon Spawn +Deni +DenseDave +DeployDave +DepthDave +Deputy Badge +DermoDermo +Dermoth +Desafe +DesertTort +DesktopDan +Destroyed +DestroyerD +DesyncDave +Detective Q +DevOpsDave +DevonRex +DewItNow +Dewgong +Diamond +Diamond Mind +DicerDan +Digi Mind +DigitalDoom +Diglett +DimBright +DimDave +DinosaurD +DinosaurKom +DiplodocusD +DipsoDipso +Dire +DirectDan +Disease +Disha +Dispute +DisscusDan +DitchDave +Ditto +Divine +DivineMortal +Dizzee +Dodrio +Doduo +DolphinDan +DominatorD +Donza +Doorman +DopeDan +DopeyDan +DorritosDew +DotDash +DoubtfulSure +Dove +DoveDan +DownUp99 +DraculaDrac +Dragon Fury +Dragon Moose +DragonFire +DragonIgua +Dragonair +Dragonite +Drakon +Dratini +Draugy +Dread +Dream +Dream Killer +Dreazd +Drewen +DriftKing +Drifter +DriveShaft +Droid +Dromedary +Drowzee +Druid Tree +DryWater +Dryme +Dubsi +Duck +DuckbillDan +Duelist Draw +Dugtrio +DukeDan +DullDan +DullShiny +DuskDave +Dust Devil +DustUp +Duva +Dwarf Mine +Dye +DyingLive +Dynamo +DynamoStatic +Dyns +EagleEye99 +EarlEd +EarlyAcces +EarlyBird +Earth +EasyEd +EasygoingE +Eceth +Echidna +Echo +Eclipse Day +EdgeNode +Edgecrusher +EdmontoE +EdmontoniE +Eeli +Eevee +Eggman +EgyptianEgy +EightEric +EightSix +EightTen +Eiko +Eimis +Ekans +ElbowEd +ElbowLeech +Elder +ElderBerry +Electabuzz +ElectricE +Electrode +Elendil +ElephantKoi +ElevenNine +Elf Arrow +Eligor +EliteEdge +Ella +Ellie +Elliot +Ellunes +ElongatdEl +Elrond +EmbEmydid +Ember +EmberEyes +Emerald +Emerald Eye +Emoer +Emote +Empha +EmptyEd +EmptyFilled +EmptySpark +Enaka +EndGame99 +EnergeticL +EnergizedE +Engineer Fix +Enigma +EnigmaEdge +Envy +Eomund +Eothain +Eowyn +EpsilonE +EradicatorE +Erasold +Erehk +Erste +Erulasteil +EscarpmentE +Eskem +Espeon +EssenceEd +EssentialE +Eternal +Ethereal +Eural +Evan +EvenUneven +EveningEd +Evu +ExactEd +ExecutorE +Exeggcute +Exeggutor +ExploitLord +ExplosiveE +EzClap +Ezpz +Ezra +F2PFrank +Fabi +Fade +Fairfax +Faith +FakeTruth +Falas +FalconSight +Fang +Fantm +Farfetch'd +Farid +Farmer Plow +FastFred +FastLane +FastSloth +Fathey +Faun +FearlessFred +Fearow +FebruaryF +FeebleMighty +FeelsBad +Felicia +Felix +Fencer Parry +Feran +FettucineF +FieldFare +FieryIce +FiestaF +FigFrank +Fighter Jet +FighterSwing +FilledEmpty +FinalBoss +Finally +Finch +Fire +Fire Storm +Fire ant +FireStarter +Firefly +FirmFloppy +FirmWobly +FirstClass +Fisher Hook +Fisherman +Fishie +Fishopotamus +FissureF +FiveFinn +FiveSeven +FiveThree +FlaatbackF +Flail Spin +FlailFury +Flakey +Flame +FlameKnight +Flamestar +FlareFrank +Flareon +Flash Bang +FlashFast +FlashFred +FlashPoint +FlatCurved +FlatRough +Flem +FlexiRigid +FlexibleRig +FloorFrank +FloppyFirm +FluffyFinn +Flury +FlyFrank +Flying Bison +FocusFrank +Fog Horn +FogCompF +FoolFrank +FootLong +Foox +ForcefulWeek +ForestFinn +Forie +ForstensF +FortressF +FortuneUnf +FosseFrank +FounderFred +FourFred +FourSix +FourTwo +FoxhoundFox +FpsCounter +Fracas +FragilTough +Frame +FrameDrop +Frank +FrankFred +FrankFurter +Frankenstein +Frankie +Fraser +FreeToPlay +FreeTrapped +FreebieF +FreewayF +FreezeFrame +FreshPrince +FridayFun +FriedRice +Friuti +Frodo +FrogLizard +FrogTurtle +FrontendF +Frost +Frost Bite +FrostBite +Frostbite +Frosty +FrostyFeet +FrozenFood +Fukiya Dart +FullBlank +FullStackF +FullTower +FullVoid +FundamentalF +FunnelCloud +FurBall +FurryFred +FuturePast +Fuzzy +GPU Beast +GachaGame +Gaia +Gainey +Galadreil +Galaxy Map +GalaxyBrain +GaleForce +Galid +GamePadGary +GamePreview +Gamma +Gamma Ray +GammaGhost +GamonGary +Gandalf +GapGary +Gar +GarGary +Gardeni +Garry +Gaspar +Gastly +GaugeGary +Gaur +Gaze +GearHead +GeckoSnake +Geist +Gemini Twin +General Star +GeneralG +Gengar +Genna +GeoGeoemy +GeocGeoche +Geodude +Geoff +Gerad +Geralt +GetGood +GetRekt +Gez +GharialCaim +Ghazan +Ghost Rider +Ghost spider +GhostOps +GhostReaper +GhostToast +Ghoul Feast +GhoulLiving +Giant Step +Gibbery +GigaGary +Gilbo +Gilly +Gimli +Giselle +GitGud +Gladiator +Glaive Sweep +GlareGary +Glass Canon +GleamTeam +GlenGary +GlitchKing +GlitterGuy +Gloom +GlossyMatte +GlowingGus +Gloz +GnarlyGary +Goat +Goblin Raid +GodMode99 +Golbat +Gold +Gold Rush +GoldFinch +Goldeen +GoldfishG +Golduck +Golem +GoliathGrim +Gollum +Gong +GoodFight +GoodGame99 +GoofyGary +GooseBerry +GophGopher +GopherTort +GorgeGus +GourdGary +GrandGary +GrapGrapt +Grape +GrapeGary +Graveler +Graves +GreatWhite +GreekGreek +Green +Green Arrow +GreenSea +GrenadeGuy +Grenadier +Grey Talon +GridGary +Gridley +Griffin Wing +GriffinGuard +Grimer +Gripe +Groan +GroundZero +Grounder +Grouse +GrouseGary +Growl +Growlithe +Grumble +Grunt +GuanacoGus +Guandao Spin +GuardianGod +GulchGreg +GullyGabe +GumboGary +Gun Club +GunSlinger +Gunbon +Gunner Aim +Gunslinger +Gupples +GuppyGary +Gus +GustGary +Gustav +GutsynGary +Guzma +Gwynrael +Gyarados +Gyatso +Hack Smith +HackerHal +HaddockH +Hades +HadrosaurH +Haggard +Hail Mary +Halberd Chop +HalberdHero +HalfWay +HalibutHal +Halp +Ham Slice +Hama +Hammer Time +HammerHead +HammerTime +Hampe +Hana +Hanbo Short +Hano +Hanson +HappySorrow +HardHardy +HardScope +HardSoft +HardlyYes +HareTurtle +Harmony +HatingLove +Haunter +Hawk +Hawk Eye +HawkVision +Hawkmoth +HawksbillH +Hawky +HaxorMan +Haze +Hazi +HeadHunter +HeartHank +HeartHarry +Heaven +HeavyFeather +Helicaze +Hellz +Hera +HereThere +HermanHerm +Hero +HeroZero +Hex Editor +HexHavoc +Hexen +Hexo +Hidden King +HiddenNeck +HierHiero +HighPingGod +HighlandH +HighwayH +HillHank +Hippo cow +Hippy +Hitmonchan +Hitmonlee +Hog monkey +Holiday +Holliday +HolyAxe +HolyBow +HolyCross +HolyDagger +HolyGrail +HolyMace +HolyShield +HolySword +HolyUnholy +HomHomopus +HomHompous +HomHomscutm +HomoHomo +HomopHomo2 +HomopusHomo +HonestHank +HoneyDew99 +HootnannyH +Hopium +Hoplite Wall +Horsea +HorsfieldsH +HotDog99 +HotHank +Hotkeys +Houndmaster +HourZero +Howard +Howl +Hubbub +HuionHank +Hullabaloo +HumanPlayer +HumpDay +Hunter Trap +HunterHank +Huntsman +HurricaneH +Hydra Head +HydraHead +HyperDrive +HyperHit +Hypno +IAmLegend +IballEye +Ice +Ice Dragon +IceBerg +IceCold +IceCold99 +IceCube +IceStorm +Iceberg +IgnitePro +Igor +IguanaDrgn +IguanadonI +IllMoves +Ilona +Immola +ImmortalDie +ImmortalMan +ImperialI +ImpotentPot +ImprssImprs +InOut99 +IncognitoI +IndiIndote +IndianStar +Infantry +InfernoIvan +Infernus +InputLag +InsaneIan +Inspector G +InstantIan +InvaderIvan +InvincibleI +Iroh +Iron Fist +Iron Will +IronFist +IronJaw +IronSky +Isacc +Isaknda +Iscan +Isidor +Isildur +IslandGiant +Ivy +Ivysaur +Izumi +JackJones +Jackato +Jacob +Jade Stone +Jaguar +Jaii +Jake +Jamboree +Jan +JanuaryJim +JasmineJ +Jawn +Jaws +Jay +JayBird +Jayce +Jaylen +Jayson +Jayvon +Jebaited +Jeffe +Jeimzu +Jennica +Jenny +Jere +Jeremie +Jeremy +Jerome +Jerry +Jessica +Jessie +JesterJack +JetStream +Jetsun +Jezebel +Jezura +Jian Sword +Jigglypuff +Jimmerik +Jinora +JitterBug +Jo Stick +Joan +Joao +Jobless +Joe +Joffrey +JoggingWalk +JokerJim +JoltJohn +Jolteon +Jon +Jonathan +Jord +Joris +Jos +Jose +JoyStickJoe +Judas +JuggernautJ +Juked +JulyJim +JumpySeat +JuneBug +JuneJay +JungleJim +Juntas +JustAbout +Jutte Guard +Jynx +Kabuto +Kabutops +Kadabra +Kadett +Kakuna +Kaladin +Kalten +Kalyt +Kama Hook +KanbanKen +Kangaskhan +Kappa +Kappa123 +KappaCrush +Karl +Karma +Karmy +Kat +Katana Slash +KatanaKing +Katara +KataraMai +Kate +Kathos +Katrin +Katya +KeenEye +Kelvin +KempRidley +Ken +Kennet +Kenny +Kerboros +Kerfuffle +KetchupKid +KeyKevin +KeyboardCat +Khamul +Khandro +Kickback +Kierzo +Killaz +Killer Whale +KillerKurt +Killzone +KindaSort +KindleKid +KingCool +KingKurt +KingKyle +Kingler +Kingly +Kinn +KinyKinyxs +Kirito +KittyKat +Kjell +Klas +KleinmanK +Klop +KnifeMaster +Knight +Knight Guard +KnightKen +KnollKen +Knor +Knuckles +Koalaotter +Koffing +KoiKen +Kolik +Kolton +Komfortable +KomodoDrag +Koray +Korra +Kort +Krabby +Kraken Deep +KrakenKill +Krieg +Krill +Krox +Kube Node +Kukri Slice +Kunai Throw +Kurtz +Kusarigama +Kyle +Kyoketsu +Kyoshi +Kyros +Kyurem +Kyza +LUL +Lady Geist +Lady Zodiac +LadyLiberty +LadyVictoria +LagSwitch +LaggingLou +LaidBackLou +LambdaLion +Lambert +Lament +LampeoL +Lance Charge +LanceLord +Lane +LaneLord +LapTopLuke +Lapras +LargeOof +Larry +Lash +Laskari +Lasota +LastStand +Lathow +Latty +LaunchDay +Layla +LazyLuke +LeafyGreen +LeatherBack +Leen +LeftRight99 +Legacy +Legionnaire +Legions +Legolas +Leito +Leo Lion +Leon +LeopardLeo +Leorex +LepidLepid +LethargeticE +Letho +LevelLuke +LevelUnlevl +LeviathanL +Liam +Libra Scale +Lickitung +Light +LightDark99 +LightningL +Lily +LineCircle +LinguineL +Linus +Lionfish +LiteralLee +Liva +LivingDead +LizardBeetle +LizardToad +LlamaLuke +LoadingBar +Lob +Lockhart +LocoLou +LoggerHead +LogicLord +Loki +Lone Wolf +LonewolfLeo +Long Feng +LongHair +Longsword +LooseTight +LootBoxLou +LooterLuke +LopsidedSqr +LordLuke +Loredo +LostWhisper +Lothe +LoudMouse +Louis +LoveBird +LovingHate +LowFpsLord +LowerLevel +LowlandLou +Lucifer +LuckShot +LuckyThirtn +LuckyUnluck +Luiqis +Luiz +Luke +Lukey +LuminousL +Luna +Lunar Phase +Lynx Titan +Lyric +Macao +MacaroniM +MacawMax +Macaws +Mace Swing +MaceKnight +MachSpeed +Machamp +Machete Hack +Machoke +Machop +MackerelM +MadMike +Maddie +MadnessSane +Maelstrom +Maffers +Mage Fool +Magenta Mist +Magicnite +Magikarp +Magmar +Magnemite +Magnetic +Magneton +MaiMaia +Maikeru +MainCharac +MainMike +MaineCoon +Majke +Major Order +MajorMike +Makav +MakiMike +Mako +MakoMax +MalaMalacho +MalaMalacl +Malget +MalleableU +MambaBlack +MandarinM +Mankey +Manks +ManoManour +ManouMano +ManrikiChain +Mantis +ManualMax +ManxMax +MapTurtle +Marauder +MarauderM +MarchMad +Marcus +MargMargin +MarginatdM +Margot +Maria +MarkmanMax +Marksman Hit +Marowak +Marshal Law +MarshalM +MasterMax +MataMata +Mathas +Mathcore +Matoya +Matrix Neo +MatrixMage +MatteGlossy +Matthew +MaureMaure +Maverick +Max Power +MaxChill +MaxLevel99 +MayFlower +MaybeSo +MaybeYes +McCaine +McGinnis +MeadowM +MeasureM +Medic Bag +Medusa Gaze +Mega +MegaOof +MegaPunch +MegalodonM +MelanMelano +Melc +Melee +Melissa +MellowMike +Melo +Melon +MelonMan +Mendoza +MeowMix +Meowster +Meowth +MerchantGold +Merry +MesaMax +MeshMike +MetaStrike +Metal Head +Metapod +Meteor Hit +Meteor Link +MeterMike +Mew +Mewtwo +MicroATX +MicroRaptor +MidTierM +MidTower +MiddleWeek +MidnightM +Mighty +MightyFeeble +Miles +Miley +Milky Way +Mime +Mina +MineSweeper +Miner Pick +Ming +MiniPC +Minir +MinnowMike +Minotaur +MinuteMan +Mirage +Miriam +MissileMan +Mist Walker +Mistime +Misty +Mo +Moan +MoatMike +ModernOld +Moeldi +MohairMax +MoleRat +MollyMike +Molnar +Moltres +MomentMike +MondayMan +Monk Fist +Monk Spade +MonkaS +MonsterMash +Moon +Moon Beam +Moon Lord +Moon Walker +Morax +MoreMornie +Morena +Moril +MorningM +MortalDiv +MortarMan +MotorMouth +Mottle +MoundMike +MountainM +Mourn +MourningM +MouseAccel +MouseMatt +MovingStill +Moxie +MtnDewMe +MuMaster +MudTurtle +Muk +MummyAlive +MunchkinM +MuskTurtle +MuskieMax +MustardMan +MyBad +Myron +MysteryMan +Mystic +Naga +Naginata +Nanci +Narto +Nashala +Natalie +Natasha +Naura +Nazgul +NearlyNear +Nebula +Nebula Gas +Necron +NegavNegev +NeiR +Neits +Nellie +Nelly +Nelus +Neon +Neon Dash +Neon Knight +NerfThis +NetAdminN +NetCaster +NetNinja +NeverYes +NewHistory +Newbie +NewbieBob +Nexo +NiceShot +Nicholas +Nidoking +Nidoqueen +Nidoran♀ +Nidoran♂ +Nidorina +Nidorino +Night +Night Hawk +NightHawk +NightOwl99 +NigiriNed +Nims +NineEleven +NineNick +NineSeven +Ninetales +Ninja Star +Nite +Nitpick +Nitro Speed +NitroNova +Niwa +NoBrain +NoCapFr +NoScope360 +NoSkinNed +NoYes99 +NobleNed +Noch +NodosaursN +NonneOne +Noob +NoodleNed +NoonNed +Norilsk +Norsu +NotABot123 +NotAbsolute +Nova Blast +NovaBlast +NovemberN +NowThen +NuNinja +NuchaNucha +NucleusN +Nuke +NullFilled +NullKnight +Numa +Nunchaku +NuttyNed +OGPlayer +OMEGALUL +Oathkeeper +ObliviatorO +Oceanic +Oceanus +OctoberO +Octopus +OcultaOcul +Oddish +Odin +Odrin +OffCentered +OffOn99 +OftenNo +Ogre Crush +OkBoomer +Olcan +OldNews +Ole +OliveRidley +Omanyte +Omastar +Omega +Omega End +OmegaOps +OmicronOne +Omid +OnOff99 +OnPurpose +OneNone +OneThree +OneWill +Onix +Onyx +Onyx Black +OofSize +OpPleaseNo +OpenOscar +OpeningO +OrangeOscar +Orbit King +OrbitingO +Orc Smash +OrderChaos +Origami +OrzoOscar +OscarOllie +Oshme +Osku +Otio +OtterPenguin +Otto +OuachOuach +OutIn99 +Outer Planes +Outlaw Gun +Outplayed +OverPower +OwOSniper +OwlWatch +PachyPete +PachycephP +Pack Leader +PackerPatr +PaddlefishP +PaellaP +Paid +Paige +Pain Train +PaintedTurt +Paladin Oath +PaltPaltho +Paly +Pammi +PancakePan +Panics +Pano +Papu +ParaPete +Parab +Paradox +ParakeetP +Paras +ParasaurP +Parasect +ParkwayPat +ParrotBeak +ParrotPete +PartiallyYes +PartridgeP +PartyTime +PashmineP +PassiveActv +PastFutur +Pasta +PastaPete +PatchDay +Patheon +Patron +Pax +PayDay +PayToWin +Peabody +PeaceThreat +PeacefulWar +PeachPit +PeakPete +PearPete +Pearl +Pearl White +PedalPro +PeerToPeer +Pegasus Fly +Pehle +PennePete +Pentapus +PepeHands +Pepega +PerchPat +PerhapsNo +Persian +PersianPat +Phalanx +Phantom +Phantom Pain +PhantomFox +PhantomPhan +Phaser +PheasantP +PhiPhantom +Philippa +PhoPhil +Phoenix +Phoenix Rise +PhoenixDown +PiPirate +PickleRick +Pidgeot +Pidgeotto +Pidgey +PigNosedPig +PigeonPete +Pikachu +Pike Wall +PikeMan +PikePete +PilafPete +PillagerLoot +PillagerPat +PineApple +PingPong99 +Pingu +Pinsir +PintoPinto +Pippin +Pirate +Pirate Ship +Pisces Fish +PistolPete +Pistolero +PistonPete +PivotPoint +Pixel Art +PixelWarrior +Pixie +PlainPete +PlainsPete +Planet X +PlatePlate +PlateauPat +Platinum +PlatyPete +PliableStif +PlotArmor +PluggedIn +PlumPerfect +PlumbPete +PlumbTilted +Plunderer +PlundererP +PocketLoss +PogChamp99 +Polar Bear +PolarDog +PolarOrca +PolishdRough +Poliwag +Poliwhirl +Poliwrath +PolkockPete +Polycarp +Ponyta +Porio +Porygon +PossiblyYes +PotentImpot +PowerFist +PowerStrife +PoweredUp +PowerlessPow +PracticlyP +Praetorian +PrairieP +PranksterP +PreciseP +PredatorP +PrehistorP +Prie +PrimaryPat +PrimePete +PrimeTime +Primeape +PrinceFresh +PrincePete +Princess +PrincessP +Prism +Private Eye +Privateer +ProPunch +ProbablyNo +Prod +ProductOwn +ProfaneSacr +Professor +ProfoundP +Promiz +PropPro +ProtectorPro +PrunesPete +PsamPsamobb +PsammPsamm +PseuPseudem +PsiPower +Psyduck +PtarmiganP +PteroPete +Pudao Chop +PuffinSeal +Pugg +PullRates +Pulp +Pulsar Wave +PulsePete +PulsePoint +Pulu +PumpkinPie +PurePatrick +Purple Haze +Purrfect +PyroManiac +PythonPower +PyxiPyxis +QAQuinn +Qiang Poke +QuailQuinn +QuantumQuake +Quarrel +Quasar Jet +QueenQuin +QueenQuinn +Questi +Quibble +Quick Shot +QuickDraw +QuickQuinn +QuickScope +Quiet +QuietLion +Quinn +QuinoaQ +Quint +Quintis +Quteth +RacingSnail +RackMount +RadBrad +RadiantRay +Radovid +RagdollRay +Raibu +Raichu +Raid +Raider Camp +RaiderRex +Rail +Rain Maker +RainMaker +RaisinRon +Raketti +Ram Boost +RamenRon +Ranger Hat +Ranger Track +Ranx +RapidFire +RapidRon +Rapidash +Rapier Point +RapierRex +RaptorRon +RarelyYes +Raskal +Raslak +RaspBerry +Rastafa +Raticate +Rattata +RattleSnake +Raul +RavenGaze +RavineRon +RawRon +Ray +Razor Beast +Razor Edge +RealRyan +Realm +Reck +Recon Drone +Red Phoenix +RedEared +RedFootRed +RedWing +Rekt +RelaxedRon +ReleaseRon +Relic +RelishRob +Rellano +Rem +RenderRon +RespawnKing +RespawnNow +ReticleR +RetroRon +RevolvingR +RevvedUp +Rew +RhinoRhino +RhoRanger +Rhydon +Rhyhorn +RhythmRick +RiceRon +RidgeRider +Rido +RifleRanger +Rifleman +RiftRick +RigatoniR +RightLeft99 +RigidFlexy +RigidSoft +Ril +Riley +RipperRyan +RippleRick +Risen +RiskSecure +RisottoR +RoadRage +Roan +RobinRed +Robotnik +Roca +Roche +Rock Solid +RocketRider +Roger +Rogue +Rogue Sneak +Rogue007 +RogueKnight +Rolis +RollRon +RolloutRay +Rooda +Rope Dart +RopeDancer +Rose +RotatingR +RoughPolish +RoundSquare +Rowan +Roxer +RoyalRon +Royka +RubberBand +RubberBurn +Ruby +Ruby Red +Ruckus +Rumpus +RunningStop +Ruralli +RushHour +Ryza +SRankHero +SSSTier +Saber Strike +SaberSage +SabinSabin +SacraedSacr +Sacred +Sacred Blood +Sacred Dawn +Sacred Heart +Sacred Oath +Sacred Sword +Sacred Wings +SacredLegacy +SacredProf +SadJoy +Sadge +Saena +SafeDanger +Sage +SageSparrow +Sai Catch +Sailor Knot +Sakura +Sal +Sally +SalmonSam +Salve +SamuraiBlade +Samwise +Sand Storm +Sandshrew +Sandslash +Saney +SanityMad +Sanna +Sanny +Saph +Sapper Mine +SapperSam +SapphireSoul +Saruman +SashimiS +SatinySam +SaturdaySam +Sauade +Sauron +SauropeltaS +SavannaS +Sawyer +ScaldingSam +ScaleSteve +Scarak +ScaredBear +ScarelyNo +Scarlett +ScavengerS +Scimitar Arc +ScimitarS +ScopeDogg +ScopeKing +ScorchMark +Scorpio Tail +Scott +Scourge +Scout Map +Scrap +Scratch +ScreenTear +Scrimmage +ScrubLord +ScrumMastr +Scuffle +Scythe Cut +ScytheSage +Scyther +SeaSponge +SeaSquid +SeaTurtle +Seadra +Seaking +Seal +Seal lion +Sean +Seance +SearingSam +SecondSam +SecretSix +SecurRisky +Sedyana +Seel +Sellarity +Sena +SentinelSix +September9 +Sergeant Bar +Serpect +SerpentSoul +ServerSam +ServerSide +Seven +SevenFive +SevenNine +SevenSeth +ShadeShadow +ShadeShady +Shadow +Shadow Fox +ShadowBlade +ShadowOps +ShaftedSam +ShakyStable +ShakySteady +Shaman Mask +Shaper +Shark +SharpCloud +SharpShooter +Sharpshoot +ShatterSean +Shaw +Shawn +Shellder +ShellsSam +Shelly +Shelob +Sheng Biao +Sheriff Star +ShieldHero +ShifterS +ShimmerS +ShindigS +ShiningS +ShinyDull +Shir +Shiv +ShockWave +Shop +ShortHair +Shortsword +Shorty +Shuang Gou +Shuriken Fly +SiameseSam +SickSkills +Sid +SideNeck +SiebnSiebn +Sigil +Sigma +SigmaSix +SignSignatu +Signal +SignalSam +SignatuSign +Silas +Silence +Silent Kill +SilentKill +SilentScrem +SilkySteve +SillySam +Silver +SilverFang +SimmerStan +SimpleSam +Sinclair +SirSam +SixEight +SixFour +SixSam +SkewedAlign +SkidMark +Skilla +Skirmish +Skoll +Sky High +Sky Reaper +SkyHigh99 +Skype +SlackTaut +Slain +SlayQueen +SlayerSeth +SlicerSteve +SliderSlide +SlightlyYes +SlingShot +Sliske +SlopeSam +SlothCheetah +SlowPoke +Slowbro +Slowpoke +SmallGiant +SmasherSam +Smiles +SmokeyJoe +SmolBean +SmoothBrain +SmoothBumpy +SmoothSam +SnailBunny +SnailRacing +SnakeGecko +SnakyNecked +Snap +SnapperSnap +Snarl +SneakySnail +Sniffle +Sniper Scope +SniperElite +Snivel +Snorlax +Snort +Snow Fall +Snow raccoon +SnowFlake +Snowman +Snuffle +SnugSam +SoClose +SoMaybe +SoSad +Soad +Sob +SobaSteve +SoftHammer +SoftHard +SoftShellS +Softy +Sokka +Solar +Solar Flare +SolitaSolit +Solitude +SolutionS +SometimesNo +Sonic +SonicBoom +Sophics +Sorcerer Hat +SortKinda +Soul +Soul Eater +SoulStan +Soulless +SoupSam +Sozin +Space Cadet +SpaghettiS +Spark +SparkPlug +SparkleKid +SparrowS +Spartan 300 +Spat +Spear Thrust +SpearTip +Spearow +SpecklSpeck +SpecterHaunt +SpecterSpec +SpeedDemon +SpeedHack99 +SpeedySam +SphinxRiddle +SphinxSam +SpicyTuna +Spider Bat +SpiderTort +Spiderfly +SpinningS +SpinosarusS +SpinyShell +SpiritSpike +SpiritSpook +SplitSteve +SplitterS +SpoonfishS +SpotStripe +SprintCrawl +SprintStan +SpurThigh +Spy Glass +SpyMaster +Squabble +SquallSteve +SquareLopsid +SquareRound +SquashSteve +Squid +Squirtle +StablShaky +StablWobble +Stain +Stalhrim +StalkerStan +StandUpSam +Star +Star Gazer +Star Lord +StarStar +StarlingS +Starmie +StartPoint +StarterStan +Staryu +StaticDynmc +StaticS +SteadyShake +SteadyUnstdy +Stealth Fly +StealthMode +SteamRice +Steel Claw +Steel Heart +SteelClaw +SteelViper +StegoStan +Stellar +Stennis +Stephan +SteppeStan +StewSteve +StickShift +StickyDoor +StigStgmoch +StillMoving +StockStan +Stok +Stone Cold +Stony +StoppedRun +Storm +Storm Blade +StormCalm +StormCloud +StormRider +Stormy +StraightBent +StraightLean +StraightS +StraightTwst +StrawBerry +StreetKing +StripedSpot +StrongWeak +Stuey +SturgenSam +StutterStep +StygimolchS +Styloire +StylysSteve +StyracoS +Subtle +SuchomimusS +Suhly +Suki +SulcataSul +SummitSam +Sun +Sun Spot +SundayFun +SunfishSam +SunriseSam +SunsetStan +SuperSlam +Support Unit +SupremeS +SurfaceSam +Surge +SurgeSteve +SushiSam +Suspect +Sven +SwallowS +Swan +SwiftSteve +SwiftStrike +SwirlingSam +SwordSaint +Swordsman +SwordtailS +Syndicate +SyntaxSlayer +SysAdminSam +TRexRex +Tabby +TabbyTom +TabletTim +TaigaTed +Tails +Talies +TallAnt +Talon +TameWild +Tangela +TangerinT +Tank Shell +Tanto Blade +Tanto Sharp +TargetLock +TarmacTom +TauTerror +Tauros +Taurus Bull +TautRelaxed +TearerTom +Tech Noir +TechLead +Tekko Hand +Teleportin +TempestTom +TempoTom +TenEight +TenTony +TenTwelve +TenseChill +TentTortois +Tentacool +Tentacruel +TenuiTenui +Teps +Teran +Term +TerminatorT +TerraTerrp +TerrapinTur +Tessa +Tessen Fan +TestTim +TestuTestud +TettraTom +TexasTort +ThenNow +Theoden +ThereHere +TherizinoT +ThetaThree +Theump +ThickSkull +Thief Steal +ThirteenElv +Thor +Thorak +Thoron +ThreatPeace +ThreeFive +ThreeOne +ThreeTom +ThrottleT +Thru +Thulth +Thump +Thunder +Thunder God +ThunderHead +ThunderStrix +ThunderT +ThursdayT +TiedLoose +Tiff +Tiger Hook +Tiger monkey +TigerSeal +TigerShark +TightSlack +TilapiaTom +TiltedPlumb +Tinne +TinyBrain +TippedBal +TireFlip +TiredVigor +Titan Fall +TitanGrip +TitanTerror +ToadLizard +Toar +ToastyTom +TodayTomrw +Todes +Tom +TomorrowYes +Tonfa Block +Toni +TooBad +TooEz +TopSideTom +TopTier1 +Topaz +Toph +Topher +TorchBearer +Tornado +TornadoT +TotallyNot +TotallyReal +TouchPadTom +ToughFragile +TowerTom +Toxic Venom +ToyboyTom +TrachemTrac +TrackBallT +TrackerTom +TransMaster +TrappedFree +Trapper Net +Traut +Trav +TravanTrav +Travis +TreeTop99 +TrenchTitan +TrenchTony +Trevor +TrialTime +TriceraTop +TrickShot99 +TricksterT +TridentKing +TrimTrimacu +Triss +Trist +TrodonTroy +Troll Bridge +Trork +TroutTim +TrueLies +TrueTodd +TrueTom +TryAgain99 +Tryhard +Tryver +TubularTom +Tuesday2 +Tuga +Tugas +TunaTodd +TundraTom +Turbo Boost +TurboTed +TurboTitan +Turk +TurnPro +TurtleDove +TurtleFrog +TurtleHare +TurtleSeal +Tussle +TwelveTen +Twenty +TwilightTom +TwirlingTom +TwisterTed +TwoFour +TwoTim +TwoZero +TyphoonT +UdonUlf +Uiua +UltimateU +UltraKick +UltraOof +Umar +Unagi +UnbalancedB +UnbendablB +UnbentBent +UndeadLive +UnevenEven +UnfortuneF +Unholy +UnholyHoly +Unicorn Horn +UnknownX +UnlevelLevl +UnluckyLuck +UnluckyLuk +UnsteadyStdy +UpDown99 +UpdateTime +UplandUlf +UpperDeck +Uraby +Usagi +UtahraptrU +Uther +UwUWarrior +VacayVibes +Vaht +Valar +Valette +ValiantV +Valkyrie +ValleyVic +Valor +Vampire Bite +VampireSun +Vaporeon +Varyn +Vasco +Vector +VelociRapV +VelocityV +VelutiVelu +VelvetVic +Venator +VenomStrike +Venomoth +Venonat +Venusaur +VersionV +Ves +VetVince +VibeCheck +Victor +Victreebel +VicunaVic +VigorousTird +Viking Ship +Vileplume +Vindicta +Vine +Violet +Violet Storm +ViperVenom +Virgo Pure +Virtual Pro +VirtualVoid +VirtuallyV +Viscous +VitalVince +Void Hunter +VoidFull +VoidVader +VoidVince +VoidWalker +VoltageV +Voltorb +Vortex +VortexVic +VsyncOff +Vub +Vulpix +VultureView +Vyper +WackyWill +WacomWill +Wail +Waio +Wake +Wakizashi +WalkingJog +WallHackWil +WalleyeWill +War Machine +WarMachine +WarPeace +Warden +WardenWill +WardenWrath +Warlock Deal +WarmWill +WarpDrive +Wartortle +Water +WaterMelon +WaterfallW +Waves +WaveyWill +Wazzy +WeakStrong +Wednesday +Wednesday2 +Weedle +Weekday +Weekend99 +Weep +Weepinbell +Weezing +WellPlayed +Wendy +WerewolfDay +WerewolfHowl +Werr +Wes +WetFire +Whale +WhaleShark +WhaleWalrus +WhaleWill +WheelieKing +Whiff +Whimper +Whine +WhipMaster +WhirlWind +WhiskerWig +Whisky +White Tiger +WhiteRice +Whoops +WickedWill +WienerKing +Wigglytuff +Wild Card +WildCard99 +WildRice +WildTame +WildfireW +Willy +Wilt +Wind +Wind Breaker +Wind buffalo +WindStorm +WiredWill +Wisely +Witch Brew +Wizard Staff +WobblyStabl +WoblyFirm +Wobsy +WolfPack99 +WolfmanWolf +WoodFrog +WoodlandW +Woof +WorkDay +Wraith +Wraith Form +WraithWrath +Wrecked +WreckerWill +WyvernWing +Xelio +Xepher +Xephyr +Xeria +XeroXeroba +XiWarrior +XpPenXander +Xtone +Xuhe +Yamato +Yap +Yari Spear +Yarpen +YeetMaster +YellowBelly +YellowFoot +YellowtailY +Yelp +Yelpsie +Yenn +Yennefer +YesNo99 +YesterdayNo +Yickee +Ylaria +Yogo +Yoji +Yoram +YourDaddy +Yowl +YudonYuri +Yumi Bow +Yuno +Yurcher +YutytyrannY +Zac +Zach +Zamata +ZanyZack +ZapZack +Zapdos +Zapy +Zebra +Zell +Zenig +Zephyr +ZephyrZack +Zero +Zero Cool +ZeroChill +ZeroGrav +ZeroHero +ZeroHero99 +ZeroTwo +Zeroth +Zerxis +ZetaZero +Zeurpiet +Zhao +Ziffix +Zig +Zindra +Zip +Ziyte +Zodiac Sign +Zodo +Zohan +Zoltan +Zombie Brain +ZombieAlive +ZoomMaster +Zubat +ZucchiniZ +Zuko +Zulu +Zurm +Zush +Zweb +Zyvik \ No newline at end of file diff --git a/data/dirs.txt b/data/dirs.txt index 96dded11be..acf98a7940 100644 --- a/data/dirs.txt +++ b/data/dirs.txt @@ -6,4 +6,5 @@ entity minigame quest skill -social \ No newline at end of file +social +bot \ No newline at end of file diff --git a/data/entity/bot/nav_graph.toml b/data/entity/bot/nav_graph.toml deleted file mode 100644 index a8c18c42bd..0000000000 --- a/data/entity/bot/nav_graph.toml +++ /dev/null @@ -1,965 +0,0 @@ -[lumbridge_gate_south_to_village] -from = { x = 3234, y = 3218 } -to = { x = 3236, y = 3205 } - -[lumbridge_gate_south_to_church] -from = { x = 3234, y = 3218 } -to = { x = 3243, y = 3209 } - -[lumbridge_south_village_to_church] -from = { x = 3236, y = 3205 } -to = { x = 3243, y = 3209 } - -[lumbridge_gate_south_to_behind_church] -from = { x = 3234, y = 3218 } -to = { x = 3250, y = 3212 } - -[lumbridge_behind_church_to_church_fishing_spot] -from = { x = 3250, y = 3212 } -to = { x = 3258, y = 3206 } - -[lumbridge_south_village_to_bobs_axes] -from = { x = 3236, y = 3205 } -to = { x = 3231, y = 3203 } -cost = 5 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3234, y = 3203 } }, - { tile = { x = 3231, y = 3203 } }, -] - -[lumbridge_bobs_axes_to_south_village] -from = { x = 3231, y = 3203 } -to = { x = 3236, y = 3205 } -cost = 5 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3234, y = 3203 } }, - { tile = { x = 3236, y = 3205 } }, -] - -[lumbridge_south_village_to_south_range_house] -from = { x = 3236, y = 3205 } -to = { x = 3231, y = 3197 } -cost = 5 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3235, y = 3199 } }, - { tile = { x = 3231, y = 3197 } }, -] - -[lumbridge_south_range_house_to_south_village] -from = { x = 3231, y = 3197 } -to = { x = 3236, y = 3205 } -cost = 5 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3235, y = 3199 } }, - { tile = { x = 3236, y = 3205 } }, -] - -[lumbridge_south_village_to_graveyard_exit] -from = { x = 3236, y = 3205 } -to = { x = 3244, y = 3190 } - -[lumbridge_graveyard_exit_to_behind_graveyard] -from = { x = 3244, y = 3190 } -to = { x = 3253, y = 3200 } - -[lumbridge_behind_church_to_behind_graveyard] -from = { x = 3250, y = 3212 } -to = { x = 3253, y = 3200 } - -[lumbridge_church_fishing_spot_to_behind_graveyard] -from = { x = 3258, y = 3206 } -to = { x = 3253, y = 3200 } - -[lumbridge_castle_2nd_floor_south_stairs_to_1st_floor] -from = { x = 3205, y = 3209, level = 2 } -to = { x = 3205, y = 3209, level = 1 } -cost = 1 -steps = [ - { option = "Climb-down", object = "lumbridge_staircase_top", tile = { x = 3204, y = 3207, level = 2 } }, -] - -[lumbridge_castle_2nd_floor_bank_to_south_stairs] -from = { x = 3208, y = 3219, level = 2 } -to = { x = 3205, y = 3209, level = 2 } - -[lumbridge_castle_ground_floor_south_stairs_to_1st_floor] -from = { x = 3205, y = 3209 } -to = { x = 3205, y = 3209, level = 1 } -cost = 1 -steps = [ - { option = "Climb-up", object = "lumbridge_staircase", tile = { x = 3204, y = 3207 } }, -] - -[lumbridge_castle_ground_floor_south_stairs_to_kitchen_corridor] -from = { x = 3205, y = 3209 } -to = { x = 3208, y = 3210 } - -[lumbridge_castle_kitchen_corridor_to_castle_south_entrance] -from = { x = 3208, y = 3210 } -to = { x = 3215, y = 3216 } - -[lumbridge_castle_kitchen_corridor_to_kitchen] -from = { x = 3208, y = 3210 } -to = { x = 3211, y = 3214 } - -[lumbridge_castle_south_entrance_to_courtyard_south] -from = { x = 3215, y = 3216 } -to = { x = 3222, y = 3218 } - -[lumbridge_castle_1st_floor_south_stairs_to_ground_floor] -from = { x = 3205, y = 3209, level = 1 } -to = { x = 3205, y = 3209 } -cost = 1 -steps = [ - { option = "Climb-down", object = "lumbridge_castle_staircase_south_middle", tile = { x = 3204, y = 3207, level = 1 } }, -] - -[lumbridge_castle_1st_floor_south_stairs_to_2nd_floor] -from = { x = 3205, y = 3209, level = 1 } -to = { x = 3205, y = 3209, level = 2 } -cost = 1 -steps = [ - { option = "Climb-up", object = "lumbridge_castle_staircase_south_middle", tile = { x = 3204, y = 3207, level = 1 } }, -] - -[lumbridge_castle_grounds_south_to_tower_west] -from = { x = 3209, y = 3205 } -to = { x = 3199, y = 3218 } - -[lumbridge_castle_tower_west_to_yew_trees] -from = { x = 3199, y = 3218 } -to = { x = 3184, y = 3225 } - -[lumbridge_castle_tower_west_to_tree_patch] -from = { x = 3199, y = 3218 } -to = { x = 3193, y = 3236 } - -[lumbridge_castle_yew_trees_to_yew_trees_west] -from = { x = 3184, y = 3225 } -to = { x = 3168, y = 3221 } - -[lumbridge_castle_yew_trees_to_tree_patch] -from = { x = 3184, y = 3225 } -to = { x = 3193, y = 3236 } - -[lumbridge_south_tower_to_ground_floor] -from = { x = 3226, y = 3214 } -to = { x = 3227, y = 3214 } -cost = 3 -steps = [ - { option = "Open", object = "door_627_closed", tile = { x = 3226, y = 3214 } }, -] - -[lumbridge_south_tower_ground_floor_to_1st_floor] -from = { x = 3227, y = 3214 } -to = { x = 3229, y = 3214, level = 1 } -cost = 1 -steps = [ - { option = "Climb-up", object = "36768", tile = { x = 3229, y = 3213 } }, -] - -[lumbridge_south_tower_1st_floor_to_2nd_floor] -from = { x = 3229, y = 3214, level = 1 } -to = { x = 3229, y = 3214, level = 2 } -cost = 1 -steps = [ - { option = "Climb-up", object = "36769", tile = { x = 3229, y = 3213, level = 1 } }, -] - -[lumbridge_south_tower_1st_floor_to_ground_floor] -from = { x = 3229, y = 3214, level = 1 } -to = { x = 3229, y = 3214 } -cost = 1 -steps = [ - { option = "Climb-down", object = "36769", tile = { x = 3229, y = 3213, level = 1 } }, -] - -[lumbridge_south_tower_2nd_floor_to_1st_floor] -from = { x = 3229, y = 3214, level = 2 } -to = { x = 3229, y = 3214, level = 1 } -cost = 1 -steps = [ - { option = "Climb-down", object = "36770", tile = { x = 3229, y = 3213, level = 2 } }, -] - -[lumbridge_gate_north_to_bridge_west] -from = { x = 3234, y = 3220 } -to = { x = 3236, y = 3225 } - -[lumbridge_bridge_west_to_unstable_house] -from = { x = 3236, y = 3225 } -to = { x = 3230, y = 3232 } - -[lumbridge_bridge_west_to_bridge_east] -from = { x = 3236, y = 3225 } -to = { x = 3253, y = 3225 } - -[lumbridge_bridge_east_to_trees_east] -from = { x = 3253, y = 3225 } -to = { x = 3263, y = 3222 } - -[lumbridge_bridge_east_to_east_crossroad] -from = { x = 3253, y = 3225 } -to = { x = 3260, y = 3228 } - -[lumbridge_east_crossroad_to_trees_east] -from = { x = 3260, y = 3228 } -to = { x = 3263, y = 3222 } - -[lumbridge_bridge_north_to_cow_entrance] -from = { x = 3235, y = 3261 } -to = { x = 3250, y = 3266 } - -[lumbridge_goblin_path_north_to_cow_entrance] -from = { x = 3251, y = 3252 } -to = { x = 3250, y = 3266 } - -[lumbridge_cow_entrance_to_cow_path] -from = { x = 3250, y = 3266 } -to = { x = 3240, y = 3280 } - -[lumbridge_cow_path_to_chicken_entrance] -from = { x = 3240, y = 3280 } -to = { x = 3238, y = 3295 } - -[lumbridge_chicken_entrance_to_chicken_pen] -from = { x = 3238, y = 3295 } -to = { x = 3235, y = 3295 } -steps = [ - { option = "Open", object = "gate_235_closed", tile = { x = 3237, y = 3295 } }, -] - -[lumbridge_chicken_pen_to_chicken_entrance] -from = { x = 3235, y = 3295 } -to = { x = 3238, y = 3295 } -steps = [ - { option = "Open", object = "gate_237_closed", tile = { x = 3237, y = 3296 } }, -] - -[lumbridge_cow_entrance_to_cow_field] -from = { x = 3250, y = 3266 } -to = { x = 3255, y = 3266 } -steps = [ - { option = "Open", object = "gate_241_closed", tile = { x = 3252, y = 3266 } }, -] - -[lumbridge_cow_field_to_cow_entrance] -from = { x = 3255, y = 3266 } -to = { x = 3250, y = 3266 } -steps = [ - { option = "Open", object = "gate_239_closed", tile = { x = 3252, y = 3267 } }, -] - -[lumbridge_graveyard_exit_to_swamp_path] -from = { x = 3244, y = 3190 } -to = { x = 3241, y = 3176 } - -[lumbridge_swamp_path_to_swamp_cross_roads] -from = { x = 3241, y = 3176 } -to = { x = 3239, y = 3160 } - -[lumbridge_graveyard_exit_to_rats_north_east] -from = { x = 3244, y = 3190 } -to = { x = 3231, y = 3188 } - -[lumbridge_graveyard_exit_to_rats_south_east] -from = { x = 3244, y = 3190 } -to = { x = 3231, y = 3181 } - -[lumbridge_swamp_path_to_rats_north_east] -from = { x = 3241, y = 3176 } -to = { x = 3231, y = 3188 } - -[lumbridge_swamp_path_to_rats_south_east] -from = { x = 3241, y = 3176 } -to = { x = 3231, y = 3181 } - -[lumbridge_swamp_rats_north_east_to_south_east] -from = { x = 3231, y = 3188 } -to = { x = 3231, y = 3181 } - -[lumbridge_swamp_cross_roads_to_copper_mine] -from = { x = 3239, y = 3160 } -to = { x = 3228, y = 3148 } - -[lumbridge_swamp_copper_mine_to_tin_mine] -from = { x = 3228, y = 3148 } -to = { x = 3225, y = 3147 } - -[lumbridge_swamp_copper_mine_to_east_mine] -from = { x = 3228, y = 3148 } -to = { x = 3221, y = 3156 } - -[lumbridge_swamp_cross_roads_to_east_mine] -from = { x = 3239, y = 3160 } -to = { x = 3221, y = 3156 } - -[lumbridge_swamp_cross_roads_to_fishing_spot] -from = { x = 3239, y = 3160 } -to = { x = 3243, y = 3155 } - -[lumbridge_swamp_east_mine_to_urhney_house] -from = { x = 3221, y = 3156 } -to = { x = 3201, y = 3155 } - -[lumbridge_swamp_urhney_house_to_water_altar] -from = { x = 3201, y = 3155 } -to = { x = 3181, y = 3153 } - -[lumbridge_swamp_water_altar_to_south] -from = { x = 3181, y = 3153 } -to = { x = 3161, y = 3151 } - -[lumbridge_swamp_south_to_west_mine] -from = { x = 3161, y = 3151 } -to = { x = 3148, y = 3149 } - -[lumbridge_swamp_west_mine_to_mithril_mine] -from = { x = 3148, y = 3149 } -to = { x = 3146, y = 3147 } - -[lumbridge_swamp_west_mine_to_coal_mine] -from = { x = 3148, y = 3149 } -to = { x = 3146, y = 3150 } - -[lumbridge_swamp_west_mine_to_west_1] -from = { x = 3148, y = 3149 } -to = { x = 3146, y = 3167 } - -[lumbridge_swamp_west_1_to_2] -from = { x = 3146, y = 3167 } -to = { x = 3143, y = 3186 } - -[lumbridge_swamp_west_2_to_west_camp] -from = { x = 3143, y = 3186 } -to = { x = 3142, y = 3203 } - -[lumbridge_swamp_west_camp_to_west_wall] -from = { x = 3142, y = 3203 } -to = { x = 3136, y = 3219 } - -[lumbridge_swamp_west_camp_to_north_wall] -from = { x = 3142, y = 3203 } -to = { x = 3153, y = 3216 } - -[lumbridge_swamp_west_wall_to_north_wall] -from = { x = 3136, y = 3219 } -to = { x = 3153, y = 3216 } - -[lumbridge_swamp_west_wall_to_draynor_east_path] -from = { x = 3136, y = 3219 } -to = { x = 3138, y = 3227 } - -[lumbridge_swamp_west_wall_to_draynor_jail_path_south] -from = { x = 3136, y = 3219 } -to = { x = 3119, y = 3228 } - -[lumbridge_courtyard_south_to_tower_door] -from = { x = 3222, y = 3218 } -to = { x = 3226, y = 3214 } - -[lumbridge_courtyard_south_to_north] -from = { x = 3222, y = 3218 } -to = { x = 3222, y = 3220 } - -[lumbridge_courtyard_south_to_gate_north] -from = { x = 3222, y = 3218 } -to = { x = 3234, y = 3218 } - -[lumbridge_courtyard_north_to_gate_north] -from = { x = 3222, y = 3220 } -to = { x = 3234, y = 3220 } - -[lumbridge_courtyard_gate_south_to_gate_north] -from = { x = 3234, y = 3218 } -to = { x = 3234, y = 3220 } - -[lumbridge_courtyard_south_to_grounds_south] -from = { x = 3222, y = 3218 } -to = { x = 3209, y = 3205 } - -[lumbridge_unstable_house_to_general_store_east] -from = { x = 3230, y = 3232 } -to = { x = 3222, y = 3241 } - -[lumbridge_general_store_east_to_general_store] -from = { x = 3222, y = 3241 } -to = { x = 3217, y = 3241 } -cost = 6 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3219, y = 3241 } }, - { tile = { x = 3217, y = 3241 } }, -] - -[lumbridge_general_store_to_general_store_east] -from = { x = 3217, y = 3241 } -to = { x = 3222, y = 3241 } -cost = 6 -steps = [ - { option = "Open", object = "door_720_closed", tile = { x = 3219, y = 3241 } }, - { tile = { x = 3222, y = 3241 } }, -] - -[lumbridge_general_store_east_to_trees_west] -from = { x = 3222, y = 3241 } -to = { x = 3226, y = 3245 } - -[lumbridge_west_village_trees_to_bridge_north] -from = { x = 3226, y = 3245 } -to = { x = 3235, y = 3261 } - -[lumbridge_general_store_east_to_task_building] -from = { x = 3222, y = 3241 } -to = { x = 3204, y = 3247 } - -[lumbridge_general_store_to_task_building] -from = { x = 3217, y = 3241 } -to = { x = 3204, y = 3247 } - -[lumbridge_task_building_to_fishing_shop_entrance] -from = { x = 3204, y = 3247 } -to = { x = 3194, y = 3247 } - -[lumbridge_fishing_shop_entrance_to_west_path] -from = { x = 3194, y = 3247 } -to = { x = 3172, y = 3239 } - -[lumbridge_fishing_shop_entrance_to_tree_patch] -from = { x = 3194, y = 3247 } -to = { x = 3193, y = 3236 } - -[lumbridge_fishing_shop_entrance_to_fishing_shop] -from = { x = 3194, y = 3247 } -to = { x = 3195, y = 3251 } - -[lumbridge_west_path_to_ham_path] -from = { x = 3172, y = 3239 } -to = { x = 3157, y = 3234 } - -[lumbridge_west_path_to_yew_trees] -from = { x = 3172, y = 3239 } -to = { x = 3184, y = 3225 } - -[lumbridge_west_path_to_yew_trees_west] -from = { x = 3172, y = 3239 } -to = { x = 3168, y = 3221 } - -[lumbridge_yew_trees_west_to_swamp_north_wall] -from = { x = 3168, y = 3221 } -to = { x = 3153, y = 3216 } - -[lumbridge_ham_path_to_draynor_east_path] -from = { x = 3157, y = 3234 } -to = { x = 3138, y = 3227 } - -[lumbridge_general_store_east_to_west_crossroad] -from = { x = 3222, y = 3241 } -to = { x = 3219, y = 3247 } - -[lumbridge_west_crossroad_to_combat_hall_east_entrance] -from = { x = 3219, y = 3247 } -to = { x = 3212, y = 3247 } - -[lumbridge_combat_hall_east_entrance_to_task_building] -from = { x = 3212, y = 3247 } -to = { x = 3204, y = 3247 } - -[lumbridge_combat_hall_entrance_to_east] -from = { x = 3212, y = 3247 } -to = { x = 3212, y = 3250 } - -[lumbridge_task_building_to_combat_hall_west] -from = { x = 3204, y = 3247 } -to = { x = 3204, y = 3250 } - -[lumbridge_village_trees_west_to_smiths_south] -from = { x = 3226, y = 3245 } -to = { x = 3225, y = 3252 } - -[lumbridge_smiths_south_to_west] -from = { x = 3225, y = 3252 } -to = { x = 3222, y = 3255 } - -[lumbridge_smiths_west_to_smiths_crossroad] -from = { x = 3222, y = 3255 } -to = { x = 3218, y = 3255 } - -[lumbridge_smiths_crossroad_to_west_crossroad] -from = { x = 3218, y = 3255 } -to = { x = 3219, y = 3247 } - -[lumbridge_smiths_south_to_west_crossroad] -from = { x = 3225, y = 3252 } -to = { x = 3219, y = 3247 } - -[lumbridge_bridge_east_to_goblins_river] -from = { x = 3253, y = 3225 } -to = { x = 3245, y = 3238 } - -[lumbridge_bridge_east_to_goblins] -from = { x = 3253, y = 3225 } -to = { x = 3254, y = 3239 } - -[lumbridge_goblins_to_trees_east] -from = { x = 3254, y = 3239 } -to = { x = 3258, y = 3250 } - -[lumbridge_goblins_to_river] -from = { x = 3254, y = 3239 } -to = { x = 3245, y = 3238 } - -[lumbridge_goblins_to_path_north] -from = { x = 3254, y = 3239 } -to = { x = 3251, y = 3252 } - -[lumbridge_goblins_path_north_to_bridge_north] -from = { x = 3251, y = 3252 } -to = { x = 3235, y = 3261 } - -[lumbridge_bridge_north_to_goblin_fishing_spot] -from = { x = 3235, y = 3261 } -to = { x = 3240, y = 3249 } - -[lumbridge_goblin_path_north_to_fishing_spot] -from = { x = 3251, y = 3252 } -to = { x = 3240, y = 3249 } - -[lumbridge_goblin_fishing_spot_to_river] -from = { x = 3240, y = 3249 } -to = { x = 3245, y = 3238 } - -[lumbridge_east_crossroad_to_goblins] -from = { x = 3260, y = 3228 } -to = { x = 3254, y = 3239 } - -[lumbridge_east_crossroad_to_tollgate] -from = { x = 3260, y = 3228 } -to = { x = 3267, y = 3227 } - -[lumbridge_east_crossroad_to_tollgate_north] -from = { x = 3260, y = 3228 } -to = { x = 3267, y = 3228 } - -[lumbridge_east_crossroad_to_trees_east] -from = { x = 3260, y = 3228 } -to = { x = 3263, y = 3222 } - -[varrock_teleport_to_centre_east] -from = { x = 3213, y = 3428 } -to = { x = 3223, y = 3429 } - -[varrock_teleport_to_centre_west] -from = { x = 3213, y = 3428 } -to = { x = 3200, y = 3429 } - -[varrock_teleport_to_centre_south] -from = { x = 3213, y = 3428 } -to = { x = 3211, y = 3420 } - -[varrock_centre_west_to_west_bank_south_entrance] -from = { x = 3200, y = 3429 } -to = { x = 3186, y = 3430 } - -[varrock_west_bank_south_entrance_to_bank_south] -from = { x = 3186, y = 3430 } -to = { x = 3186, y = 3435 } - -[varrock_west_bank_south_entrance_to_romeo_crossroad] -from = { x = 3186, y = 3430 } -to = { x = 3171, y = 3429 } - -[varrock_west_romeo_crossroads_to_oak_tress] -from = { x = 3171, y = 3429 } -to = { x = 3162, y = 3420 } - -[varrock_west_oak_trees_to_cat_house] -from = { x = 3162, y = 3420 } -to = { x = 3150, y = 3416 } - -[varrock_west_cat_house_to_barbarian_path] -from = { x = 3150, y = 3416 } -to = { x = 3135, y = 3416 } - -[varrock_west_barbarian_path_to_air_altar_ruins] -from = { x = 3135, y = 3416 } -to = { x = 3128, y = 3407 } - -[varrock_west_air_altar_ruins_to_air_altar] -from = { x = 3128, y = 3407 } -to = { x = 2841, y = 4830 } -cost = 3 -steps = [ - { option = "Enter", object = "air_altar_ruins", tile = { x = 3126, y = 3404 } }, -] - -[varrock_west_air_altar_to_exit] -from = { x = 2841, y = 4830 } -to = { x = 2841, y = 4829 } - -[varrock_west_air_altar_exit_to_ruins] -from = { x = 2841, y = 4829 } -to = { x = 3128, y = 3407 } -cost = 3 -steps = [ - { option = "Enter", object = "air_altar_portal", tile = { x = 2841, y = 4828 } }, -] - -[varrock_east_earth_altar_ruins_to_altar] -from = { x = 3304, y = 3474 } -to = { x = 2655, y = 4831 } -cost = 3 -steps = [ - { option = "Enter", object = "earth_altar_ruins", tile = { x = 3305, y = 3473 } }, -] - -[varrock_east_earth_altar_to_exit] -from = { x = 2655, y = 4831 } -to = { x = 2655, y = 4830 } - -[varrock_east_earth_altar_exit_to_ruins] -from = { x = 2655, y = 4830 } -to = { x = 3304, y = 3474 } -cost = 3 -steps = [ - { option = "Enter", object = "earth_altar_portal", tile = { x = 2655, y = 4829 } }, -] - -[varrock_east_bank_to_dirt_crossroad] -from = { x = 3254, y = 3422 } -to = { x = 3265, y = 3428 } - -[varrock_east_dirt_crossroad_to_east_crossroad] -from = { x = 3265, y = 3428 } -to = { x = 3280, y = 3428 } - -[varrock_east_crossroad_to_east_path_south] -from = { x = 3280, y = 3428 } -to = { x = 3287, y = 3414 } - -[varrock_east_path_to_south] -from = { x = 3287, y = 3414 } -to = { x = 3291, y = 3399 } - -[varrock_east_path_south_to_east_border] -from = { x = 3291, y = 3399 } -to = { x = 3293, y = 3384 } - -[varrock_east_crossroad_to_path_north] -from = { x = 3280, y = 3428 } -to = { x = 3286, y = 3442 } - -[varrock_east_path_to_north] -from = { x = 3286, y = 3442 } -to = { x = 3287, y = 3457 } - -[varrock_east_path_north_to_sawmill_crossroad] -from = { x = 3287, y = 3457 } -to = { x = 3296, y = 3462 } - -[varrock_east_sawmill_crossroad_to_eath_altar_ruins] -from = { x = 3296, y = 3462 } -to = { x = 3304, y = 3474 } - -[varrock_east_dirt_crossroad_to_bank_crossroad] -from = { x = 3265, y = 3428 } -to = { x = 3246, y = 3429 } - -[varrock_east_bank_crossroad_to_east_bank] -from = { x = 3246, y = 3429 } -to = { x = 3254, y = 3422 } - -[varrock_east_armour_shop_to_bank_crossroad] -from = { x = 3230, y = 3430 } -to = { x = 3246, y = 3429 } - -[varrock_east_armour_shop_to_centre_east] -from = { x = 3230, y = 3430 } -to = { x = 3223, y = 3429 } - -[lumbridge_chicken_entrance_to_varrock_south_split] -from = { x = 3238, y = 3295 } -to = { x = 3238, y = 3304 } - -[varrock_south_split_to_al_kharid_path_west] -from = { x = 3238, y = 3304 } -to = { x = 3251, y = 3319 } - -[varrock_al_kharid_path_west_to_crossroads] -from = { x = 3251, y = 3319 } -to = { x = 3268, y = 3331 } - -[al_kharid_north_entrance_to_varrock_north_west] -from = { x = 3283, y = 3331 } -to = { x = 3295, y = 3334 } - -[varrock_al_kharid_north_west_to_crossroad] -from = { x = 3295, y = 3334 } -to = { x = 3304, y = 3335 } - -[varrock_al_kharid_north_west_to_south_east_path] -from = { x = 3295, y = 3334 } -to = { x = 3299, y = 3346 } - -[varrock_al_kharid_north_west_crossroad_to_south_east_path] -from = { x = 3304, y = 3335 } -to = { x = 3299, y = 3346 } - -[varrock_south_east_path_to_east_mine_path_south] -from = { x = 3299, y = 3346 } -to = { x = 3298, y = 3359 } - -[varrock_south_east_mine_south_to_path] -from = { x = 3298, y = 3359 } -to = { x = 3295, y = 3372 } - -[varrock_south_east_mine_path_to_east] -from = { x = 3295, y = 3372 } -to = { x = 3286, y = 3371 } - -[varrock_south_east_mine_to_border] -from = { x = 3286, y = 3371 } -to = { x = 3293, y = 3384 } - -[varrock_south_east_mine_path_to_border] -from = { x = 3295, y = 3372 } -to = { x = 3293, y = 3384 } - -[varrock_centre_south_to_dirt_crossroads] -from = { x = 3211, y = 3420 } -to = { x = 3210, y = 3407 } - -[varrock_south_dirt_crossroads_to_blue_moon_inn] -from = { x = 3210, y = 3407 } -to = { x = 3211, y = 3395 } - -[varrock_blue_moon_inn_to_south_border] -from = { x = 3211, y = 3395 } -to = { x = 3211, y = 3381 } - -[varrock_south_border_to_dark_mages] -from = { x = 3211, y = 3381 } -to = { x = 3214, y = 3367 } - -[varrock_south_dark_mages_to_south] -from = { x = 3214, y = 3367 } -to = { x = 3226, y = 3352 } - -[varrock_dark_mages_to_south_fields_path] -from = { x = 3226, y = 3352 } -to = { x = 3228, y = 3337 } - -[varrock_south_fields_path_to_stile] -from = { x = 3228, y = 3337 } -to = { x = 3240, y = 3336 } - -[varrock_south_stile_to_path] -from = { x = 3240, y = 3336 } -to = { x = 3254, y = 3333 } - -[varrock_south_stile_to_al_kharid_crossroad] -from = { x = 3254, y = 3333 } -to = { x = 3268, y = 3331 } - -[draynor_east_to_jail_path_south] -from = { x = 3138, y = 3227 } -to = { x = 3119, y = 3228 } - -[draynor_path_south_to_west] -from = { x = 3119, y = 3228 } -to = { x = 3105, y = 3238 } - -[draynor_path_west_to_bank_crossroad] -from = { x = 3105, y = 3238 } -to = { x = 3104, y = 3248 } - -[draynor_bank_crossroad_to_bank] -from = { x = 3104, y = 3248 } -to = { x = 3093, y = 3245 } - -[draynor_bank_to_stalls] -from = { x = 3093, y = 3245 } -to = { x = 3079, y = 3249 } - -[draynor_bank_to_trees] -from = { x = 3093, y = 3245 } -to = { x = 3099, y = 3246 } - -[draynor_bank_crossroad_to_trees] -from = { x = 3104, y = 3248 } -to = { x = 3099, y = 3246 } - -[draynor_stalls_to_pigsty] -from = { x = 3079, y = 3249 } -to = { x = 3071, y = 3266 } - -[draynor_stall_to_willow_trees] -from = { x = 3079, y = 3249 } -to = { x = 3086, y = 3237 } - -[draynor_stalls_to_west_trees] -from = { x = 3079, y = 3249 } -to = { x = 3072, y = 3250 } - -[draynor_stalls_to_north_trees] -from = { x = 3079, y = 3249 } -to = { x = 3079, y = 3265 } - -[draynor_bank_to_willow_trees] -from = { x = 3093, y = 3245 } -to = { x = 3086, y = 3237 } - -[draynor_willow_trees_to_south] -from = { x = 3086, y = 3237 } -to = { x = 3097, y = 3235 } - -[draynor_willow_trees_to_fishing_spot] -from = { x = 3086, y = 3237 } -to = { x = 3086, y = 3231 } - -[draynor_fishing_spot_to_south] -from = { x = 3086, y = 3231 } -to = { x = 3097, y = 3235 } - -[draynor_jail_path_west_to_south] -from = { x = 3105, y = 3238 } -to = { x = 3097, y = 3235 } - -[draynor_east_path_to_swamp_north_wall] -from = { x = 3138, y = 3227 } -to = { x = 3153, y = 3216 } - -[varrock_al_kharid_crossroad_to_north_entrance] -from = { x = 3268, y = 3331 } -to = { x = 3283, y = 3331 } - -[al_kharid_mine_north_entrance_to_west_path] -from = { x = 3283, y = 3331 } -to = { x = 3284, y = 3313 } - -[al_kharid_mine_west_path_to_south] -from = { x = 3284, y = 3313 } -to = { x = 3287, y = 3294 } - -[al_kharid_mine_west_path_to_entrance] -from = { x = 3287, y = 3294 } -to = { x = 3298, y = 3280 } - -[al_kharid_mine_entrance_to_mine_south] -from = { x = 3298, y = 3280 } -to = { x = 3299, y = 3263 } - -[al_kharid_mine_south_to_north_path] -from = { x = 3299, y = 3263 } -to = { x = 3294, y = 3242 } - -[al_kharid_north_path_to_crossroad] -from = { x = 3294, y = 3242 } -to = { x = 3278, y = 3228 } - -[al_kharid_crossroad_to_tollgate_north] -from = { x = 3278, y = 3228 } -to = { x = 3268, y = 3228 } - -[al_kharid_crossroad_to_tollgate] -from = { x = 3278, y = 3228 } -to = { x = 3268, y = 3227 } - -[al_kharid_crossroad_to_glider] -from = { x = 3278, y = 3228 } -to = { x = 3280, y = 3216 } - -[lumbridge_tollgate_north_to_al_kharid_tollgate] -from = { x = 3267, y = 3228 } -to = { x = 3268, y = 3228 } -cost = 1 -steps = [ - { option = "Pay-toll(10gp)", object = "toll_gate_al_kharid_north", tile = { x = 3268, y = 3228 } }, -] -conditions = [ - { type = "inventory_item", item = "coins", amount = 10 }, -] - -[al_kharid_tollgate_north_to_lumbridge_tollgate] -from = { x = 3268, y = 3228 } -to = { x = 3267, y = 3228 } -cost = 1 -steps = [ - { option = "Pay-toll(10gp)", object = "toll_gate_al_kharid_north", tile = { x = 3268, y = 3228 } }, -] -conditions = [ - { type = "inventory_item", item = "coins", amount = 10 }, -] - -[lumbridge_tollgate_to_al_kharid] -from = { x = 3267, y = 3227 } -to = { x = 3268, y = 3227 } -cost = 1 -steps = [ - { option = "Pay-toll(10gp)", object = "toll_gate_al_kharid_south", tile = { x = 3268, y = 3227 } }, -] -conditions = [ - { type = "inventory_item", item = "coins", amount = 10 }, -] - -[al_kharid_tollgate_to_lumbridge] -from = { x = 3268, y = 3227 } -to = { x = 3267, y = 3227 } -cost = 1 -steps = [ - { option = "Pay-toll(10gp)", object = "toll_gate_al_kharid_south", tile = { x = 3268, y = 3227 } }, -] -conditions = [ - { type = "inventory_item", item = "coins", amount = 10 }, -] - -[al_kharid_tollgate_to_glider] -from = { x = 3268, y = 3227 } -to = { x = 3280, y = 3216 } - -[al_kharid_tollgate_north_to_glider] -from = { x = 3268, y = 3228 } -to = { x = 3280, y = 3216 } - -[al_kharid_glider_to_musician] -from = { x = 3280, y = 3216 } -to = { x = 3292, y = 3215 } - -[al_kharid_musician_to_silk_path] -from = { x = 3292, y = 3215 } -to = { x = 3300, y = 3198 } - -[al_kharid_silk_path_to_scimitar_shop] -from = { x = 3300, y = 3198 } -to = { x = 3288, y = 3189 } - -[al_kharid_glider_to_west_shortcut] -from = { x = 3280, y = 3216 } -to = { x = 3280, y = 3200 } - -[al_kharid_west_shortcut_to_scimitar_shop] -from = { x = 3280, y = 3200 } -to = { x = 3288, y = 3189 } - -[al_kharid_scimitar_shop_to_furnace_entrance] -from = { x = 3288, y = 3189 } -to = { x = 3282, y = 3185 } - -[al_kharid_west_shortcut_to_furnace_to_entrance] -from = { x = 3280, y = 3200 } -to = { x = 3282, y = 3185 } - -[al_kharid_furnace_entrance_to_bank_crossroads] -from = { x = 3282, y = 3185 } -to = { x = 3278, y = 3177 } - -[al_kharid_bank_crossroad_to_entrance] -from = { x = 3278, y = 3177 } -to = { x = 3276, y = 3168 } - -[al_kharid_bank_crossroad_to_kebab_shop] -from = { x = 3278, y = 3177 } -to = { x = 3274, y = 3180 } - -[al_kharid_bank_entrance_to_bank] -from = { x = 3276, y = 3168 } -to = { x = 3270, y = 3167 } diff --git a/data/skill/runecrafting/runecrafting.areas.toml b/data/skill/runecrafting/runecrafting.areas.toml index 8f966fa422..fc642054c6 100644 --- a/data/skill/runecrafting/runecrafting.areas.toml +++ b/data/skill/runecrafting/runecrafting.areas.toml @@ -71,25 +71,15 @@ tags = ["multi_combat"] [earth_altar_ruins] x = [3304, 3308] y = [3472, 3476] -tags = ["ruins", "earth"] [air_altar_ruins] x = [3125, 3129] y = [3403, 3407] -tags = ["ruins", "air"] [air_altar] x = [2816, 2879] y = [4800, 4863] -tags = ["altar"] -type = "air" -spaces = 3 -levels = "1-9" [earth_altar] x = [2624, 2687] y = [4800, 4863] -tags = ["altar"] -type = "earth" -spaces = 3 -levels = "9-14" diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt index ec8b6a5cda..1c49ce6d54 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt @@ -73,16 +73,16 @@ class PlayerAccountLoader( } } - suspend fun connect(player: Player, client: Client? = null, displayMode: Int = 0) { - if (!accounts.setup(player, client, displayMode)) { + suspend fun connect(player: Player, client: Client, displayMode: Int = 0, viewport: Boolean = true) { + if (!accounts.setup(player, client, displayMode, viewport)) { logger.warn { "Error setting up account" } - client?.disconnect(Response.WORLD_FULL) + client.disconnect(Response.WORLD_FULL) return } withContext(gameContext) { queue.await() - logger.info { "${if (client != null) "Player" else "Bot"} logged in ${player.accountName} index ${player.index}." } - client?.login(player.name, player.index, player.rights.ordinal, member = World.members, membersWorld = World.members) + logger.info { "${if (viewport) "Player" else "Bot"} logged in ${player.accountName} index ${player.index}." } + client.login(player.name, player.index, player.rights.ordinal, member = World.members, membersWorld = World.members) accounts.spawn(player, client) AuditLog.event(player, "connected", player.tile) } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandler.kt index f70b3be489..0ff209eb32 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandler.kt @@ -8,5 +8,5 @@ abstract class InstructionHandler { /** * Validates the [instruction] information is correct and emits a [Player] event with the relevant data */ - abstract fun validate(player: Player, instruction: T) + abstract fun validate(player: Player, instruction: T): Boolean } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandlers.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandlers.kt index 6d3ff9f394..e689435f1a 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandlers.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandlers.kt @@ -59,23 +59,24 @@ class InstructionHandlers( } } - fun handle(player: Player, instruction: Instruction) { + fun handle(player: Player, instruction: Instruction): Boolean { when (instruction) { - is InteractInterfaceItem -> interactInterfaceItem.validate(player, instruction) - is InteractInterfacePlayer -> interactInterfacePlayer.validate(player, instruction) - is InteractInterfaceObject -> interactInterfaceObject.validate(player, instruction) - is InteractInterfaceNPC -> interactInterfaceNPC.validate(player, instruction) - is InteractInterfaceFloorItem -> interactInterfaceFloorItem.validate(player, instruction) - is InteractFloorItem -> interactFloorItem.validate(player, instruction) - is InteractDialogue -> interactDialogue.validate(player, instruction) - is ContinueKey -> continueKey.validate(player, instruction) - is InteractDialogueItem -> interactDialogueItem.validate(player, instruction) - is InterfaceClosedInstruction -> closeInterface.validate(player, instruction) - is InteractInterface -> interactInterface.validate(player, instruction) - is MoveInventoryItem -> moveInventoryItem.validate(player, instruction) - is InteractNPC -> interactNPC.validate(player, instruction) - is InteractObject -> interactObject.validate(player, instruction) - is InteractPlayer -> interactPlayer.validate(player, instruction) + is InteractInterfaceItem -> return interactInterfaceItem.validate(player, instruction) + is InteractInterfacePlayer -> return interactInterfacePlayer.validate(player, instruction) + is InteractInterfaceObject -> return interactInterfaceObject.validate(player, instruction) + is InteractInterfaceNPC -> return interactInterfaceNPC.validate(player, instruction) + is InteractInterfaceFloorItem -> return interactInterfaceFloorItem.validate(player, instruction) + is InteractFloorItem -> return interactFloorItem.validate(player, instruction) + is InteractDialogue -> return interactDialogue.validate(player, instruction) + is ContinueKey -> return continueKey.validate(player, instruction) + is InteractDialogueItem -> return interactDialogueItem.validate(player, instruction) + is InterfaceClosedInstruction -> return closeInterface.validate(player, instruction) + is InteractInterface -> return interactInterface.validate(player, instruction) + is MoveInventoryItem -> return moveInventoryItem.validate(player, instruction) + is InteractNPC -> return interactNPC.validate(player, instruction) + is InteractObject -> return interactObject.validate(player, instruction) + is InteractPlayer -> return interactPlayer.validate(player, instruction) + is ExecuteCommand -> return executeCommand.validate(player, instruction) is ExamineItem -> examineItem.invoke(instruction, player) is ExamineNpc -> examineNPC.invoke(instruction, player) is ExamineObject -> examineObject.invoke(instruction, player) @@ -83,7 +84,6 @@ class InstructionHandlers( is Walk -> walk.invoke(instruction, player) is WorldMapClick -> worldMapClick.invoke(instruction, player) is FinishRegionLoad -> finishRegionLoad.invoke(instruction, player) - is ExecuteCommand -> executeCommand.validate(player, instruction) is EnterString -> enterString.invoke(instruction, player) is EnterName -> enterName.invoke(instruction, player) is EnterInt -> enterInt.invoke(instruction, player) @@ -100,7 +100,9 @@ class InstructionHandlers( is ClanChatKick -> clanChatKickHandler.invoke(instruction, player) is ClanChatRank -> clanChatRankHandler.invoke(instruction, player) is SongEnd -> songEndHandler.invoke(instruction, player) + else -> return false } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt index 9c3cacf636..926afdb061 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InterfaceHandler.kt @@ -15,14 +15,12 @@ class InterfaceHandler( private val inventoryDefinitions: InventoryDefinitions, private val enumDefinitions: EnumDefinitions, ) { - private val logger = InlineLogger() fun getInterfaceItem(player: Player, interfaceId: Int, componentId: Int, itemId: Int, itemSlot: Int): InterfaceData? { val id = getOpenInterface(player, interfaceId) ?: return null val componentDefinition = getComponentDefinition(player, interfaceId, componentId) ?: return null val component = componentDefinition.stringId var item = Item.EMPTY - var inventory = "" if (itemId != -1) { when { id.startsWith("summoning_") && id.endsWith("_creation") -> item = Item(ItemDefinitions.get(itemId).stringId) @@ -38,12 +36,12 @@ class InterfaceHandler( id == "common_item_costs" -> item = Item(ItemDefinitions.get(itemId).stringId) id == "farming_equipment_store" || id == "farming_equipment_store_side" -> {} else -> { - inventory = getInventory(player, id, component, componentDefinition) ?: return null + val inventory = getInventory(player, id, component, componentDefinition) ?: return null item = getInventoryItem(player, id, componentDefinition, inventory, itemId, itemSlot) ?: return null } } } - return InterfaceData(id, component, item, inventory, componentDefinition.options) + return InterfaceData(id, component, item, componentDefinition.options) } private fun getOpenInterface(player: Player, interfaceId: Int): String? { @@ -65,25 +63,6 @@ class InterfaceHandler( return componentDefinition } - private fun getInventory(player: Player, id: String, component: String, componentDefinition: InterfaceComponentDefinition): String? { - if (component.isEmpty()) { - logger.info { "No inventory component found [$player, interface=$id, inventory=$component]" } - return null - } - if (id == "shop") { - return player["shop"] - } - var inventory = componentDefinition["inventory", ""] - if (id == "grand_exchange") { - inventory = "collection_box_${player["grand_exchange_box", -1]}" - } - if (!player.inventories.contains(inventory)) { - logger.info { "Player doesn't have interface inventory [$player, interface=$id, inventory=$inventory]" } - return null - } - return inventory - } - private fun getInventoryItem(player: Player, id: String, componentDefinition: InterfaceComponentDefinition, inventoryId: String, item: Int, itemSlot: Int): Item? { val itemId = if (item == -1 || item > ItemDefinitions.size) "" else ItemDefinitions.get(item).stringId val slot = when { @@ -113,13 +92,38 @@ class InterfaceHandler( } return inventory[slot] } + + companion object { + private val logger = InlineLogger() + + fun getInventory(player: Player, id: String, component: String, componentDefinition: InterfaceComponentDefinition): String? { + if (component.isEmpty()) { + logger.info { "No inventory component found [$player, interface=$id, inventory=$component]" } + return null + } + if (id == "shop") { + return player["shop"] + } + var inventory = componentDefinition["inventory", ""] + if (id == "grand_exchange") { + inventory = "collection_box_${player["grand_exchange_box", -1]}" + } + if (inventory == "") { + return null + } + if (!player.inventories.contains(inventory)) { + logger.info { "Player doesn't have interface inventory [$player, interface=$id, inventory=$inventory]" } + return null + } + return inventory + } + } } data class InterfaceData( val id: String, val component: String, val item: Item, - val inventory: String, val options: Array?, ) { override fun equals(other: Any?): Boolean { @@ -131,7 +135,6 @@ data class InterfaceData( if (id != other.id) return false if (component != other.component) return false if (item != other.item) return false - if (inventory != other.inventory) return false if (options != null) { if (other.options == null) return false if (!options.contentEquals(other.options)) return false @@ -146,7 +149,6 @@ data class InterfaceData( var result = id.hashCode() result = 31 * result + component.hashCode() result = 31 * result + item.hashCode() - result = 31 * result + inventory.hashCode() result = 31 * result + (options?.contentHashCode() ?: 0) return result } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueHandler.kt index d7599e1f55..d61c7d2f93 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueHandler.kt @@ -13,20 +13,23 @@ class DialogueContinueHandler( private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractDialogue) { + override fun validate(player: Player, instruction: InteractDialogue): Boolean { val (interfaceId, componentId) = instruction val id = definitions.get(interfaceId).stringId if (!player.interfaces.contains(id)) { logger.debug { "Dialogue $interfaceId not found for player $player" } - return + return false } val component = definitions.get(id).components?.get(componentId) if (component == null) { logger.debug { "Dialogue $interfaceId component $componentId not found for player $player" } - return + return false + } + if (player["debug", false]) { + logger.info { "$player - $id:${component.stringId}" } } - Dialogues.continueDialogue(player, "$id:${component.stringId}") + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueKeyHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueKeyHandler.kt index 4ab9ccf47b..46120503e3 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueKeyHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueContinueKeyHandler.kt @@ -13,15 +13,14 @@ import world.gregs.voidps.network.client.instruction.ContinueKey class DialogueContinueKeyHandler( private val definitions: InterfaceDefinitions, ) : InstructionHandler() { - override fun validate(player: Player, instruction: ContinueKey) { - val dialogue = player.dialogue - if (dialogue == null) { - return - } + override fun validate(player: Player, instruction: ContinueKey): Boolean { + val dialogue = player.dialogue ?: return false val option = if (instruction.button == -1) "continue" else "line${instruction.button}" if (definitions.get(dialogue).components?.values?.any { it.stringId == option } == true) { Dialogues.continueDialogue(player, "$dialogue:$option") + return true } + return false } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueItemContinueHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueItemContinueHandler.kt index 9e7de16f72..9d7f5c7632 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueItemContinueHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/DialogueItemContinueHandler.kt @@ -11,12 +11,13 @@ class DialogueItemContinueHandler : InstructionHandler() { private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractDialogueItem) { + override fun validate(player: Player, instruction: InteractDialogueItem): Boolean { val definition = ItemDefinitions.getOrNull(instruction.item) if (definition == null) { logger.debug { "Item ${instruction.item} not found for player $player." } - return + return false } Dialogues.continueItem(player, definition.stringId) + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt index ab007261bf..c5bed6d08f 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ExecuteCommandHandler.kt @@ -10,10 +10,10 @@ import world.gregs.voidps.network.client.instruction.ExecuteCommand class ExecuteCommandHandler : InstructionHandler() { - override fun validate(player: Player, instruction: ExecuteCommand) { + override fun validate(player: Player, instruction: ExecuteCommand): Boolean { if (instruction.tab) { Commands.autofill(player, instruction.command) - return + return true } val parts = instruction.command.split(" ") val prefix = parts[0] @@ -26,11 +26,12 @@ class ExecuteCommandHandler : InstructionHandler() { player.tele(x, y, level) player["world_map_centre"] = player.tile.id player["world_map_marker_player"] = player.tile.id - return + return true } Script.launch { AuditLog.event(player, "command", "\"${instruction.command}\"") Commands.call(player, instruction.command) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/FloorItemOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/FloorItemOptionHandler.kt index 8c7ef6b791..f5df58756a 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/FloorItemOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/FloorItemOptionHandler.kt @@ -17,29 +17,30 @@ class FloorItemOptionHandler : InstructionHandler() { private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractFloorItem) { + override fun validate(player: Player, instruction: InteractFloorItem): Boolean { if (player.contains("delay")) { - return + return false } val (id, x, y, optionIndex) = instruction val tile = player.tile.copy(x, y) val floorItem = FloorItems.at(tile).firstOrNull { it.def.id == id } if (floorItem == null) { logger.warn { "Invalid floor item $id $tile" } - return + return false } val options = floorItem.def.floorOptions val selectedOption = options.getOrNull(optionIndex) if (selectedOption == null) { logger.warn { "Invalid floor item option $optionIndex ${options.contentToString()}" } - return + return false } if (selectedOption == "Examine") { - player.message(floorItem.def.getOrNull("examine") ?: return, ChatType.ItemExamine) - return + player.message(floorItem.def.getOrNull("examine") ?: return false, ChatType.ItemExamine) + return false } player.closeInterfaces() player.interactFloorItem(floorItem, selectedOption, -1) + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceClosedHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceClosedHandler.kt index 793ec9ec86..f4899635d3 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceClosedHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceClosedHandler.kt @@ -6,10 +6,12 @@ import world.gregs.voidps.network.client.instruction.InterfaceClosedInstruction class InterfaceClosedHandler : InstructionHandler() { - override fun validate(player: Player, instruction: InterfaceClosedInstruction) { + override fun validate(player: Player, instruction: InterfaceClosedInstruction): Boolean { val id = player.interfaces.get("main_screen") ?: player.interfaces.get("wide_screen") ?: player.interfaces.get("underlay") if (id != null) { player.interfaces.close(id) + return true } + return false } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnFloorItemOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnFloorItemOptionHandler.kt index 4eb98262f4..39d816c53c 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnFloorItemOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnFloorItemOptionHandler.kt @@ -16,21 +16,22 @@ class InterfaceOnFloorItemOptionHandler(private val handler: InterfaceHandler) : private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractInterfaceFloorItem) { + override fun validate(player: Player, instruction: InteractInterfaceFloorItem): Boolean { val (floorItemId, x, y, interfaceId, componentId, itemId, itemSlot) = instruction val tile = player.tile.copy(x, y) val floorItem = FloorItems.at(tile).firstOrNull { it.def.id == floorItemId } if (floorItem == null) { logger.warn { "Invalid floor item $itemId $tile" } - return + return false } - val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return + val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return false player.closeInterfaces() if (item.isEmpty()) { player.interactOn(floorItem, id, component, itemSlot, approachRange = -1) } else { player.interactItemOn(floorItem, id, component, item, itemSlot, approachRange = -1) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnInterfaceOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnInterfaceOptionHandler.kt index 8d65d91376..0df17ba8b5 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnInterfaceOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnInterfaceOptionHandler.kt @@ -11,11 +11,11 @@ class InterfaceOnInterfaceOptionHandler( private val handler: InterfaceHandler, ) : InstructionHandler() { - override fun validate(player: Player, instruction: InteractInterfaceItem) { + override fun validate(player: Player, instruction: InteractInterfaceItem): Boolean { val (fromItemId, toItemId, fromSlot, toSlot, fromInterfaceId, fromComponentId, toInterfaceId, toComponentId) = instruction - val (fromId, fromComponent, fromItem) = handler.getInterfaceItem(player, fromInterfaceId, fromComponentId, fromItemId, fromSlot) ?: return - val (_, _, toItem) = handler.getInterfaceItem(player, toInterfaceId, toComponentId, toItemId, toSlot) ?: return + val (fromId, fromComponent, fromItem) = handler.getInterfaceItem(player, fromInterfaceId, fromComponentId, fromItemId, fromSlot) ?: return false + val (_, _, toItem) = handler.getInterfaceItem(player, toInterfaceId, toComponentId, toItemId, toSlot) ?: return false player.closeInterfaces() player.queue.clearWeak() @@ -25,5 +25,6 @@ class InterfaceOnInterfaceOptionHandler( } else { InterfaceApi.itemOnItem(player, fromItem, toItem, fromSlot, toSlot) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnNPCOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnNPCOptionHandler.kt index 517b4fc97a..22cb6dcbf3 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnNPCOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnNPCOptionHandler.kt @@ -14,11 +14,11 @@ import world.gregs.voidps.network.client.instruction.InteractInterfaceNPC class InterfaceOnNPCOptionHandler(private val handler: InterfaceHandler) : InstructionHandler() { - override fun validate(player: Player, instruction: InteractInterfaceNPC) { + override fun validate(player: Player, instruction: InteractInterfaceNPC): Boolean { val (npcIndex, interfaceId, componentId, itemId, itemSlot) = instruction - val npc = NPCs.indexed(npcIndex) ?: return + val npc = NPCs.indexed(npcIndex) ?: return false - val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return + val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return false player.closeInterfaces() player.talkWith(npc) @@ -27,6 +27,7 @@ class InterfaceOnNPCOptionHandler(private val handler: InterfaceHandler) : Instr } else { player.interactItemOn(npc, id, component, item, itemSlot) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnObjectOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnObjectOptionHandler.kt index e03c01e63b..b75e6708b8 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnObjectOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnObjectOptionHandler.kt @@ -17,22 +17,23 @@ class InterfaceOnObjectOptionHandler( private val handler: InterfaceHandler, ) : InstructionHandler() { - override fun validate(player: Player, instruction: InteractInterfaceObject) { + override fun validate(player: Player, instruction: InteractInterfaceObject): Boolean { val (objectId, x, y, interfaceId, componentId, itemId, itemSlot) = instruction val tile = Tile(x, y, player.tile.level) val obj = GameObjects.findOrNull(tile, objectId) if (obj == null) { player.noInterest() - return + return false } - val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return + val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return false player.closeInterfaces() if (item.isEmpty()) { player.interactOn(obj, id, component, itemSlot) } else { player.interactItemOn(obj, id, component, item, itemSlot) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnPlayerOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnPlayerOptionHandler.kt index 857724be10..a48c61fc67 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnPlayerOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOnPlayerOptionHandler.kt @@ -12,12 +12,13 @@ class InterfaceOnPlayerOptionHandler( private val handler: InterfaceHandler, ) : InstructionHandler() { - override fun validate(player: Player, instruction: InteractInterfacePlayer) { + override fun validate(player: Player, instruction: InteractInterfacePlayer): Boolean { val (playerIndex, interfaceId, componentId, itemId, itemSlot) = instruction - val target = Players.indexed(playerIndex) ?: return + val target = Players.indexed(playerIndex) ?: return false - val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return + val (id, component, item) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return false player.closeInterfaces() player.mode = ItemOnPlayerInteract(target, "$id:$component", item, itemSlot, player) + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOptionHandler.kt index d5d3224cb1..a404acb31f 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceOptionHandler.kt @@ -17,10 +17,10 @@ class InterfaceOptionHandler( private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractInterface) { + override fun validate(player: Player, instruction: InteractInterface): Boolean { val (interfaceId, componentId, itemId, itemSlot, option) = instruction - var (id, component, item, _, options) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return + var (id, component, item, options) = handler.getInterfaceItem(player, interfaceId, componentId, itemId, itemSlot) ?: return false if (options == null) { options = interfaceDefinitions.getComponent(id, component)?.getOrNull("options") ?: emptyArray() @@ -28,7 +28,7 @@ class InterfaceOptionHandler( if (option !in options.indices) { logger.info { "Interface option not found [$player, interface=$interfaceId, component=$componentId, option=$option, options=${options.toList()}]" } - return + return false } val selectedOption = options.getOrNull(option) ?: "" @@ -42,5 +42,6 @@ class InterfaceOptionHandler( Script.launch { InterfaceApi.option(player, event) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceSwitchHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceSwitchHandler.kt index 25a0e3e5c2..1c71d9f4db 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceSwitchHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/InterfaceSwitchHandler.kt @@ -10,7 +10,7 @@ class InterfaceSwitchHandler( private val handler: InterfaceHandler, ) : InstructionHandler() { - override fun validate(player: Player, instruction: MoveInventoryItem) { + override fun validate(player: Player, instruction: MoveInventoryItem): Boolean { var (fromInterfaceId, fromComponentId, fromItemId, fromSlot, toInterfaceId, toComponentId, toItemId, toSlot) = instruction if (toInterfaceId == 149) { toSlot -= 28 @@ -18,8 +18,9 @@ class InterfaceSwitchHandler( fromItemId = toItemId toItemId = temp } - val (fromId, fromComponent) = handler.getInterfaceItem(player, fromInterfaceId, fromComponentId, fromItemId, fromSlot) ?: return - val (toId, toComponent) = handler.getInterfaceItem(player, toInterfaceId, toComponentId, toItemId, toSlot) ?: return + val (fromId, fromComponent) = handler.getInterfaceItem(player, fromInterfaceId, fromComponentId, fromItemId, fromSlot) ?: return false + val (toId, toComponent) = handler.getInterfaceItem(player, toInterfaceId, toComponentId, toItemId, toSlot) ?: return false InterfaceApi.swap(player, "$fromId:$fromComponent", "$toId:$toComponent", fromSlot, toSlot) + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/NPCOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/NPCOptionHandler.kt index d95cbe1eec..784e4ab97d 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/NPCOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/NPCOptionHandler.kt @@ -21,11 +21,11 @@ class NPCOptionHandler : InstructionHandler() { private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractNPC) { + override fun validate(player: Player, instruction: InteractNPC): Boolean { if (player.contains("delay")) { - return + return false } - val npc = NPCs.indexed(instruction.npcIndex) ?: return + val npc = NPCs.indexed(instruction.npcIndex) ?: return false var def = npc.def val transform = npc["transform_id", ""] if (transform.isNotBlank()) { @@ -38,19 +38,20 @@ class NPCOptionHandler : InstructionHandler() { if (selectedOption == null) { player.noInterest() logger.warn { "Invalid npc option $npc $index ${options.contentToString()}" } - return + return false } if (selectedOption == "Listen-to" && player["movement", "walk"] == "music") { player.message("You are already resting.") - return + return false } if (player.hasClock("stunned")) { player.message("You're stunned!", ChatType.Filter) - return + return false } player.closeInterfaces() player.talkWith(npc, definition) player.interactNpc(npc, selectedOption) + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ObjectOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ObjectOptionHandler.kt index b5f2364f3a..20960ce2bd 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ObjectOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/ObjectOptionHandler.kt @@ -21,31 +21,32 @@ class ObjectOptionHandler : InstructionHandler() { private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractObject) { + override fun validate(player: Player, instruction: InteractObject): Boolean { if (player.contains("delay")) { - return + return false } val (objectId, x, y, option) = instruction val tile = player.tile.copy(x = x, y = y) val target = getObject(tile, objectId) if (target == null) { logger.warn { "Invalid object $objectId $tile" } - return + return false } val definition = getDefinition(player, ObjectDefinitions, target.def, target.def) val options = definition.options if (options == null) { logger.warn { "Invalid object interaction $target $option ${definition.options.contentToString()}" } - return + return false } val index = option - 1 val selectedOption = options.getOrNull(index) if (selectedOption == null) { - logger.warn { "Invalid object option $target $index ${definition.options.contentToString()}" } - return + logger.warn { "Invalid object option $target $index ${options.contentToString()}" } + return false } player.closeInterfaces() player.interactObject(target, selectedOption) + return true } private fun getObject(tile: Tile, objectId: Int): GameObject? { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/PlayerOptionHandler.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/PlayerOptionHandler.kt index 6502d34712..31515006a8 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/PlayerOptionHandler.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/handle/PlayerOptionHandler.kt @@ -16,16 +16,16 @@ class PlayerOptionHandler : InstructionHandler() { private val logger = InlineLogger() - override fun validate(player: Player, instruction: InteractPlayer) { + override fun validate(player: Player, instruction: InteractPlayer): Boolean { if (player.contains("delay")) { - return + return false } - val target = Players.indexed(instruction.playerIndex) ?: return + val target = Players.indexed(instruction.playerIndex) ?: return false val optionIndex = instruction.option val option = player.options.get(optionIndex) if (option == PlayerOptions.EMPTY_OPTION) { logger.info { "Invalid player option $optionIndex ${player.options.get(optionIndex)} for $player on $target" } - return + return false } player.closeInterfaces() if (option == "Follow") { @@ -33,6 +33,7 @@ class PlayerOptionHandler : InstructionHandler() { } else { player.interactPlayer(target, option) } + return true } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt index 5a7f07cec1..9633afbe3f 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt @@ -1,5 +1,6 @@ package world.gregs.voidps.engine.data +import world.gregs.voidps.engine.GameLoop import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.client.ui.InterfaceOptions import world.gregs.voidps.engine.client.ui.Interfaces @@ -48,7 +49,7 @@ class AccountManager( this["new_player"] = true } - fun setup(player: Player, client: Client?, displayMode: Int): Boolean { + fun setup(player: Player, client: Client?, displayMode: Int, viewport: Boolean = true): Boolean { player.index = Players.index() ?: return false player.visuals.hits.self = player.index player.interfaces = Interfaces(player, interfaceDefinitions) @@ -70,10 +71,10 @@ class AccountManager( accountDefinitions.add(player) } player.interfaces.displayMode = displayMode - if (client != null) { + player.client = client + (player.variables as PlayerVariables).client = client + if (viewport) { player.viewport = Viewport() - player.client = client - (player.variables as PlayerVariables).client = client } player.collision = CollisionStrategyProvider.get(character = player) return true @@ -95,7 +96,7 @@ class AccountManager( val original = player.tile.minus(offset) for (def in Areas.get(original.zone)) { if (original in def.area) { - Moved.enter(player, def.name, def.area) + Moved.enter(player, def.name, def) } } } @@ -120,14 +121,14 @@ class AccountManager( } player.client?.disconnect() connectionQueue.disconnect { - World.queue("logout", 1) { + World.queue("logout_${player.accountName}", 1) { Players.remove(player) } val offset = player.get("instance_offset")?.let { Delta(it) } ?: Delta.EMPTY val original = player.tile.minus(offset) for (def in Areas.get(original.zone)) { if (original in def.area) { - Moved.exit(player, def.name, def.area) + Moved.exit(player, def.name, def) } } Despawn.player(player) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/Areas.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/Areas.kt index 4d4360ecc0..6fcc9c2b99 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/Areas.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/Areas.kt @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import org.jetbrains.annotations.TestOnly import world.gregs.config.Config import world.gregs.voidps.engine.timedLoad import world.gregs.voidps.type.Area @@ -103,7 +104,8 @@ object Areas { return this } - internal fun set(named: Map, tagged: Map>, areas: Map>) { + @TestOnly + fun set(named: Map, tagged: Map>, areas: Map>) { this.named = named this.tagged = tagged this.areas = areas diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/AreaQueue.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/AreaQueue.kt index e968d3a6fd..449b3e2346 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/AreaQueue.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/AreaQueue.kt @@ -24,12 +24,12 @@ class AreaQueue( val to = player.tile for (def in Areas.get(from.zone)) { if (from in def.area && to !in def.area) { - Moved.exit(player, def.name, def.area) + Moved.exit(player, def.name, def) } } for (def in Areas.get(to.zone)) { if (to in def.area && from !in def.area) { - Moved.enter(player, def.name, def.area) + Moved.enter(player, def.name, def) } } } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Moved.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Moved.kt index dee0e52c8a..143e555340 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Moved.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Moved.kt @@ -2,6 +2,7 @@ package world.gregs.voidps.engine.entity.character.mode.move import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectArrayList +import world.gregs.voidps.engine.data.definition.AreaDefinition import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.event.Wildcard @@ -23,21 +24,21 @@ interface Moved { } } - fun entered(area: String, handler: Player.(area: Area) -> Unit) { + fun entered(area: String, handler: Player.(area: AreaDefinition) -> Unit) { entered.getOrPut(area) { mutableListOf() }.add(handler) } - fun exited(area: String, handler: Player.(area: Area) -> Unit) { + fun exited(area: String, handler: Player.(area: AreaDefinition) -> Unit) { exited.getOrPut(area) { mutableListOf() }.add(handler) } companion object : AutoCloseable { - private val entered = Object2ObjectOpenHashMap Unit>>(25) - private val exited = Object2ObjectOpenHashMap Unit>>(25) + private val entered = Object2ObjectOpenHashMap Unit>>(25) + private val exited = Object2ObjectOpenHashMap Unit>>(25) val playerMoved = ObjectArrayList<(Player, Tile) -> Unit>(15) private val npcMoved = Object2ObjectOpenHashMap Unit>>(10) - fun enter(player: Player, id: String, area: Area) { + fun enter(player: Player, id: String, area: AreaDefinition) { for (handler in entered[id] ?: emptyList()) { handler(player, area) } @@ -46,7 +47,7 @@ interface Moved { } } - fun exit(player: Player, id: String, area: Area) { + fun exit(player: Player, id: String, area: AreaDefinition) { for (handler in exited[id] ?: emptyList()) { handler(player, area) } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt index 5fc879afc8..6f271ba7f2 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt @@ -222,12 +222,12 @@ open class Movement( val fromOriginal = from.minus(offset) for (def in Areas.get(fromOriginal.zone)) { if (fromOriginal in def.area && toOriginal !in def.area) { - Moved.exit(character, def.name, def.area) + Moved.exit(character, def.name, def) } } for (def in Areas.get(toOriginal.zone)) { if (toOriginal in def.area && fromOriginal !in def.area) { - Moved.enter(character, def.name, def.area) + Moved.enter(character, def.name, def) } } } else if (character is NPC) { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/event/Wildcards.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/event/Wildcards.kt index 92fb9a7627..b8a0da5b6d 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/event/Wildcards.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/event/Wildcards.kt @@ -79,6 +79,12 @@ object Wildcards { } } + fun get(key: String, type: Wildcard): Set { + val set = mutableSetOf() + find(key, type) { set.add(it) } + return set + } + fun find(key: String, type: Wildcard, block: (String) -> Unit) { if (key == "*") { block(key) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/inv/Inventories.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/inv/Inventories.kt index 2a49d97c72..e8dc56dd06 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/inv/Inventories.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/inv/Inventories.kt @@ -126,6 +126,10 @@ val Player.equipment: Inventory val Player.beastOfBurden: Inventory get() = inventories.inventory("beast_of_burden") -fun Player.holdsItem(id: String) = inventory.contains(id) || equipment.contains(id) +fun Player.carriesItem(id: String) = inventory.contains(id) || equips(id) -fun Player.holdsItem(id: String, amount: Int) = inventory.contains(id, amount) || equipment.contains(id, amount) +fun Player.carriesItem(id: String, amount: Int) = inventory.contains(id, amount) || equips(id, amount) + +fun Player.equips(id: String) = equipment.contains(id) + +fun Player.equips(id: String, amount: Int) = equipment.contains(id, amount) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt index 1642290d1f..ff5c13797f 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt @@ -26,6 +26,7 @@ import world.gregs.voidps.engine.entity.character.player.equip.AppearanceOverrid import world.gregs.voidps.engine.script.KoinMock import world.gregs.voidps.network.client.Client import world.gregs.voidps.network.client.ConnectionQueue +import world.gregs.voidps.network.client.DummyClient import world.gregs.voidps.network.login.protocol.encode.logout import world.gregs.voidps.type.Tile import world.gregs.voidps.type.area.Rectangle @@ -99,7 +100,7 @@ class AccountManagerTest : KoinMock() { @Test fun `Initialise player`() { val player = Player(0) - manager.setup(player, null, 0) + manager.setup(player, DummyClient(), 0, false) assertNotNull(player.visuals) assertNotNull(player.interfaces) assertNotNull(player.interfaceOptions) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovedTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovedTest.kt index 734f421d98..867e9959d6 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovedTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/mode/move/MovedTest.kt @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Nested import world.gregs.voidps.engine.Caller import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.ScriptTest +import world.gregs.voidps.engine.data.definition.AreaDefinition import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.type.Tile @@ -67,12 +68,13 @@ class MovedTest { override fun Script.register(args: List, caller: Caller) { entered(args[0]) { area -> caller.call() - assertEquals(Rectangle(Tile(1, 2), 1, 2), area) + assertEquals(Rectangle(Tile(1, 2), 1, 2), area.area) } } override fun invoke(args: List) { - Moved.enter(Player(), args[0], Rectangle(Tile(1, 2), 1, 2)) + val def = AreaDefinition("", area = Rectangle(Tile(1, 2), 1, 2), tags = emptySet()) + Moved.enter(Player(), args[0], def) } override val apis = listOf(Moved) @@ -89,12 +91,13 @@ class MovedTest { override fun Script.register(args: List, caller: Caller) { exited(args[0]) { area -> caller.call() - assertEquals(Rectangle(Tile(1, 2), 1, 2), area) + assertEquals(Rectangle(Tile(1, 2), 1, 2), area.area) } } override fun invoke(args: List) { - Moved.exit(Player(), args[0], Rectangle(Tile(1, 2), 1, 2)) + val def = AreaDefinition("", area = Rectangle(Tile(1, 2), 1, 2), tags = emptySet()) + Moved.exit(Player(), args[0], def) } override val apis = listOf(Moved) diff --git a/game/src/main/kotlin/ContentLoader.kt b/game/src/main/kotlin/ContentLoader.kt index ac88b95b9f..6dcc8af0e9 100644 --- a/game/src/main/kotlin/ContentLoader.kt +++ b/game/src/main/kotlin/ContentLoader.kt @@ -1,5 +1,4 @@ import com.github.michaelbull.logging.InlineLogger -import content.bot.Bots import content.skill.prayer.PrayerApi import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.ui.chat.plural @@ -43,7 +42,6 @@ class ContentLoader { private fun loadContentApis() { Script.interfaces.add(PrayerApi) - Script.interfaces.add(Bots) } private fun loadScript(name: String): Any { diff --git a/game/src/main/kotlin/GameModules.kt b/game/src/main/kotlin/GameModules.kt index ea1ad74bd6..aa5397f88f 100644 --- a/game/src/main/kotlin/GameModules.kt +++ b/game/src/main/kotlin/GameModules.kt @@ -1,7 +1,5 @@ -import content.bot.TaskManager -import content.bot.interact.navigation.graph.NavigationGraph -import content.bot.interact.path.Dijkstra -import content.bot.interact.path.DijkstraFrontier +import content.bot.BotManager +import content.bot.behaviour.loadGraph import content.entity.obj.ship.CharterShips import content.entity.player.modal.book.Books import content.entity.world.music.MusicTracks @@ -9,32 +7,20 @@ import content.quest.member.fairy_tale_part_2.fairy_ring.FairyRingCodes import content.skill.farming.FarmingDefinitions import content.social.trade.exchange.GrandExchange import content.social.trade.exchange.history.ExchangeHistory -import kotlinx.io.pool.DefaultPool import org.koin.dsl.module import world.gregs.voidps.engine.client.instruction.InstructionHandlers import world.gregs.voidps.engine.client.instruction.InterfaceHandler -import world.gregs.voidps.engine.data.* -import world.gregs.voidps.engine.data.definition.ObjectDefinitions +import world.gregs.voidps.engine.data.ConfigFiles +import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.Storage import world.gregs.voidps.engine.data.file.FileStorage import world.gregs.voidps.engine.entity.item.floor.ItemSpawns import java.io.File fun gameModule(files: ConfigFiles) = module { single { ItemSpawns() } - single { TaskManager() } - single { - val size = get().size - Dijkstra( - get(), - object : DefaultPool(10) { - override fun produceInstance() = DijkstraFrontier(size) - }, - ) - } - single(createdAtStart = true) { - get() - NavigationGraph().load(files.find(Settings["map.navGraph"])) - } + single { BotManager().load(files) } + single { loadGraph(files) } single(createdAtStart = true) { Books().load(files.list(Settings["definitions.books"])) } single(createdAtStart = true) { MusicTracks().load(files.find(Settings["map.music"])) } single(createdAtStart = true) { FairyRingCodes().load(files.find(Settings["definitions.fairyCodes"])) } diff --git a/game/src/main/kotlin/GameTick.kt b/game/src/main/kotlin/GameTick.kt index 91fe271601..cb2f99fc35 100644 --- a/game/src/main/kotlin/GameTick.kt +++ b/game/src/main/kotlin/GameTick.kt @@ -1,4 +1,5 @@ import com.github.michaelbull.logging.InlineLogger +import content.bot.BotManager import content.social.trade.exchange.GrandExchange import world.gregs.voidps.engine.client.instruction.InstructionHandlers import world.gregs.voidps.engine.client.instruction.InstructionTask @@ -42,6 +43,7 @@ fun getTickStages( sequential: Boolean = CharacterTask.DEBUG, handlers: InstructionHandlers = get(), dynamicZones: DynamicZones = get(), + botManager: BotManager = get(), ): List { val sequentialNpc: TaskIterator = SequentialIterator() val sequentialPlayer: TaskIterator = SequentialIterator() @@ -70,19 +72,12 @@ fun getTickStages( PlayerUpdateTask(), NPCUpdateTask(npcVisualEncoders()), ), - AiTick, + botManager, accountSave, SaveLogs(), ) } -object AiTick : Runnable { - var method: (() -> Unit)? = null - override fun run() { - method?.invoke() - } -} - private class SaveLogs : Runnable { private val directory = File(Settings["storage.players.logs"]) private var ticks = TimeUnit.SECONDS.toTicks(Settings["storage.players.logs.seconds", 10]) diff --git a/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt b/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt index a96139f1cb..c63fbc6b8d 100644 --- a/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt +++ b/game/src/main/kotlin/content/area/asgarnia/falador/MakeoverMage.kt @@ -13,7 +13,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.chat.notEnough import world.gregs.voidps.engine.entity.character.player.flagAppearance import world.gregs.voidps.engine.entity.character.player.male -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.transact.TransactionError import world.gregs.voidps.engine.inv.transact.operation.AddItem.add @@ -157,7 +157,7 @@ class MakeoverMage(val enums: EnumDefinitions) : Script { fun ChoiceOption.amulet(): Unit = option("Cool amulet! Can I have one?") { val cost = 100 npc("No problem, but please remember that the amulet I will sell you is only a copy of my own. It contains no magical powers and, as such, will only cost you $cost coins.") - if (!holdsItem("coins", cost)) { + if (!carriesItem("coins", cost)) { player("Oh, I don't have enough money for that.") return@option } diff --git a/game/src/main/kotlin/content/area/asgarnia/falador/SirVyvin.kt b/game/src/main/kotlin/content/area/asgarnia/falador/SirVyvin.kt index d96e117eb6..297a1e4245 100644 --- a/game/src/main/kotlin/content/area/asgarnia/falador/SirVyvin.kt +++ b/game/src/main/kotlin/content/area/asgarnia/falador/SirVyvin.kt @@ -19,7 +19,7 @@ import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.entity.obj.replace import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.timer.toTicks import java.util.concurrent.TimeUnit @@ -48,7 +48,7 @@ class SirVyvin( npc("HEY! Just WHAT do you THINK you are DOING??? STAY OUT of MY cupboard!") return@objectOperate } - if (holdsItem("portrait")) { + if (carriesItem("portrait")) { statement("There is just a load of junk in here.") } else { statement("You find a small portrait in here which you take.") diff --git a/game/src/main/kotlin/content/area/asgarnia/falador/SquireAsrol.kt b/game/src/main/kotlin/content/area/asgarnia/falador/SquireAsrol.kt index e9b0413006..d4be43bf21 100644 --- a/game/src/main/kotlin/content/area/asgarnia/falador/SquireAsrol.kt +++ b/game/src/main/kotlin/content/area/asgarnia/falador/SquireAsrol.kt @@ -13,8 +13,8 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.combatLevel import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.event.AuditLog +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.equipment -import world.gregs.voidps.engine.inv.holdsItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.softQueue @@ -57,7 +57,7 @@ class SquireAsrol : Script { suspend fun Player.checkPicture() { npc("So how are you doing getting a sword?") - if (holdsItem("portrait")) { + if (carriesItem("portrait")) { player("I have the picture. I'll just take it to the dwarf now!") npc("Please hurry!") return @@ -72,7 +72,7 @@ class SquireAsrol : Script { npc("So can you un-equip it and hand it over to me now please?") return } - if (holdsItem("blurite_sword")) { + if (carriesItem("blurite_sword")) { player("I have retrieved your sword for you.") npc("Thank you, thank you, thank you! I was seriously worried I would have to own up to Sir Vyvin!") statement("You give the sword to the squire.") diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/PortSarim.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/PortSarim.kt index 1825de7c18..f153a3bde8 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/PortSarim.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/PortSarim.kt @@ -10,14 +10,14 @@ import world.gregs.voidps.engine.entity.character.player.skill.exp.exp import world.gregs.voidps.engine.entity.character.player.skill.level.Level import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.queue.weakQueue class PortSarim : Script { init { takeable("white_apron_port_sarim") { - if (holdsItem("white_apron")) { + if (carriesItem("white_apron")) { message("You already have one of those.") null } else { diff --git a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt index de52b1a84d..d930c2fa08 100644 --- a/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt +++ b/game/src/main/kotlin/content/area/asgarnia/port_sarim/Thurgo.kt @@ -6,8 +6,8 @@ import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.item.Item +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.contains -import world.gregs.voidps.engine.inv.holdsItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.inv.transact.operation.AddItem.add @@ -42,7 +42,7 @@ class Thurgo : Script { suspend fun Player.menuReplacementSword() { choice { - if (holdsItem("blurite_sword")) { + if (carriesItem("blurite_sword")) { madeSword() } else { replacementSword() @@ -119,7 +119,7 @@ class Thurgo : Script { fun ChoiceOption.aboutSword() = option("About that sword...") { npc("Have you got a picture of the sword for me yet?") - if (!holdsItem("portrait")) { + if (!carriesItem("portrait")) { player("Sorry, not yet.") npc("Well, come back when you do.") return@option @@ -145,7 +145,7 @@ class Thurgo : Script { } fun ChoiceOption.redberryPie(player: Player) { - if (!player.holdsItem("redberry_pie")) { + if (!player.carriesItem("redberry_pie")) { return } option("Would you like a redberry pie?") { diff --git a/game/src/main/kotlin/content/area/kandarin/ardougne/Alrena.kt b/game/src/main/kotlin/content/area/kandarin/ardougne/Alrena.kt index 3f9a26bdb4..436945c3e6 100644 --- a/game/src/main/kotlin/content/area/kandarin/ardougne/Alrena.kt +++ b/game/src/main/kotlin/content/area/kandarin/ardougne/Alrena.kt @@ -10,7 +10,7 @@ import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace @@ -40,7 +40,7 @@ class Alrena : Script { suspend fun Player.started(target: NPC) { player("Hello, Edmond has asked me to help find your daughter.") npc("Yes he told me. I've begun making your special gas mask, but I need some dwellberries to finish it.") - if (holdsItem("dwellberries")) { + if (carriesItem("dwellberries")) { player("Yes I've got some here.") item("dwellberries", 600, "You give the dwellberries to Alrena.") target.anim("human_herbing_grind") diff --git a/game/src/main/kotlin/content/area/kandarin/ardougne/Edmond.kt b/game/src/main/kotlin/content/area/kandarin/ardougne/Edmond.kt index ffe50c7c42..b2656fd8b2 100644 --- a/game/src/main/kotlin/content/area/kandarin/ardougne/Edmond.kt +++ b/game/src/main/kotlin/content/area/kandarin/ardougne/Edmond.kt @@ -26,7 +26,7 @@ import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.engine.entity.obj.ObjectShape import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.type.Direction import world.gregs.voidps.type.Region @@ -90,7 +90,7 @@ class Edmond : Script { suspend fun Player.started() { player("Hello Edmond.") npc("Have you got the dwellberries yet?") - if (holdsItem("dwellberries")) { + if (carriesItem("dwellberries")) { player("Yes I've got some here.") npc("Take them to my wife Alrena, she's inside.") } else { @@ -152,7 +152,7 @@ class Edmond : Script { suspend fun Player.spoken() { player("Hello.") npc("Have you found Elena yet?") - if (holdsItem("picture_plague_city")) { + if (carriesItem("picture_plague_city")) { player("Not yet, it's a big city over there.") npc("I hope it's not too late.") } else { diff --git a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Bravek.kt b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Bravek.kt index c7b1dcef11..fcb293b9fa 100644 --- a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Bravek.kt +++ b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Bravek.kt @@ -12,7 +12,7 @@ import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.softQueue @@ -88,7 +88,7 @@ class Bravek : Script { suspend fun Player.hasCurePaper(target: NPC) { npc("Uurgh! My head still hurts too much to think straight. Oh for one of Trudi's hangover cures!") - if (holdsItem("hangover_cure")) { + if (carriesItem("hangover_cure")) { player("Try this.") inventory.remove("hangover_cure") set("plague_city", "gave_cure") @@ -105,7 +105,7 @@ class Bravek : Script { suspend fun Player.gaveCure() { npc("Thanks again for the hangover cure.") - if (holdsItem("warrant")) { + if (carriesItem("warrant")) { player("Not a problem, happy to help out.") npc("I'm just having a little drop of whisky, then I'll feel really good.") } else { diff --git a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Mourner.kt b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Mourner.kt index a86b047669..21370b58cc 100644 --- a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Mourner.kt +++ b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/Mourner.kt @@ -9,7 +9,7 @@ import content.entity.player.dialogue.type.statement import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.npc.NPCs import world.gregs.voidps.engine.entity.obj.GameObjects -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.type.Direction import world.gregs.voidps.type.Tile import world.gregs.voidps.type.equals @@ -18,7 +18,7 @@ class Mourner : Script { init { npcOperate("Talk-to", "mourner_elena_guard_vis") { (target) -> - if (holdsItem("warrant")) { + if (carriesItem("warrant")) { player("I have a warrant from Bravek to enter here.") npc("This is highly irregular. Please wait...") val otherGuard = NPCs.find(if (target.tile.equals(2539, 3273)) Tile(2534, 3273) else Tile(2539, 3273), "mourner_elena_guard_vis") diff --git a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/WestArdougne.kt b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/WestArdougne.kt index b93c9ed2d8..995ca34d7d 100644 --- a/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/WestArdougne.kt +++ b/game/src/main/kotlin/content/area/kandarin/ardougne/west_ardougne/WestArdougne.kt @@ -14,7 +14,7 @@ import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.entity.obj.remove import world.gregs.voidps.engine.entity.obj.replace import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.softQueue @@ -84,7 +84,7 @@ class WestArdougne : Script { return@objectOperate } npc("ted_rehnison", "Go away. We don't want any.") - if (holdsItem("book_turnip_growing_for_beginners")) { + if (carriesItem("book_turnip_growing_for_beginners")) { player("I'm a friend of Jethick's, I have come to return a book he borrowed.") npc("ted_rehnison", "Oh... Why didn't you say, come in then.") enterDoor(target, delay = 2) diff --git a/game/src/main/kotlin/content/area/kandarin/catherby/Harry.kt b/game/src/main/kotlin/content/area/kandarin/catherby/Harry.kt index f53fc71e72..259a7cb4c9 100644 --- a/game/src/main/kotlin/content/area/kandarin/catherby/Harry.kt +++ b/game/src/main/kotlin/content/area/kandarin/catherby/Harry.kt @@ -5,7 +5,7 @@ import content.entity.player.dialogue.* import content.entity.player.dialogue.type.* import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove @@ -20,18 +20,18 @@ class Harry : Script { openShop("harrys_fishing_shop") } - if (holdsItem("fishbowl_water") || holdsItem("fishbowl_seaweed")) { + if (carriesItem("fishbowl_water") || carriesItem("fishbowl_seaweed")) { option("Can I get a fish for this bowl?") { player("Can I get a fish for this bowl?") when { - holdsItem("fishbowl_water") -> { + carriesItem("fishbowl_water") -> { npc("Sorry, you need to put some seaweed into the bowl first") player("Seaweed?") npc("Yes, the fish seem to like it. Come and see me when you have put some in the bowl.") } - holdsItem("fishbowl_seaweed") -> { + carriesItem("fishbowl_seaweed") -> { npc("Yes, you can. I can see that you have a nicely filled fishbowl there to use, and you can catch a fish from my aquarium if you want. You will need a special net to do this though - I sell them for 10 gold.") choice { option("I'll take it.") { @@ -56,7 +56,7 @@ class Harry : Script { // TODO: add fishing spot for tiny_net & get pet_fish to work like it is in real runescape - if (holdsItem("fishbowl_water") || holdsItem("fishbowl_seaweed") || holdsItem("fishbowl")) { + if (carriesItem("fishbowl_water") || carriesItem("fishbowl_seaweed") || carriesItem("fishbowl")) { option("Do you have any fish food?") { player("Do you have any fish food?") npc("Sorry, I'm all out. I used up the last of it feeding the fish in the aquarium. I have some empty boxes, though - they have the ingredients written on the back.") diff --git a/game/src/main/kotlin/content/area/kharidian_desert/al_kharid/Ellis.kt b/game/src/main/kotlin/content/area/kharidian_desert/al_kharid/Ellis.kt index b9fd7f2b5a..57b94f7dee 100644 --- a/game/src/main/kotlin/content/area/kharidian_desert/al_kharid/Ellis.kt +++ b/game/src/main/kotlin/content/area/kharidian_desert/al_kharid/Ellis.kt @@ -20,7 +20,7 @@ import world.gregs.voidps.engine.data.definition.ItemDefinitions import world.gregs.voidps.engine.data.definition.data.Tanning import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.male -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove import world.gregs.voidps.engine.inv.transact.operation.ReplaceItem.replace @@ -86,11 +86,10 @@ class Ellis : Script { fun tan(player: Player, type: String, amount: Int) { val item = type.removeSuffix("_1") - if (!player.holdsItem(item)) { + if (!player.carriesItem(item)) { player.message("You don't have any ${item.toLowerSpaceCase()} to tan.") return } - player.softTimers.start("tanning") val tanning: Tanning = ItemDefinitions.get(item)["tanning"] val (leather, cost) = tanning.prices[if (type.endsWith("_1")) 1 else 0] var tanned = 0 @@ -108,7 +107,6 @@ class Ellis : Script { } tanned++ } - player.softTimers.stop("tanning") if (tanned == 1) { player.message("The tanner tans your ${item.toLowerSpaceCase()}.") } else if (tanned > 0) { diff --git a/game/src/main/kotlin/content/area/kharidian_desert/sophanem/GuardianMummy.kt b/game/src/main/kotlin/content/area/kharidian_desert/sophanem/GuardianMummy.kt index 8b74efe5e5..ffeff04a27 100644 --- a/game/src/main/kotlin/content/area/kharidian_desert/sophanem/GuardianMummy.kt +++ b/game/src/main/kotlin/content/area/kharidian_desert/sophanem/GuardianMummy.kt @@ -12,7 +12,7 @@ import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.inv.Inventory -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace import world.gregs.voidps.engine.inv.transact.TransactionError @@ -27,7 +27,7 @@ class GuardianMummy : Script { init { npcOperate("Talk-to", "guardian_mummy") { - if (holdsItem("pharaohs_sceptre")) { + if (carriesItem("pharaohs_sceptre")) { sceptreRecharging() return@npcOperate } @@ -75,7 +75,7 @@ class GuardianMummy : Script { iKnowWhatImDoing() } option("I want to charge or remove charges from my sceptre.") { - if (holdsItem("pharaohs_sceptre")) { + if (carriesItem("pharaohs_sceptre")) { sceptreRecharging() } else { sceptreDischarging() diff --git a/game/src/main/kotlin/content/area/misthalin/BorderGuard.kt b/game/src/main/kotlin/content/area/misthalin/BorderGuard.kt index cce97942b6..d1d1b55b0c 100644 --- a/game/src/main/kotlin/content/area/misthalin/BorderGuard.kt +++ b/game/src/main/kotlin/content/area/misthalin/BorderGuard.kt @@ -1,12 +1,12 @@ package content.area.misthalin import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.data.definition.AreaDefinition import world.gregs.voidps.engine.data.definition.Areas import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.engine.entity.obj.ObjectLayer -import world.gregs.voidps.type.Area import world.gregs.voidps.type.Distance.nearestTo import world.gregs.voidps.type.area.Rectangle import kotlin.collections.set @@ -47,8 +47,8 @@ class BorderGuard : Script { exited("border_guard_draynor_falador", ::exit) } - fun enter(player: Player, area: Area) { - val border = area as Rectangle + fun enter(player: Player, def: AreaDefinition) { + val border = def.area as Rectangle if (player.steps.destination in border || player.steps.isEmpty()) { val tile = border.nearestTo(player.tile) val endSide = Border.getOppositeSide(border, tile) @@ -60,8 +60,8 @@ class BorderGuard : Script { changeGuardState(guards, true) } - fun exit(player: Player, area: Area) { - val border = area as Rectangle + fun exit(player: Player, def: AreaDefinition) { + val border = def.area as Rectangle val guards = guards[border] ?: return player.steps.update(noCollision = false, noRun = false) changeGuardState(guards, false) diff --git a/game/src/main/kotlin/content/area/misthalin/barbarian_village/Dororan.kt b/game/src/main/kotlin/content/area/misthalin/barbarian_village/Dororan.kt index 730ec9e9e8..5bebda0b96 100644 --- a/game/src/main/kotlin/content/area/misthalin/barbarian_village/Dororan.kt +++ b/game/src/main/kotlin/content/area/misthalin/barbarian_village/Dororan.kt @@ -12,7 +12,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.chat.noInterest import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace import world.gregs.voidps.engine.queue.softQueue @@ -975,7 +975,7 @@ class Dororan : Script { engrave() return } - if (holdsItem("ring_from_jeffery")) { + if (carriesItem("ring_from_jeffery")) { player("I have one right here.") item("ring_from_jeffery", 600, "You show Dororan the ring from Jeffery.") npc("Thank you! That's exactly what I need!") diff --git a/game/src/main/kotlin/content/area/misthalin/barbarian_village/Gudrun.kt b/game/src/main/kotlin/content/area/misthalin/barbarian_village/Gudrun.kt index 2a26219159..1b6c47d301 100644 --- a/game/src/main/kotlin/content/area/misthalin/barbarian_village/Gudrun.kt +++ b/game/src/main/kotlin/content/area/misthalin/barbarian_village/Gudrun.kt @@ -22,7 +22,7 @@ import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.engine.entity.obj.ObjectShape import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.softQueue @@ -266,7 +266,7 @@ class Gudrun : Script { } suspend fun Player.poem() { - if (holdsItem("gunnars_ground")) { + if (carriesItem("gunnars_ground")) { npc("What have you got there?") player("Another gift from your mysterious suitor.") npc("A scroll?") @@ -342,7 +342,7 @@ class Gudrun : Script { npc("Sorry about that, stranger. Did you want something?.") player("Are you Gudrun?") npc("Yes.") - if (holdsItem("dororans_engraved_ring")) { + if (carriesItem("dororans_engraved_ring")) { player("This is for you.") anim("hand_over_item") item("dororans_engraved_ring", 400, "You show Gudrun the ring.") diff --git a/game/src/main/kotlin/content/area/misthalin/barbarian_village/stronghold_of_security/StrongholdOfSecurityRewards.kt b/game/src/main/kotlin/content/area/misthalin/barbarian_village/stronghold_of_security/StrongholdOfSecurityRewards.kt index 9df591d2eb..39f4ddf5f1 100644 --- a/game/src/main/kotlin/content/area/misthalin/barbarian_village/stronghold_of_security/StrongholdOfSecurityRewards.kt +++ b/game/src/main/kotlin/content/area/misthalin/barbarian_village/stronghold_of_security/StrongholdOfSecurityRewards.kt @@ -13,7 +13,7 @@ import world.gregs.voidps.engine.entity.character.jingle import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace @@ -116,7 +116,7 @@ class StrongholdOfSecurityRewards : Script { objectOperate("Search", "stronghold_dead_explorer") { anim("pick_pocket") sound("pick") - if (holdsItem("stronghold_notes")) { + if (carriesItem("stronghold_notes")) { message("You don't find anything.") return@objectOperate } diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/Aggie.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/Aggie.kt index 78052069f3..f1ec65903f 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/Aggie.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/Aggie.kt @@ -10,7 +10,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.inv.transact.operation.AddItem.add @@ -31,7 +31,7 @@ class Aggie : Script { "leela", "equipment", "joe_one_beer", "joe_two_beers", "joe_three_beers", "tie_up_lady_keli" -> { option("Talk about Prince Ali Rescue.") { player("Could you think of a way to make skin paste?") - if (holdsItem("ashes") && holdsItem("pot_of_flour") && holdsItem("bucket_of_water") && holdsItem("redberries")) { + if (carriesItem("ashes") && carriesItem("pot_of_flour") && carriesItem("bucket_of_water") && carriesItem("redberries")) { npc("Yes I can. I see you already have the ingredients. Would you like me to mix some for you now?") choice { option("Yes please. Mix me some skin paste.") { diff --git a/game/src/main/kotlin/content/area/misthalin/draynor_village/Ned.kt b/game/src/main/kotlin/content/area/misthalin/draynor_village/Ned.kt index 3f8e6108d2..6f772340e1 100644 --- a/game/src/main/kotlin/content/area/misthalin/draynor_village/Ned.kt +++ b/game/src/main/kotlin/content/area/misthalin/draynor_village/Ned.kt @@ -8,7 +8,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.male import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.inv.transact.operation.AddItem.add @@ -34,7 +34,7 @@ class Ned : Script { fun ChoiceOption.wig() { option("How about some sort of wig?") { npc("Well... that's an interesting thought. Yes, I think I could do something. Give me three balls of wool and I might be able to do it.") - if (!holdsItem("ball_of_wool", 3)) { + if (!carriesItem("ball_of_wool", 3)) { player("Great, I will get some. I think a wig would be useful.") return@option } diff --git a/game/src/main/kotlin/content/area/misthalin/edgeville/Jeffery.kt b/game/src/main/kotlin/content/area/misthalin/edgeville/Jeffery.kt index 33b68f547b..e93434d50c 100644 --- a/game/src/main/kotlin/content/area/misthalin/edgeville/Jeffery.kt +++ b/game/src/main/kotlin/content/area/misthalin/edgeville/Jeffery.kt @@ -10,8 +10,8 @@ import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.equipment -import world.gregs.voidps.engine.inv.holdsItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace @@ -37,7 +37,7 @@ class Jeffery : Script { choice { option("I was hoping you would trade me a gold ring.") { npc("Trade you? Trade you for what?") - if (holdsItem("love_poem")) { + if (carriesItem("love_poem")) { choice { option("This splendid love poem.") { lovePoem(target) diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Cook.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Cook.kt index ef648396c2..aceb495f29 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Cook.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Cook.kt @@ -12,7 +12,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove @@ -47,33 +47,33 @@ class Cook : Script { suspend fun Player.started() { npc("how are you getting on with finding the ingredients?") - if (holdsItem("top_quality_milk")) { + if (carriesItem("top_quality_milk")) { item("top_quality_milk", 500, "You give the top-quality milk to the cook.") inventory.remove("top_quality_milk") set("cooks_assistant_milk", 1) player("Here's some top-quality milk.") } - if (holdsItem("extra_fine_flour")) { + if (carriesItem("extra_fine_flour")) { item("extra_fine_flour", 500, "You give the extra fine flour to the cook.") inventory.remove("extra_fine_flour") set("cooks_assistant_flour", 1) player("Here's the extra fine flour.") } - if (holdsItem("super_large_egg")) { + if (carriesItem("super_large_egg")) { item("super_large_egg", 500, "You give the super large egg to the cook.") inventory.remove("super_large_egg") set("cooks_assistant_egg", 1) player("Here's a super large egg.") } - if (holdsItem("egg") && (get("cooks_assistant_egg", 0) == 0)) { + if (carriesItem("egg") && (get("cooks_assistant_egg", 0) == 0)) { player("I've this egg.") npc("No, I need a super large egg. You'll probably find one near the local chickens.") } - if (holdsItem("pot_of_flour") && (get("cooks_assistant_flour", 0) == 0)) { + if (carriesItem("pot_of_flour") && (get("cooks_assistant_flour", 0) == 0)) { player("I've this flour.") npc("That's not fine enough. I imagine if you speak with Millie at the mill to the north she'll help you out.") } - if (holdsItem("bucket_of_milk") && (get("cooks_assistant_milk", 0) == 0)) { + if (carriesItem("bucket_of_milk") && (get("cooks_assistant_milk", 0) == 0)) { player("I've this milk.") npc("Not bad, but not good enough. There's a milk maid that looks after the cows to the north-east. She might have some advice.") } @@ -185,7 +185,7 @@ class Cook : Script { npc("It's called the Cook-o-Matic 25 and it uses a combination of state-of-the-art temperature regulation and magic.") player("Will it mean my food will burn less often?") npc("As long as the food is fairly easy to cook in the first place!") - if (holdsItem("cook_o_matic_manual")) { + if (carriesItem("cook_o_matic_manual")) { npc("The manual you have in your inventory should tell you more.") } else if (inventory.isFull()) { npc("I'd give you the manual, but you don't have room to take it. Ask me again when you have some space.") diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt index 357cd70a65..3bcabfcc52 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/church/FatherAereck.kt @@ -8,7 +8,7 @@ import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.ui.open import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace @@ -76,7 +76,7 @@ class FatherAereck : Script { } suspend fun Player.foundSkull() { - if (holdsItem("ghostspeak_amulet")) { + if (carriesItem("ghostspeak_amulet")) { npc("Have you got rid of the ghost yet?") player("I've finally found the ghost's skull!") npc("Great! Put it in the ghost's coffin and see what happens!") diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/combat_hall/ArcheryTarget.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/combat_hall/ArcheryTarget.kt index 1ba7fe68fb..2dbac1b096 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/combat_hall/ArcheryTarget.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/combat_hall/ArcheryTarget.kt @@ -50,11 +50,11 @@ class ArcheryTarget : Script { } player.ammo = "" val ammo = player.equipped(EquipSlot.Ammo) - if (ammo.id != "training_arrows") { + if (ammo.isNotEmpty() && ammo.id != "training_arrows") { player.message("You can't use that ammo with your bow.") return@weakQueue } - if (ammo.amount < 1) { + if (ammo.isEmpty()) { player.message("There is no ammo left in your quiver.") return@weakQueue } diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/farm/GillieGroats.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/farm/GillieGroats.kt index a0888a0413..a9b906c4ad 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/farm/GillieGroats.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/farm/GillieGroats.kt @@ -9,7 +9,7 @@ import content.entity.player.dialogue.type.player import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class GillieGroats : Script { @@ -17,7 +17,7 @@ class GillieGroats : Script { npcOperate("Talk-to", "gillie_groats") { npc("Hello, I'm Gillie the Milkmaid. What can I do for you?") choice { - if (quest("cooks_assistant") == "started" && !holdsItem("top_quality_milk")) { + if (quest("cooks_assistant") == "started" && !carriesItem("top_quality_milk")) { option("I'm after some Top-quality milk.") { topQualityMilk() } diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/windmill/MillieMiller.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/windmill/MillieMiller.kt index 53fca2f548..93e194561a 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/windmill/MillieMiller.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/windmill/MillieMiller.kt @@ -7,7 +7,7 @@ import content.entity.player.dialogue.type.* import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class MillieMiller : Script { @@ -20,7 +20,7 @@ class MillieMiller : Script { suspend fun Player.menu() { choice { - if (quest("cooks_assistant") == "started" && !holdsItem("extra_fine_flour")) { + if (quest("cooks_assistant") == "started" && !carriesItem("extra_fine_flour")) { option("I'm looking for extra fine flour.") { npc("What's wrong with ordinary flour?") player("Well, I'm no expert chef, but apparently it makes better cakes. This cake, you see, is for Duke Horacio.") diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/Baraek.kt b/game/src/main/kotlin/content/area/misthalin/varrock/Baraek.kt index 66b2372049..3b3e6c1fe2 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/Baraek.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/Baraek.kt @@ -8,7 +8,7 @@ import content.entity.player.dialogue.type.player import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove @@ -16,7 +16,7 @@ class Baraek : Script { init { npcOperate("Talk-to", "baraek") { - if (holdsItem("bear_fur")) { + if (carriesItem("bear_fur")) { choice { option("Can you sell me some furs?") { sellFur() diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/palace/CaptainRovin.kt b/game/src/main/kotlin/content/area/misthalin/varrock/palace/CaptainRovin.kt index 963f69e27e..6a35c3cdc5 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/palace/CaptainRovin.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/palace/CaptainRovin.kt @@ -8,7 +8,7 @@ import content.quest.questCompleted import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory class CaptainRovin : Script { @@ -82,7 +82,7 @@ class CaptainRovin : Script { suspend fun Player.haveYouNotKilledIt() { npc("Yes, you said before, haven't you killed it yet?") player("I'm going to use the powerful sword Silverlight, which I believe you have one of the keys for?") - if (holdsItem("silverlight_key_captain_rovin")) { + if (carriesItem("silverlight_key_captain_rovin")) { npc("I already gave you my key. Check your pockets.") } else { npc("I already gave you my key. Maybe you left it somewhere. Have you checked your bank account?") diff --git a/game/src/main/kotlin/content/area/misthalin/varrock/palace/SirPrysin.kt b/game/src/main/kotlin/content/area/misthalin/varrock/palace/SirPrysin.kt index d2ca789b13..61f1453997 100644 --- a/game/src/main/kotlin/content/area/misthalin/varrock/palace/SirPrysin.kt +++ b/game/src/main/kotlin/content/area/misthalin/varrock/palace/SirPrysin.kt @@ -12,7 +12,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.type.Direction @@ -148,9 +148,9 @@ class SirPrysin : Script { suspend fun Player.keyProgressCheck(target: NPC) { npc("So how are you doing with getting the keys?") - val rovin = holdsItem("silverlight_key_captain_rovin") - val prysin = holdsItem("silverlight_key_sir_prysin") - val traiborn = holdsItem("silverlight_key_wizard_traiborn") + val rovin = carriesItem("silverlight_key_captain_rovin") + val prysin = carriesItem("silverlight_key_sir_prysin") + val traiborn = carriesItem("silverlight_key_wizard_traiborn") when { rovin && prysin && traiborn -> { giveSilverlight(target) diff --git a/game/src/main/kotlin/content/area/misthalin/wizards_tower/Sedridor.kt b/game/src/main/kotlin/content/area/misthalin/wizards_tower/Sedridor.kt index 2dd2e3f771..69d4af6ea7 100644 --- a/game/src/main/kotlin/content/area/misthalin/wizards_tower/Sedridor.kt +++ b/game/src/main/kotlin/content/area/misthalin/wizards_tower/Sedridor.kt @@ -17,7 +17,7 @@ import world.gregs.voidps.engine.entity.character.player.name import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.event.AuditLog import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.softQueue @@ -166,7 +166,7 @@ class Sedridor : Script { npc("Ah, $name. How goes your quest? Have you delivered my research to Aubury yet?") player("Yes, I have. He gave me some notes to give to you.") npc("Wonderful! Let's have a look then.") - if (holdsItem("research_notes_rune_mysteries")) { + if (carriesItem("research_notes_rune_mysteries")) { item("research_notes_rune_mysteries", 600, "You hand the notes to Sedridor.") npc("Alright, let's see what Aubury has for us...") npc("Yes, this is it! The lost incantation!") diff --git a/game/src/main/kotlin/content/area/troll_country/god_wars_dungeon/ArmadylPillar.kt b/game/src/main/kotlin/content/area/troll_country/god_wars_dungeon/ArmadylPillar.kt index d54d61843e..32b6cec048 100644 --- a/game/src/main/kotlin/content/area/troll_country/god_wars_dungeon/ArmadylPillar.kt +++ b/game/src/main/kotlin/content/area/troll_country/god_wars_dungeon/ArmadylPillar.kt @@ -11,8 +11,8 @@ import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.obj.GameObjects import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.equipment -import world.gregs.voidps.engine.inv.holdsItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.type.Direction import world.gregs.voidps.type.Tile @@ -49,7 +49,7 @@ class ArmadylPillar : Script { objectOperate("Search", "godwars_armadyl_crate") { val hasCrossbow = inventory.items.any { Weapon.crossbows.contains(it.id) } || equipment.items.any { Weapon.crossbows.contains(it.id) } - val hasGrapple = holdsItem("mithril_grapple") + val hasGrapple = carriesItem("mithril_grapple") if (!hasCrossbow || !hasGrapple) { if (inventory.add("bronze_crossbow", "mithril_grapple")) { item("bronze_crossbow", 400, "Inside the crate you find a bronze crossbow and a grappling hook.") diff --git a/game/src/main/kotlin/content/bot/Bot.kt b/game/src/main/kotlin/content/bot/Bot.kt index 718ea0b3a4..b1bd35475b 100644 --- a/game/src/main/kotlin/content/bot/Bot.kt +++ b/game/src/main/kotlin/content/bot/Bot.kt @@ -1,9 +1,47 @@ package content.bot +import content.bot.behaviour.BehaviourFrame +import content.bot.behaviour.BehaviourState +import content.bot.behaviour.Reason +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.activity.BotActivity import world.gregs.voidps.engine.entity.character.Character import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.network.client.Instruction +import java.util.Stack data class Bot(val player: Player) : Character by player { - var step: Instruction? = null + val blocked: MutableSet = mutableSetOf() + var previous: BotActivity? = null + val frames = Stack() + val available = mutableSetOf() + var evaluate = mutableSetOf() + + fun noTask() = frames.isEmpty() + + internal fun action(): BotAction = frames.peek().action() + + internal fun frame(): BehaviourFrame = frames.peek() + + internal fun reset() { + frames.clear() + } + + internal fun queue(frame: BehaviourFrame) { + frames.add(frame) + } + + fun stop() { + if (noTask()) { + return + } + frame().state = BehaviourState.Failed(Reason.Cancelled) + } + + override fun toString(): String = "BOT ${player.accountName}" } + +val Player.isBot: Boolean + get() = contains("bot") + +val Player.bot: Bot + get() = get("bot")!! diff --git a/game/src/main/kotlin/content/bot/BotSpawns.kt b/game/src/main/kotlin/content/bot/BotCommands.kt similarity index 54% rename from game/src/main/kotlin/content/bot/BotSpawns.kt rename to game/src/main/kotlin/content/bot/BotCommands.kt index 0bb94bf073..0013d2341e 100644 --- a/game/src/main/kotlin/content/bot/BotSpawns.kt +++ b/game/src/main/kotlin/content/bot/BotCommands.kt @@ -1,5 +1,6 @@ package content.bot +import content.quest.questJournal import kotlinx.coroutines.* import world.gregs.voidps.engine.Contexts import world.gregs.voidps.engine.Script @@ -10,6 +11,7 @@ import world.gregs.voidps.engine.client.command.stringArg import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.data.AccountManager import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.definition.AccountDefinitions import world.gregs.voidps.engine.data.definition.Areas import world.gregs.voidps.engine.data.definition.EnumDefinitions import world.gregs.voidps.engine.data.definition.StructDefinitions @@ -18,8 +20,9 @@ import world.gregs.voidps.engine.entity.World import world.gregs.voidps.engine.entity.character.move.running import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.appearance +import world.gregs.voidps.engine.entity.character.player.chat.ChatType +import world.gregs.voidps.engine.entity.character.player.name import world.gregs.voidps.engine.entity.character.player.sex -import world.gregs.voidps.engine.get import world.gregs.voidps.engine.inv.add import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.timer.* @@ -27,47 +30,64 @@ import world.gregs.voidps.network.client.DummyClient import world.gregs.voidps.network.login.protocol.visual.update.player.BodyColour import world.gregs.voidps.network.login.protocol.visual.update.player.BodyPart import world.gregs.voidps.type.random +import java.io.File import java.util.concurrent.TimeUnit import kotlin.coroutines.resume import kotlin.text.toIntOrNull -class BotSpawns( +class BotCommands( val enums: EnumDefinitions, val structs: StructDefinitions, - val tasks: TaskManager, val loader: PlayerAccountLoader, + val manager: BotManager, + val accounts: AccountManager, + accountDefinitions: AccountDefinitions, ) : Script { val bots = mutableListOf() + val names = mutableListOf() var counter = 0 init { - timerStart("bot_spawn") { TimeUnit.SECONDS.toTicks(Settings["bots.spawnSeconds", 60]) } + worldTimerStart("bot_spawn") { TimeUnit.SECONDS.toTicks(Settings["bots.spawnSeconds", 60]) } - timerTick("bot_spawn") { + worldTimerTick("bot_spawn") { if (counter > Settings["bots.count", 0]) { - return@timerTick Timer.CANCEL + return@worldTimerTick Timer.CANCEL } spawn() - return@timerTick Timer.CONTINUE + return@worldTimerTick Timer.CONTINUE } - worldSpawn { - if (Settings["bots.count", 0] > 0) { - World.timers.start("bot_spawn") + playerDespawn { + if (isBot) { + manager.remove(bot) } } + worldSpawn { + loadSettings() + } + settingsReload { - if (Settings["bots.count", 0] > bots.size) { - World.timers.start("bot_spawn") - } + loadSettings() } adminCommand("bots", intArg("count", optional = true), desc = "Spawn (count) number of bots", handler = ::spawn) adminCommand("clear_bots", intArg("count", optional = true), desc = "Clear all or some amount of bots", handler = ::clear) - adminCommand("bot", stringArg("task", optional = true, autofill = tasks.names), desc = "Toggle yourself on/off as a bot player", handler = ::toggle) + adminCommand("bot", stringArg("task", optional = true, autofill = manager.activityNames), desc = "Toggle yourself on/off as a bot player", handler = ::toggle) + adminCommand("bot_info", stringArg("name", optional = true, desc = "Filter by bot name", autofill = accountDefinitions.displayNames.keys), desc = "Print bot info", handler = ::info) + } + + private fun loadSettings() { + if (Settings["bots.count", 0] > 0) { + World.timers.start("bot_spawn") + } + if (!Settings["bots.numberedNames", false]) { + names.clear() + names.addAll(File(Settings["bots.names"]).readLines()) + } } fun spawn(player: Player, args: List) { @@ -87,46 +107,92 @@ class BotSpawns( } fun clear(player: Player, args: List) { - val count = args[0].toIntOrNull() ?: MAX_PLAYERS - World.queue("bot_$counter") { - val manager = get() + val count = args.getOrNull(0)?.toIntOrNull() ?: MAX_PLAYERS + World.queue("bot_clear") { runBlocking { - for (bot in bots.take(count)) { - manager.logout(bot, false) + var removed = 0 + for (bot in manager.bots) { + if (bot.player.client != null && bot.player.client !is DummyClient) { + continue + } + manager.remove(bot) + Script.launch { + accounts.logout(bot.player, true) + } + if (++removed >= count) { + break + } } } } } + fun info(player: Player, args: List) { + if (player.isBot) { + player.message("Available activities:", ChatType.Console) + for (activity in player.bot.available) { + player.message(" $activity", ChatType.Console) + } + } + val filter = args.getOrNull(0) + val info = mutableListOf() + for (bot in manager.bots) { + if (filter != null && !filter.equals(bot.player.name, ignoreCase = true)) { + continue + } + info.add(bot.player.name) + for (frame in bot.frames) { + info.add("${frame.behaviour.id} - ${frame.state}") + } + } + player.questJournal("Bots List", info.take(300)) + } + fun toggle(player: Player, args: List) { if (player.isBot) { + manager.remove(player.bot) player.clear("bot") player.message("Bot disabled.") } else { - player.initBot() - if (args[0].isNotBlank()) { - player["task_bot"] = args[0] + val bot = player.initBot() + manager.add(bot) + if (args.getOrNull(0)?.isNotBlank() == true) { + bot.available.clear() + val name = args[0] + bot.blocked.remove(name) + bot.available.add(name) + manager.assign(bot, name) } - Bots.start(player) player.message("Bot enabled.") } } fun spawn() { GlobalScope.launch(Contexts.Game) { - val name = "Bot ${++counter}" - val bot = Player(tile = Areas["lumbridge_teleport"].random(), accountName = name) - bot.initBot() - loader.connect(bot, if (Settings["development.bots.live", false]) DummyClient() else null) - setAppearance(bot) - if (bot.inventory.isEmpty()) { - bot.inventory.add("coins", 10000) + counter++ + val name = if (Settings["bots.numberedNames", false]) { + "Bot $counter" + } else { + val prefix = Settings["bots.namePrefix", ""].trim('"') + val length = 12 - prefix.length + val short = names.filter { it.length < length } + var selected = short.randomOrNull(random) + if (selected == null) { + selected = names.removeAt(random.nextInt(names.size)) + } else { + names.remove(selected) + } + "${prefix}$selected" } - Bots.start(bot) - bot.viewport?.loaded = true + val player = Player(tile = Areas["lumbridge_teleport"].random(), accountName = name) + val bot = player.initBot() + loader.connect(player, DummyClient(), viewport = Settings["development.bots.live", false]) + setAppearance(player) + player.inventory.add("coins", 10000) + player.viewport?.loaded = true delay(3) - bots.add(bot) - bot.running = true + manager.add(bot) + player.running = true } } diff --git a/game/src/main/kotlin/content/bot/BotManager.kt b/game/src/main/kotlin/content/bot/BotManager.kt new file mode 100644 index 0000000000..ed71372a17 --- /dev/null +++ b/game/src/main/kotlin/content/bot/BotManager.kt @@ -0,0 +1,333 @@ +package content.bot + +import com.github.michaelbull.logging.InlineLogger +import content.bot.behaviour.Behaviour +import content.bot.behaviour.BehaviourFrame +import content.bot.behaviour.BehaviourState +import content.bot.behaviour.Condition +import content.bot.behaviour.HardReason +import content.bot.behaviour.Reason +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.activity.ActivitySlots +import content.bot.behaviour.activity.BotActivity +import content.bot.behaviour.loadBehaviours +import content.bot.behaviour.setup.DynamicResolvers +import content.bot.behaviour.setup.Resolver +import world.gregs.voidps.engine.data.ConfigFiles +import world.gregs.voidps.engine.event.AuditLog +import world.gregs.voidps.engine.timer.toTicks +import world.gregs.voidps.type.random +import java.util.concurrent.TimeUnit + +/** + * Manages [Bot] behaviour execution and activity scheduling + * - Assigns [activities] to idle bots when possible. + * - Advances the current [BehaviourFrame]. + * - Resolves unmet [BotActivity.setup] requirements. + * - Handles activity completion, failure, and slot allocation. + */ +class BotManager( + private val activities: MutableMap = mutableMapOf(), + val resolvers: MutableMap> = mutableMapOf(), + private val groups: MutableMap> = mutableMapOf(), +) : Runnable { + internal val slots = ActivitySlots() + val bots = mutableListOf() + private val logger = InlineLogger("BotManager") + + val activityNames: Set + get() = activities.keys + + fun load(files: ConfigFiles): BotManager { + loadBehaviours(files, activities, groups, resolvers) + return this + } + + fun add(bot: Bot) { + bots.add(bot) + // Update all available activities + bot.available.clear() + for (activity in activities.values) { + if (activity.requires.any { !it.check(bot.player) }) { + continue + } + bot.available.add(activity.id) + } + } + + fun remove(bot: Bot): Boolean { + if (bots.remove(bot)) { + stop(bot) + return true + } + return false + } + + override fun run() { + for (bot in bots) { + tick(bot) + } + } + + fun tick(bot: Bot) { + try { + if (bot.noTask()) { + assignRandom(bot) + return + } + execute(bot) + } catch (exception: Exception) { + logger.error(exception) { "Error in bot '${bot.player.accountName}' tick ${bot.frames.map { it.behaviour.id }}." } + } + } + + /** + * Assign activity [id] to [bot] + * Useful for debugging + */ + fun assign(bot: Bot, id: String): Boolean { + val activity = activities[id] ?: return false + assign(bot, activity) + return true + } + + /** + * Assign a random activity that is available to the [bot]. + */ + private fun assignRandom(bot: Bot) { + if (bot.evaluate.isNotEmpty()) { + updateAvailable(bot) + } + if (bot.player["debug", false]) { + logger.trace { "Picking new activity from ${bot.available} for bot ${bot.player.accountName}." } + } + var activity = if (hasRequirements(bot, bot.previous)) { + bot.previous!! + } else { + bot.available + .filter { hasRequirements(bot, activities[it]) } + .randomOrNull(random) + ?.let { activities[it] } + } + if (activity == null) { + if (bot.player["debug", false]) { + logger.debug { "No activities with requirements met for bot: ${bot.player.accountName}." } + debugActivities(bot) + } + activity = idle + } + assign(bot, activity) + } + + /** + * Remove invalid activities, check for new valid activities based on recent state changes ready to [Bot.evaluate]. + */ + private fun updateAvailable(bot: Bot) { + // Remove activities which are no longer available + val iterator = bot.available.iterator() + while (iterator.hasNext()) { + val id = iterator.next() + val activity = activities[id] ?: continue + if (activity.requires.any { !it.check(bot.player) }) { + iterator.remove() + } + } + // Add activities which have become available + for (group in bot.evaluate) { + for (id in groups[group] ?: return) { + val activity = activities[id] ?: continue + if (activity.requires.any { !it.check(bot.player) }) { + continue + } + bot.available.add(activity.id) + } + } + bot.evaluate.clear() + } + + private val idle = BotActivity("idle", 2048, timeout = TimeUnit.HOURS.toTicks(1), actions = listOf(BotAction.Wait(TimeUnit.SECONDS.toTicks(30)))) + + private fun hasRequirements(bot: Bot, activity: BotActivity?): Boolean = activity != null && slots.hasFree(activity) && !bot.blocked.contains(activity.id) && activity.requires.all { it.check(bot.player) } + + private fun assign(bot: Bot, activity: BotActivity) { + AuditLog.event(bot, "assigned", activity.id) + if (bot.player["debug", false]) { + logger.info { "Assigned task '${activity.id}' to bot '${bot.player.accountName}'." } + } + slots.occupy(activity) + bot.previous = activity + bot.queue(BehaviourFrame(activity)) + } + + /** + * Check and update [bot]'s current activity and action state + */ + private fun execute(bot: Bot) { + val frame = bot.frame() + when (val state = frame.state) { + BehaviourState.Running -> frame.update(bot) + BehaviourState.Pending -> start(bot, frame) + BehaviourState.Success -> nextAction(bot, frame) + is BehaviourState.Failed -> handleFail(bot, frame, state) + is BehaviourState.Wait -> { + val behaviour = frame.behaviour + if (bot.player["debug", false]) { + logger.trace { "Bot wait: ${behaviour.id} state: ${frame.state} action: ${frame.action()}." } + bot["previous_state"] = frame.state + } + if (--state.ticks <= 0) { + frame.state = state.next + } + } + } + } + + /** + * Check [bot] has requirements to start the current activity + * Find [resolvers] if activity has set up [Condition]s + */ + private fun start(bot: Bot, frame: BehaviourFrame) { + val behaviour = frame.behaviour + for (requirement in behaviour.requires) { + if (requirement.check(bot.player)) { + continue + } + frame.fail(Reason.Requirement(requirement)) + return + } + val debug = bot.player["debug", false] + for (requirement in behaviour.setup) { + if (requirement.check(bot.player)) { + continue + } + frame.blocked.removeAll(DynamicResolvers.ids()) + val resolvers = buildList { + DynamicResolvers.resolver(bot.player, requirement)?.also { add(it) } + requirement.keys() + .flatMap { resolvers[it].orEmpty() } + .forEach(::add) + } + val resolver = resolvers + .filter { !frame.blocked.contains(it.id) && it.requires.none { fact -> !fact.check(bot.player) } } + .minByOrNull { it.weight } + if (resolver == null) { + if (debug) { + debugResolvers(behaviour, requirement, resolvers, frame, bot) + } + frame.fail(Reason.Requirement(requirement)) // No way to resolve + return + } + // Attempt resolution + AuditLog.event(bot, "start_resolver", resolver.id, behaviour.id) + if (debug) { + logger.info { "Starting ${resolver.id} for ${behaviour.id} requirement: $requirement." } + } + frame.blocked.add(resolver.id) + val resolverFrame = BehaviourFrame(resolver) + bot.queue(resolverFrame) + return + } + AuditLog.event(bot, "start_activity", behaviour.id) + if (debug) { + logger.info { "Starting activity: ${behaviour.id}." } + } + bot.blocked.add(behaviour.id) + frame.start(bot) + } + + /** + * Move onto the next action in a behaviour or remove the behaviour from the stack if it is completed + */ + private fun nextAction(bot: Bot, frame: BehaviourFrame) { + val debug = bot.player["debug", false] + if (frame.next()) { + if (debug) { + logger.debug { "Next action: ${frame.action()} for ${frame.behaviour.id}." } + } + frame.start(bot) + return + } + val behaviour = frame.behaviour + if (debug) { + logger.debug { "Completed action: ${frame.action()} for ${behaviour.id}." } + } + AuditLog.event(bot, "completed", frame.behaviour.id) + bot.frames.pop() + if (!bot.noTask()) { + bot.frame().blocked.remove(behaviour.id) + } + if (behaviour is BotActivity) { + bot.blocked.remove(behaviour.id) + slots.release(behaviour) + } + } + + /** + * Stop the current activity or remove the current resolver + */ + private fun handleFail(bot: Bot, frame: BehaviourFrame, state: BehaviourState.Failed) { + val behaviour = frame.behaviour + val action = frame.action() + if (bot.player["debug", false]) { + logger.warn { "Failed ${behaviour.id} action=${action::class.simpleName}, reason=${state.reason}." } + } + AuditLog.event(bot, "failed", behaviour.id, state.reason, frame.index, action::class.simpleName) + if (state.reason is HardReason) { + stop(bot) + } else { + bot.frames.pop() + } + if (behaviour is BotActivity) { + bot.blocked.add(behaviour.id) + slots.release(behaviour) + } + } + + /** + * Remove all behaviours and free up activity slots + */ + private fun stop(bot: Bot) { + for (frame in bot.frames) { + if (frame.behaviour is BotActivity) { + slots.release(frame.behaviour) + } + } + bot.reset() + } + + private fun debugResolvers(behaviour: Behaviour, requirement: Condition, resolvers: List, frame: BehaviourFrame, bot: Bot) { + logger.warn { "No resolver found for keys=${requirement.keys()} id=${behaviour.id}, requirement=$requirement." } + for (resolver in resolvers) { + if (frame.blocked.contains(resolver.id)) { + logger.debug { "Resolver ${resolver.id} - Blocked by frame behaviour: ${frame.behaviour.id}." } + break + } + for (requirement in resolver.requires) { + if (!requirement.check(bot.player)) { + logger.debug { "Resolver ${resolver.id} - Failed requirement: $requirement." } + return + } + } + logger.debug { "Resolver ${resolver.id} - Available." } + } + } + + private fun debugActivities(bot: Bot) { + logger.info { "Failed to find activity for bot ${bot.player.accountName}." } + for (id in bot.available) { + val activity = activities[id] ?: continue + if (!slots.hasFree(activity)) { + logger.trace { "Activity: $id - No available slots." } + } else if (bot.blocked.contains(activity.id)) { + logger.trace { "Activity: $id - Blocked." } + } else { + for (requirement in activity.requires) { + if (!requirement.check(bot.player)) { + logger.trace { "Activity: $id - Failed requirement: $requirement" } + break + } + } + } + } + } +} diff --git a/game/src/main/kotlin/content/bot/BotUpdates.kt b/game/src/main/kotlin/content/bot/BotUpdates.kt new file mode 100644 index 0000000000..c079f92e90 --- /dev/null +++ b/game/src/main/kotlin/content/bot/BotUpdates.kt @@ -0,0 +1,112 @@ +package content.bot + +import com.github.michaelbull.logging.InlineLogger +import content.bot.behaviour.navigation.NavigationGraph +import content.bot.behaviour.setup.DynamicResolvers +import world.gregs.voidps.cache.config.data.InventoryDefinition +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.data.definition.InventoryDefinitions +import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.network.client.instruction.InteractDialogue + +/** + * Listen for state changes which would change which activities are available to a bot + */ +class BotUpdates( + val inventoryDefinitions: InventoryDefinitions, + val graph: NavigationGraph, +) : Script { + val logger = InlineLogger() + + init { + /* + Track state changes to re-evaluate available activities + */ + levelChanged { skill, _, _ -> + if (isBot) { + bot.evaluate.add("skill:${skill.name.lowercase()}") + } + } + + inventoryUpdated { inventory, _ -> + if (isBot) { + bot.evaluate.add("inv:$inventory") + } + } + + entered("*") { + if (isBot) { + resetTimeout("area:${it.name}") + bot.evaluate.add("area:${it.name}") + } + } + + variableSet { key, from, to -> + if (isBot && from != to) { + bot.evaluate.add("var:$key") + resetTimeout("variable:$key") + } + } + + itemAdded(inventory = "inventory") { + resetTimeout("item:${it.item.id}") + } + + experience { skill, _, _ -> + resetTimeout("skill:${skill.name.lowercase()}") + } + + // Close level-up dialogues + interfaceOpened("dialogue_level_up") { + if (isBot) { + instructions.trySend(InteractDialogue(interfaceId = 740, componentId = 3, option = -1)) + } + } + // Register shops + + worldSpawn { + DynamicResolvers.shopItems.clear() + } + + npcSpawn { + if (def.contains("shop")) { + val shop = def.get("shop") + val def = inventoryDefinitions.get(shop) + registerShop(this, def, DynamicResolvers.shopItems) + val sample = inventoryDefinitions.getOrNull("${shop}_sample") ?: return@npcSpawn + registerShop(this, sample, DynamicResolvers.sampleItems) + } + } + } + + /** + * Reset timeout when produce has been produced + */ + private fun Player.resetTimeout(key: String) { + if (!isBot || bot.noTask()) { + return + } + val frame = bot.frame() + val produces = frame.behaviour.produces + if (produces.contains(key)) { + frame.timeout = 0 + } + } + + private fun registerShop(npc: NPC, definition: InventoryDefinition, map: MutableMap>>) { + val area: String = npc.def.getOrNull("area") ?: return + val ids = definition.ids ?: return + val amounts = definition.amounts ?: return + for (index in ids.indices) { + val id = ids[index] + val amount = amounts[index] + if (amount <= 0) { + continue + } + val def = ItemDefinitions.getOrNull(id) ?: continue + map.getOrPut(def.stringId) { mutableListOf() }.add(area to npc.id) + } + } +} diff --git a/game/src/main/kotlin/content/bot/Bots.kt b/game/src/main/kotlin/content/bot/Bots.kt deleted file mode 100644 index b3cabfc4f1..0000000000 --- a/game/src/main/kotlin/content/bot/Bots.kt +++ /dev/null @@ -1,133 +0,0 @@ -package content.bot - -import content.bot.interact.bank.closeBank -import content.bot.interact.bank.depositAll -import content.bot.interact.bank.openBank -import content.bot.interact.bank.withdrawCoins -import content.bot.interact.navigation.await -import content.bot.interact.shop.buy -import content.bot.interact.shop.closeShop -import content.bot.interact.shop.openNearestShop -import content.entity.player.bank.bank -import world.gregs.voidps.engine.client.ui.dialogue -import world.gregs.voidps.engine.data.definition.InterfaceDefinitions -import world.gregs.voidps.engine.data.definition.ItemDefinitions -import world.gregs.voidps.engine.entity.character.npc.NPC -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.item.slot -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.entity.obj.GameObjects -import world.gregs.voidps.engine.get -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.map.spiral -import world.gregs.voidps.network.client.instruction.InteractDialogue -import world.gregs.voidps.network.client.instruction.InteractInterface -import world.gregs.voidps.network.client.instruction.InteractNPC -import world.gregs.voidps.network.client.instruction.InteractObject -import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot - -object Bots : AutoCloseable { - fun start(bot: Player) { - start?.invoke(bot) - } - - var start: ((Player) -> Unit)? = null - - override fun close() { - start = null - } -} - -val Player.isBot: Boolean - get() = contains("bot") - -val Player.bot: Bot - get() = get("bot")!! - -fun Bot.hasCoins(amount: Int, bank: Boolean = true): Boolean { - if (player.inventory.contains("coins") && player.inventory.count("coins") >= amount) { - return true - } - if (bank && player.bank.contains("coins") && player.bank.count("coins") >= amount) { - return true - } - return false -} - -suspend fun Bot.buyItem(item: String, amount: Int = 1): Boolean { - if (player.inventory.isFull()) { - openBank() - depositAll() - closeBank() - } - withdrawCoins() - val success = try { - openNearestShop(item) - } catch (e: Exception) { - false - } - if (success) { - buy(item, amount) - closeShop() - } - return success -} - -fun Bot.equip(item: String) { - val def = ItemDefinitions.getOrNull(item) ?: return - if (def.slot == EquipSlot.None) { - return - } - val index = player.inventory.indexOf(item) - if (index != -1) { - player.instructions.trySend(InteractInterface(interfaceId = 149, componentId = 0, itemId = def.id, itemSlot = index, option = 1)) - } -} - -fun Bot.inventoryOption(item: String, option: String) { - val index = player.inventory.indexOf(item) - if (index != -1) { - val def = ItemDefinitions.getOrNull(item) ?: return - player.instructions.trySend(InteractInterface(interfaceId = 149, componentId = 0, itemId = def.id, itemSlot = index, option = def.options.indexOf(option))) - } -} - -suspend fun Bot.npcOption(npc: NPC, option: String) { - player.instructions.send(InteractNPC(npc.index, npc.def.options.indexOf(option) + 1)) -} - -suspend fun Bot.objectOption(obj: GameObject, option: String) { - player.instructions.send(InteractObject(obj.def.id, obj.tile.x, obj.tile.y, obj.def.optionsIndex(option) + 1)) -} - -suspend fun Bot.dialogueOption(option: String) { - val current = player.dialogue!! - val definitions = get() - val def = definitions.get(current) - player.instructions.send(InteractDialogue(def.id, definitions.getComponentId(current, option)!!, -1)) - await("tick") -} - -fun Bot.getObject(filter: (GameObject) -> Boolean): GameObject? { - for (zone in player.tile.zone.spiral(2)) { - val obj = zone.toCuboid() - .flatMap { tile -> GameObjects.at(tile) } - .firstOrNull(filter) - if (obj != null) { - return obj - } - } - return null -} - -fun Bot.getObjects(filter: (GameObject) -> Boolean): List { - val list = mutableListOf() - for (zone in player.tile.zone.spiral(2)) { - list.addAll( - zone.toCuboid() - .flatMap { tile -> GameObjects.at(tile) } - .filter(filter), - ) - } - return list -} diff --git a/game/src/main/kotlin/content/bot/DecisionMaking.kt b/game/src/main/kotlin/content/bot/DecisionMaking.kt deleted file mode 100644 index 1fd75606ee..0000000000 --- a/game/src/main/kotlin/content/bot/DecisionMaking.kt +++ /dev/null @@ -1,81 +0,0 @@ -package content.bot - -import com.github.michaelbull.logging.InlineLogger -import content.bot.interact.navigation.resume -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import world.gregs.voidps.engine.Contexts -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.Players -import kotlin.coroutines.resume - -class DecisionMaking( - val tasks: TaskManager, -) : Script { - - val scope = CoroutineScope(Contexts.Game) - val logger = InlineLogger("Bot") - - init { - Bots.start = start@{ bot -> - if (!bot.contains("task_bot") || bot.contains("task_started")) { - return@start - } - val name: String = bot["task_bot"]!! - val task = tasks.get(name) - if (task == null) { - bot.clear("task_bot") - } else { - assign(bot, task) - } - } - - AiTick.method = { - for (player in Players) { - if (!player.isBot) { - continue - } - val bot: Bot = player["bot"]!! - if (!bot.contains("task_bot")) { - val lastTask: String? = bot["last_task_bot"] - assign(player, tasks.assign(bot, lastTask)) - } - player.bot.resume("tick") - handleNewSuspensions(player) - } - } - } - - fun handleNewSuspensions(player: Player) { - val suspensions: MutableList Boolean, CancellableContinuation>> = player["bot_new_suspensions"] ?: return - suspensions.removeIf { - val success = it.first(player) - if (success) it.second.resume(Unit) - success - } - } - - fun assign(bot: Player, task: Task) { - if (bot["debug", false]) { - logger.debug { "Task assigned: ${bot.accountName} - ${task.name}" } - } - val last = bot.get("task_bot") - if (last != null) { - bot["last_task_bot"] = last - } - bot["task_bot"] = task.name - bot["task_started"] = true - task.spaces-- - scope.launch { - try { - task.block.invoke(bot) - } catch (t: Throwable) { - logger.warn(t) { "Task cancelled for $bot" } - } - bot.clear("task_bot") - task.spaces++ - } - } -} diff --git a/game/src/main/kotlin/content/bot/InterfaceBot.kt b/game/src/main/kotlin/content/bot/InterfaceBot.kt deleted file mode 100644 index b16e6318a7..0000000000 --- a/game/src/main/kotlin/content/bot/InterfaceBot.kt +++ /dev/null @@ -1,21 +0,0 @@ -package content.bot - -import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractInterface -import world.gregs.voidps.network.client.instruction.InteractInterfaceObject -import world.gregs.voidps.network.client.instruction.InterfaceClosedInstruction - -suspend fun Bot.closeInterface(id: Int, component: Int) { - player.instructions.send(InterfaceClosedInstruction) - clickInterface(id, component, 0) -} - -suspend fun Bot.clickInterface(id: Int, component: Int, option: Int = 0, itemId: Int = -1, itemSlot: Int = -1) { - player.instructions.send(InteractInterface(interfaceId = id, componentId = component, itemId = itemId, itemSlot = itemSlot, option = option)) -} - -suspend fun Bot.itemOnObject(item: Item, obj: GameObject, interfaceId: Int = 149, component: Int = 0) { - player.instructions.send(InteractInterfaceObject(obj.def.id, obj.tile.x, obj.tile.y, interfaceId, component, item.def.id, player.inventory.indexOf(item.id))) -} diff --git a/game/src/main/kotlin/content/bot/Task.kt b/game/src/main/kotlin/content/bot/Task.kt deleted file mode 100644 index 9414951848..0000000000 --- a/game/src/main/kotlin/content/bot/Task.kt +++ /dev/null @@ -1,17 +0,0 @@ -package content.bot - -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.type.Area -import world.gregs.voidps.type.Tile - -data class Task( - val name: String, - val block: suspend Player.() -> Unit, - val area: Area? = null, - var spaces: Int = 1, - val requirements: Collection Boolean> = emptySet(), -) { - fun full() = spaces <= 0 - - fun distanceTo(tile: Tile): Int = if (area == null) 0 else tile.distanceTo(area.random()) -} diff --git a/game/src/main/kotlin/content/bot/TaskManager.kt b/game/src/main/kotlin/content/bot/TaskManager.kt deleted file mode 100644 index 98992e66b5..0000000000 --- a/game/src/main/kotlin/content/bot/TaskManager.kt +++ /dev/null @@ -1,34 +0,0 @@ -package content.bot - -import content.bot.interact.navigation.await -import world.gregs.voidps.type.random -import java.util.* - -class TaskManager { - val names = mutableSetOf() - private val queue = LinkedList() - private var idle = Task( - name = "do nothing", - block = { - repeat(random.nextInt(10, 100)) { - bot.await("tick") - } - }, - spaces = Int.MAX_VALUE, - ) - - fun register(task: Task) { - names.add(task.name) - queue.add(task) - } - - fun get(name: String): Task? = queue.firstOrNull { it.name == name } - - fun assign(bot: Bot, last: String?): Task = queue - .filter { it.name != last && !it.full() && it.requirements.all { req -> req(bot.player) } } - .minByOrNull { it.distanceTo(bot.tile) } ?: idle - - fun idle(task: Task) { - idle = task - } -} diff --git a/game/src/main/kotlin/content/bot/WalkingBot.kt b/game/src/main/kotlin/content/bot/WalkingBot.kt deleted file mode 100644 index 58e7db9ce1..0000000000 --- a/game/src/main/kotlin/content/bot/WalkingBot.kt +++ /dev/null @@ -1,30 +0,0 @@ -package content.bot - -import content.bot.interact.navigation.await -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.data.Settings -import world.gregs.voidps.network.client.instruction.Walk - -class WalkingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - val task = Task( - name = "walk randomly", - block = { - while (true) { - val tile = tile.toCuboid(10).random() - instructions.send(Walk(tile.x, tile.y)) - bot.await("tick") - } - }, - area = null, - spaces = Int.MAX_VALUE, - requirements = emptyList(), - ) - if (Settings["bots.idle", "nothing"] == "randomWalk") { - tasks.idle(task) - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/behaviour/Behaviour.kt b/game/src/main/kotlin/content/bot/behaviour/Behaviour.kt new file mode 100644 index 0000000000..e8e28c73eb --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/Behaviour.kt @@ -0,0 +1,373 @@ +package content.bot.behaviour + +import content.bot.behaviour.action.ActionParser +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.activity.BotActivity +import content.bot.behaviour.navigation.NavigationGraph +import content.bot.behaviour.navigation.NavigationShortcut +import content.bot.behaviour.setup.Resolver +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import world.gregs.config.Config +import world.gregs.config.ConfigReader +import world.gregs.voidps.engine.data.ConfigFiles +import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.timedLoad +import world.gregs.voidps.engine.timer.toTicks +import java.util.concurrent.TimeUnit + +interface Behaviour { + val id: String + val timeout: Int + val requires: List + val setup: List + val actions: List + val produces: Set +} + +fun loadBehaviours( + files: ConfigFiles, + activities: MutableMap, + groups: MutableMap>, + resolvers: MutableMap>, +) { + val templates = loadTemplates(files.list(Settings["bots.templates"])) + loadActivities(activities, templates, files.list(Settings["bots.definitions"])) + // Group activities by requirement types + var total = 0 + for (activity in activities.values) { + for (req in activity.requires) { + for (key in req.events()) { + groups.getOrPut(key) { mutableListOf() }.add(activity.id) + } + } + total += activity.capacity + } + loadSetups(resolvers, templates, files.list(Settings["bots.setups"])) +} + +fun loadGraph(files: ConfigFiles): NavigationGraph { + val templates = loadTemplates(files.list(Settings["bots.templates"])) + val shortcuts = mutableListOf() + loadShortcuts(shortcuts, templates, files.list(Settings["bots.shortcuts"])) + return NavigationGraph.loadGraph(files.list(Settings["bots.nav.definitions"]), shortcuts) +} + +private fun loadActivities(activities: MutableMap, templates: Map, paths: List) { + timedLoad("bot activity") { + val fragments = mutableListOf() + load(paths) { id, template, fields, capacity, timeout, requires, setup, actions, produces -> + if (template != null) { + requireNotNull(fields) + fragments.add(Fragment(id, template, fields, capacity, timeout, requires, setup, actions, produces)) + } else { + val debug = "$id ${exception()}" + activities[id] = BotActivity( + id = id, + capacity = capacity, + timeout = timeout, + requires = Condition.parse(requires, debug), + setup = Condition.parse(setup, debug), + actions = ActionParser.parse(actions, debug), + produces = produces, + ) + } + } + for (fragment in fragments) { + val template = templates[fragment.template] ?: error("Unable to find template '${fragment.template}' for ${fragment.id}.") + activities[fragment.id] = fragment.activity(template) + } + activities.size + } +} + +private fun loadSetups(resolvers: MutableMap>, templates: Map, paths: List) { + timedLoad("bot setup") { + val fragments = mutableListOf() + load(paths) { id, template, fields, weight, timeout, requires, setup, actions, produces -> + if (template != null) { + requireNotNull(fields) + fragments.add(Fragment(id, template, fields, weight, timeout, requires, setup, actions, produces)) + } else { + val debug = "$id ${exception()}" + val resolver = Resolver( + id = id, + weight = weight, + timeout = timeout, + requires = Condition.parse(requires, debug), + setup = Condition.parse(setup, debug), + actions = ActionParser.parse(actions, debug), + produces = produces, + ) + for (key in produces) { + resolvers.getOrPut(key) { mutableListOf() }.add(resolver) + } + } + } + for (fragment in fragments) { + val template = templates[fragment.template] ?: error("Unable to find template '${fragment.template}' for ${fragment.id}.") + val resolver = fragment.resolver(template) + for (key in resolver.produces) { + resolvers.getOrPut(key) { mutableListOf() }.add(resolver) + } + } + resolvers.size + } +} + +private fun loadShortcuts(shortcuts: MutableList, templates: Map, paths: List) { + timedLoad("bot shortcut") { + val fragments = mutableListOf() + load(paths) { id, template, fields, weight, timeout, requires, setup, actions, produces -> + if (template != null) { + requireNotNull(fields) { "No fields found for $id ${exception()}" } + fragments.add(Fragment(id, template, fields, weight, timeout, requires, setup, actions, produces)) + } else { + val debug = "$id ${exception()}" + shortcuts.add( + NavigationShortcut( + id = id, + weight = weight, + timeout = timeout, + requires = Condition.parse(requires, debug), + setup = Condition.parse(setup, debug), + actions = ActionParser.parse(actions, debug), + produces = produces, + ), + ) + } + } + for (fragment in fragments) { + val template = templates[fragment.template] ?: error("Unable to find template '${fragment.template}' for ${fragment.id}.") + shortcuts.add(fragment.shortcut(template)) + } + shortcuts.size + } +} + +private fun load(paths: List, block: ConfigReader.(String, String?, Map?, Int, Int, List>>>, List>>>, List>>, Set) -> Unit) { + for (path in paths) { + Config.fileReader(path) { + while (nextSection()) { + val id = section() + var template: String? = null + var fields: Map? = null + var value = 1 + var timeout = TimeUnit.MINUTES.toTicks(1) + val requires = mutableListOf>>>() + val setup = mutableListOf>>>() + val actions = mutableListOf>>() + val produces = mutableSetOf() + while (nextPair()) { + when (val key = key()) { + "template" -> template = string() + "requires" -> requirements(requires) + "setup" -> requirements(setup) + "actions" -> actions(actions) + "produces" -> produces(produces) + "weight", "capacity" -> value = int() + "timeout" -> timeout = int() + "fields" -> fields = map() + else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + } + } + if (fields != null && template == null) { + error("Found fields but no template for $id in ${exception()}") + } + block.invoke(this, id, template, fields, value, timeout, requires, setup, actions, produces) + } + } + } +} + +private fun loadTemplates(paths: List): Map { + val templates = mutableMapOf() + timedLoad("bot template") { + for (path in paths) { + Config.fileReader(path) { + while (nextSection()) { + val id = section() + val requires = mutableListOf>>>() + val setup = mutableListOf>>>() + val actions = mutableListOf>>() + val produces = mutableSetOf() + while (nextPair()) { + when (val key = key()) { + "requires" -> requirements(requires) + "setup" -> requirements(setup) + "actions" -> actions(actions) + "produces" -> produces(produces) + else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + } + } + templates[id] = Template(requires, setup, actions, produces) + } + } + } + templates.size + } + return templates +} + +internal fun ConfigReader.requirements(requires: MutableList>>>) { + while (nextElement()) { + while (nextEntry()) { + val key = key() + if (peek == '[') { + val list = ObjectArrayList>() + while (nextElement()) { + list.add(map()) + } + requires.add(key to list) + } else { + requires.add(key to listOf(map())) + } + } + } +} + +internal fun ConfigReader.produces(requires: MutableSet) { + while (nextElement()) { + while (nextEntry()) { + val key = key() + val value = string() + requires.add("$key:$value") + } + } +} + +internal fun ConfigReader.actions(list: MutableList>>) { + while (nextElement()) { + while (nextEntry()) { + val type = key() + val map = map() + list.add(type to map) + } + } +} + +private data class Fragment( + val id: String, + val template: String, + val fields: Map, + val int: Int, + val timeout: Int, + val requires: List>>>, + val setup: List>>>, + val actions: List>>, + val produces: Set, +) { + fun activity(template: Template) = BotActivity( + id = id, + capacity = int, + timeout = timeout, + requires = resolveRequirements(template.requires, requires), + setup = resolveRequirements(template.setup, setup), + actions = resolveActions(template.actions, actions), + produces = resolve(template.produces) + produces, + ) + + private fun resolve(set: Set) = set.map { value -> + if (value.contains('$')) { + val ref = value.reference() + val name = ref.trim('$', '{', '}') + val replacement = fields[name] as? String ?: error("No field found for behaviour=$id ref=$ref") + value.replace(ref, replacement) + } else { + value + } + }.toSet() + + private fun resolveRequirements(templated: List>>>, original: List>>>): List { + val combinedList = mutableListOf>>>() + combinedList.addAll(original) + for ((type, list) in templated) { + val resolved = list.map { map -> resolve(map, type) } + if (resolved.isNotEmpty()) { + combinedList.add(type to resolved) + } + } + if (combinedList.isEmpty()) { + return emptyList() + } + return Condition.parse(combinedList, "$id template $template") + } + + private fun resolveActions(templated: List>>, original: List>>): List { + val combinedList = mutableListOf>>() + combinedList.addAll(original) + for ((type, map) in templated) { + val resolved = resolve(map, type) + if (resolved.isNotEmpty()) { + combinedList.add(type to resolved) + } + } + if (combinedList.isEmpty()) { + return emptyList() + } + return ActionParser.parse(combinedList, "$id template $template") + } + + @Suppress("UNCHECKED_CAST") + private fun resolve(map: Map, type: String): Map = map.mapValues { (key, value) -> + if (value is String && value.contains('$')) { + val ref = value.reference() + val name = ref.trim('$', '{', '}') + val replacement = fields[name] ?: error("No field found for behaviour=$id type=$type key=$key ref=$ref") + if (replacement is String) value.replace(ref, replacement) else replacement + } else if (value is Map<*, *>) { + resolve(value as Map, type) + } else if (value is List<*>) { + resolve(value as List, type) + } else { + value + } + }.toMap() + + @Suppress("UNCHECKED_CAST") + private fun resolve(value: List, type: String): List = value.map { element -> + when (element) { + is Map<*, *> -> resolve(element as Map, type) + is List<*> -> resolve(element as List, type) + else -> element + } + } + + private fun String.reference(): String { + if (startsWith('$')) { + return this + } + val index = indexOf($$"${") + if (index == -1) { + return "\$${substringAfter('$')}" + } + val end = indexOf('}', index) + 1 + return substring(index, end) + } + + fun resolver(template: Template) = Resolver( + id = id, + weight = int, + timeout = timeout, + requires = resolveRequirements(template.requires, requires), + setup = resolveRequirements(template.setup, setup), + actions = resolveActions(template.actions, actions), + produces = resolve(template.produces) + produces, + ) + + fun shortcut(template: Template) = NavigationShortcut( + id = id, + weight = int, + timeout = timeout, + requires = resolveRequirements(template.requires, requires), + setup = resolveRequirements(template.setup, setup), + actions = resolveActions(template.actions, actions), + produces = resolve(template.produces) + produces, + ) +} + +private data class Template( + val requires: List>>>, + val setup: List>>>, + val actions: List>>, + val produces: Set, +) diff --git a/game/src/main/kotlin/content/bot/behaviour/BehaviourFrame.kt b/game/src/main/kotlin/content/bot/behaviour/BehaviourFrame.kt new file mode 100644 index 0000000000..979ff0984f --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/BehaviourFrame.kt @@ -0,0 +1,55 @@ +package content.bot.behaviour + +import content.bot.Bot +import content.bot.behaviour.action.BotAction + +/** + * Tracks an ongoing [behaviour], it's [state] which action [index] is in progress + * [timeout] and any actions which are [blocked] from being retried. + */ +data class BehaviourFrame( + val behaviour: Behaviour, + var state: BehaviourState = BehaviourState.Pending, + var index: Int = 0, + var blocked: MutableSet = mutableSetOf(), + var timeout: Int = 0, +) { + + fun action(): BotAction = behaviour.actions[index] + + fun completed() = index >= behaviour.actions.size + + fun start(bot: Bot) { + val action = action() + state = action.start(bot, this) + } + + fun update(bot: Bot) { + if (++timeout > behaviour.timeout) { + fail(Reason.Timeout) + return + } + val action = action() + state = action.update(bot, this) ?: return + } + + fun next(): Boolean { + if (index >= behaviour.actions.lastIndex) { + return false + } + index++ + state = BehaviourState.Running + return true + } + + fun fail(reason: Reason) { + state = BehaviourState.Failed(reason) + } + + fun success() { + if (state is BehaviourState.Failed) { + return + } + state = BehaviourState.Success + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/BehaviourState.kt b/game/src/main/kotlin/content/bot/behaviour/BehaviourState.kt new file mode 100644 index 0000000000..65c17ba243 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/BehaviourState.kt @@ -0,0 +1,19 @@ +package content.bot.behaviour + +sealed interface BehaviourState { + object Pending : BehaviourState { + override fun toString() = "Pending" + } + object Running : BehaviourState { + override fun toString() = "Running" + } + object Success : BehaviourState { + override fun toString() = "Success" + } + data class Failed(val reason: Reason) : BehaviourState { + override fun toString() = "Failed($reason)" + } + data class Wait(var ticks: Int, val next: BehaviourState) : BehaviourState { + override fun toString() = "Wait($ticks, $next)" + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/Condition.kt b/game/src/main/kotlin/content/bot/behaviour/Condition.kt new file mode 100644 index 0000000000..b4d758d24d --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/Condition.kt @@ -0,0 +1,489 @@ +package content.bot.behaviour + +import content.entity.player.bank.bank +import net.pearx.kasechange.toPascalCase +import world.gregs.voidps.engine.GameLoop +import world.gregs.voidps.engine.client.variable.hasClock +import world.gregs.voidps.engine.client.variable.remaining +import world.gregs.voidps.engine.data.definition.Areas +import world.gregs.voidps.engine.entity.character.mode.EmptyMode +import world.gregs.voidps.engine.entity.character.mode.Face +import world.gregs.voidps.engine.entity.character.mode.Follow +import world.gregs.voidps.engine.entity.character.mode.Patrol +import world.gregs.voidps.engine.entity.character.mode.PauseMode +import world.gregs.voidps.engine.entity.character.mode.Rest +import world.gregs.voidps.engine.entity.character.mode.Retreat +import world.gregs.voidps.engine.entity.character.mode.Wander +import world.gregs.voidps.engine.entity.character.mode.combat.CombatMovement +import world.gregs.voidps.engine.entity.character.mode.interact.Interact +import world.gregs.voidps.engine.entity.character.mode.interact.InteractOption +import world.gregs.voidps.engine.entity.character.mode.interact.InterfaceOnFloorItemInteract +import world.gregs.voidps.engine.entity.character.mode.interact.InterfaceOnNPCInteract +import world.gregs.voidps.engine.entity.character.mode.interact.InterfaceOnObjectInteract +import world.gregs.voidps.engine.entity.character.mode.interact.ItemOnFloorItemInteract +import world.gregs.voidps.engine.entity.character.mode.interact.ItemOnNPCInteract +import world.gregs.voidps.engine.entity.character.mode.interact.ItemOnObjectInteract +import world.gregs.voidps.engine.entity.character.mode.interact.ItemOnPlayerInteract +import world.gregs.voidps.engine.entity.character.mode.interact.NPCOnFloorItemInteract +import world.gregs.voidps.engine.entity.character.mode.interact.NPCOnNPCInteract +import world.gregs.voidps.engine.entity.character.mode.interact.NPCOnObjectInteract +import world.gregs.voidps.engine.entity.character.mode.interact.NPCOnPlayerInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnFloorItemInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnNPCInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnObjectInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnPlayerInteract +import world.gregs.voidps.engine.entity.character.mode.move.Movement +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.combatLevel +import world.gregs.voidps.engine.entity.character.player.equip.equipped +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirements +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirementsToUse +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.event.Wildcard +import world.gregs.voidps.engine.event.Wildcards +import world.gregs.voidps.engine.inv.equipment +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.timer.epochSeconds +import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot +import kotlin.collections.iterator + +/** + * Checks if the world state is as expected or not + * Sorted by [priority] (lowest first) to avoid resolvers being executed in a weird order + * E.g. going to an area before getting the equipment. + */ +sealed class Condition(val priority: Int) { + abstract fun keys(): Set + abstract fun events(): Set + abstract fun check(player: Player): Boolean + + data class InArea(val id: String) : Condition(1000) { + override fun keys() = setOf("area:$id") + override fun events() = setOf("area") + override fun check(player: Player) = player.tile in Areas[id] + } + + data class AtTile(val x: Int? = null, val y: Int? = null, val level: Int? = null) : Condition(1000) { + override fun keys() = setOf("tile") + override fun events() = setOf("tile") + override fun check(player: Player): Boolean { + if (x != null && player.tile.x != x) { + return false + } + if (y != null && player.tile.y != y) { + return false + } + if (level != null && player.tile.level != level) { + return false + } + return true + } + } + + data class Queue(val id: String) : Condition(1) { + override fun keys() = setOf("queue:$id") + override fun events() = setOf("queue") + override fun check(player: Player) = player.queue.contains(id) + } + + data class Timer(val id: String) : Condition(1) { + override fun keys() = setOf("timer:$id") + override fun events() = setOf("timer") + override fun check(player: Player) = player.timers.contains(id) || player.softTimers.contains(id) + } + + data class Clock(val id: String) : Condition(1) { + override fun keys() = setOf("clock:$id") + override fun events() = setOf("clock") + override fun check(player: Player) = player.hasClock(id) + } + + data class ClockRemaining(val id: String, val min: Int? = null, val max: Int? = null, val seconds: Boolean = false) : Condition(1) { + override fun keys() = setOf("clock:$id") + override fun events() = setOf("clock") + override fun check(player: Player): Boolean { + val remaining = player.remaining(id, if (seconds) epochSeconds() else GameLoop.tick) + return inRange(remaining, min, max) + } + } + + data class Entry(val ids: Set, val min: Int? = null, val max: Int? = null, val usable: Boolean = false, val equippable: Boolean = false) + + data class Inventory(val items: List) : Condition(100) { + override fun keys() = items.flatMap { entry -> entry.ids.map { "item:$it" } }.toSet() + override fun events() = setOf("inv:inventory") + override fun check(player: Player) = contains(player, player.inventory, items) + } + + data class Equipment(val items: Map) : Condition(90) { + override fun keys() = items.values.flatMap { entry -> entry.ids.map { "item:$it" } }.toSet() + override fun events() = setOf("inv:worn_equipment") + + override fun check(player: Player): Boolean { + for ((slot, entry) in items) { + val item = player.equipped(slot) + if (entry.ids.contains("empty")) { + if (item.isEmpty()) { + continue + } + return false + } + if (!entry.ids.contains(item.id)) { + return false + } + if (!inRange(item.amount, entry.min, entry.max)) { + return false + } + if (entry.usable && item.amount > 0 && !player.hasRequirementsToUse(item)) { + return false + } + } + return true + } + } + + data class Bank(val items: List) : Condition(80) { + override fun keys() = items.flatMap { entry -> entry.ids.map { "bank:$it" } }.toSet() + override fun events() = setOf("inv:bank") + override fun check(player: Player) = contains(player, player.bank, items) + } + + data class Owns(val id: String, val min: Int? = null, val max: Int? = null) : Condition(110) { + override fun keys() = setOf("item:$id") + override fun events() = setOf("inv:worn_equipment", "inv:inventory", "inv:bank") + override fun check(player: Player): Boolean { + val count = player.inventory.count(id) + player.bank.count(id) + player.equipment.count(id) + return inRange(count, min, max) + } + } + + data class Variable(val id: String, val equals: Any, val default: Any) : Condition(1) { + override fun keys() = setOf("var:$id") + override fun events() = setOf("var:$id") + override fun check(player: Player) = player.variables.get(id, default) == equals + } + + data class VariableIn(val id: String, val default: Int, val min: Int?, val max: Int?) : Condition(1) { + override fun keys() = setOf("var:$id") + override fun events() = setOf("var:$id") + override fun check(player: Player): Boolean { + val value = player.variables.get(id, default) + return inRange(value, min, max) + } + } + + data class ObjectExists(val id: String, val x: Int, val y: Int) : Condition(900) { + override fun keys() = setOf("obj:$id") + override fun events() = setOf("object") + override fun check(player: Player) = GameObjects.findOrNull(player.tile.copy(x, y), id) != null + } + + data class CombatLevel(val min: Int? = null, val max: Int? = null) : Condition(1) { + override fun keys() = setOf("skill:combat") + override fun events() = setOf("skill") + override fun check(player: Player) = inRange(player.combatLevel, min, max) + } + + data class InterfaceOpen(val id: String) : Condition(1) { + override fun keys() = setOf("iface:$id") + override fun events() = setOf("interface") + override fun check(player: Player) = player.interfaces.contains(id) + } + + data class SkillLevel(val skill: Skill, val min: Int? = null, val max: Int? = null) : Condition(1) { + override fun keys() = setOf("skill:${skill.name}") + override fun events() = setOf("skill:${skill.name}") + override fun check(player: Player) = inRange(player.levels.get(skill), min, max) + } + + data class HasMode(val id: String) : Condition(1) { + override fun keys() = setOf("mode:$id") + override fun events() = setOf("mode") + override fun check(player: Player) = when (id) { + "empty" -> player.mode == EmptyMode + "interact" -> player.mode is Interact + "interact_on" -> player.mode is InteractOption + "combat_movement" -> player.mode is CombatMovement + "interface_on_floor_item" -> player.mode is InterfaceOnFloorItemInteract + "interface_on_npc" -> player.mode is InterfaceOnNPCInteract + "interface_on_object" -> player.mode is InterfaceOnObjectInteract + "item_on_floor_item" -> player.mode is ItemOnFloorItemInteract + "item_on_npc" -> player.mode is ItemOnNPCInteract + "item_on_object" -> player.mode is ItemOnObjectInteract + "item_on_player" -> player.mode is ItemOnPlayerInteract + "npc_on_floor_item" -> player.mode is NPCOnFloorItemInteract + "npc_on_npc" -> player.mode is NPCOnNPCInteract + "npc_on_object" -> player.mode is NPCOnObjectInteract + "npc_on_player" -> player.mode is NPCOnPlayerInteract + "player_on_floor_item" -> player.mode is PlayerOnFloorItemInteract + "player_on_npc" -> player.mode is PlayerOnNPCInteract + "player_on_object" -> player.mode is PlayerOnObjectInteract + "player_on_player" -> player.mode is PlayerOnPlayerInteract + "movement" -> player.mode is Movement + "follow" -> player.mode is Follow + "face" -> player.mode is Face + "patrol" -> player.mode is Patrol + "pause" -> player.mode == PauseMode + "rest" -> player.mode is Rest + "retreat" -> player.mode is Retreat + "wander" -> player.mode is Wander + else -> false + } + } + + companion object { + private fun inRange(value: Int, min: Int?, max: Int?): Boolean { + if (min != null && value < min) { + return false + } + if (max != null && value > max) { + return false + } + return true + } + + private fun contains(player: Player, inventory: world.gregs.voidps.engine.inv.Inventory, items: List): Boolean { + for (item in items) { + var found = false + for (id in item.ids) { + val amount = if (id == "empty") { + inventory.spaces + } else { + inventory.count(id) + } + if (!inRange(amount, item.min, item.max)) { + continue + } + if (item.usable && amount > 0 && id != "empty") { + val index = inventory.indexOf(id) + if (index == -1) { + error("Unable to find item $id in inventory.") + } + val item = inventory[index] + if (!player.hasRequirementsToUse(item)) { + continue + } + } + if (item.equippable && amount > 0 && id != "empty") { + val index = inventory.indexOf(id) + if (index == -1) { + error("Unable to find item $id in inventory.") + } + val item = inventory[index] + if (!player.hasRequirements(item)) { + continue + } + } + found = true + } + if (!found) { + return false + } + } + return true + } + + fun parse(list: List>>>, name: String): List { + val requirements = mutableListOf() + for ((type, value) in list) { + val condition = parse(type, value) ?: error("No condition parser for '$type' in $name.") + requirements.add(condition) + } + requirements.sortBy { it.priority } + return requirements + } + + fun parse(type: String, list: List>): Condition? = when (type) { + "inventory" -> parseInventory(list) + "equipment" -> parseEquipment(list) + "bank" -> parseBank(list) + "owns" -> parseOwns(list) + "variable" -> parseVariable(list) + "clock" -> parseClock(list) + "timer" -> parseTimer(list) + "queue" -> parseQueue(list) + "area" -> parseArea(list) + "tile" -> parseTile(list) + "object" -> parseObject(list) + "combat_level" -> parseCombat(list) + "interface_open" -> parseInterface(list) + "mode" -> parseMode(list) + "skill" -> parseSkills(list) + else -> null + } + + private fun parseInventory(list: List>): Inventory = Inventory(parseItems(list)) + + private fun parseItems(list: List>): MutableList { + val items = mutableListOf() + for (map in list) { + val id = map["id"] as? String ?: error("Missing item id in $list") + var min = map["min"] as? Int + val max = map["max"] as? Int + if (min == null && max == null) { + min = 1 + } + val ids = toIds(id) + items.add(Entry(ids, min, max)) + } + return items + } + + private fun toIds(id: String): Set = id.split(",").flatMap { if (it.any { char -> char == '*' || char == '#' }) Wildcards.get(it, Wildcard.Item) else setOf(it) }.toSet() + + @Suppress("UNCHECKED_CAST") + private fun parseEquipment(list: List>): Equipment { + val items = mutableMapOf() + for (map in list) { + for ((key, value) in map) { + val slot = EquipSlot.by(key) + require(slot != EquipSlot.None) { "Invalid equipment slot: $key in $list" } + value as? Map ?: error("Equipment $key expecting map, found: $value") + val id = value["id"] as? String ?: error("Missing item id in $list") + var min = value["min"] as? Int + val max = value["max"] as? Int + if (min == null && max == null) { + min = 1 + } + items[slot] = Entry( + ids = toIds(id), + min = min, + max = max, + ) + } + } + return Equipment(items) + } + + private fun parseBank(list: List>): Bank = Bank(parseItems(list)) + + private fun parseOwns(list: List>): Owns? { + val map = list.single() + if (map.containsKey("id")) { + return Owns(map["id"] as String, min = map["min"] as? Int, max = map["max"] as? Int) + } + return null + } + + @Suppress("UNCHECKED_CAST") + private fun parseVariable(list: List>): Condition? { + val map = list.single() + if (map.containsKey("equals")) { + return Variable( + id = map["id"] as String, + equals = map["equals"]!!, + default = map["default"]!!, + ) + } else if (map.containsKey("min") || map.containsKey("max")) { + return VariableIn( + id = map["id"] as String, + default = map["default"] as Int, + min = map["min"] as? Int ?: 0, + max = map["max"] as? Int, + ) + } + return null + } + + private fun parseClock(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + if (map.containsKey("min") || map.containsKey("max")) { + return ClockRemaining( + id = map["id"] as String, + min = map["min"] as? Int, + max = map["max"] as? Int, + seconds = map["seconds"] as? Boolean ?: false, + ) + } + return Clock(id = map["id"] as String) + } + return null + } + + private fun parseTimer(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return Timer(id = map["id"] as String) + } + return null + } + + private fun parseQueue(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return Queue(id = map["id"] as String) + } + return null + } + + private fun parseObject(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return ObjectExists( + id = map["id"] as String, + x = map["x"] as Int, + y = map["y"] as Int, + ) + } + return null + } + + private fun parseTile(list: List>): Condition? { + val map = list.single() + if (map.containsKey("x") || map.containsKey("y") || map.containsKey("level")) { + return AtTile( + x = map["x"] as? Int, + y = map["y"] as? Int, + level = map["level"] as? Int, + ) + } + return null + } + + private fun parseArea(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return InArea(id = map["id"] as String) + } + return null + } + + private fun parseCombat(list: List>): Condition? { + val map = list.single() + if (map.containsKey("min") || map.containsKey("max")) { + return CombatLevel(min = map["min"] as? Int, max = map["max"] as? Int) + } + return null + } + + private fun parseInterface(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return InterfaceOpen(id = map["id"] as String) + } + return null + } + + private fun parseMode(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return HasMode(id = map["id"] as String) + } + return null + } + + private fun parseSkills(list: List>): Condition? { + val map = list.single() + if (map.containsKey("id")) { + return SkillLevel( + skill = Skill.of((map["id"] as String).toPascalCase()) ?: error("Unknown skill: '${map["id"]}'"), + min = map["min"] as? Int, + max = map["max"] as? Int, + ) + } + return null + } + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/Reason.kt b/game/src/main/kotlin/content/bot/behaviour/Reason.kt new file mode 100644 index 0000000000..d785f574d1 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/Reason.kt @@ -0,0 +1,27 @@ +package content.bot.behaviour + +interface Reason { + data class Invalid(val message: String) : HardReason { + override fun toString() = "Invalid(\"$message\")" + } + object Cancelled : HardReason { + override fun toString() = "Cancelled" + } + object NoRoute : HardReason { + override fun toString() = "NoRoute" + } + object Timeout : HardReason { + override fun toString() = "Timeout" + } + object Stuck : SoftReason { + override fun toString() = "Stuck" + } + object NoTarget : SoftReason { + override fun toString() = "NoTarget" + } + data class Requirement(val condition: Condition) : HardReason { + override fun toString() = "Requirement($condition)" + } +} +interface SoftReason : Reason +interface HardReason : Reason diff --git a/game/src/main/kotlin/content/bot/behaviour/action/ActionParser.kt b/game/src/main/kotlin/content/bot/behaviour/action/ActionParser.kt new file mode 100644 index 0000000000..33ae59161a --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/action/ActionParser.kt @@ -0,0 +1,241 @@ +package content.bot.behaviour.action + +import content.bot.behaviour.Condition + +sealed class ActionParser { + open val required = emptySet() + open val optional = emptySet() + + abstract fun parse(map: Map): BotAction + + fun check(map: Map): String? { + for (key in required) { + if (!map.containsKey(key)) { + return "missing key '$key' in map $map" + } + } + for (key in map.keys) { + if (!map.containsKey(key) && !optional.contains(key)) { + return "unexpected key '$key' in map $map" + } + } + return null + } + + object InteractNpcParser : ActionParser() { + override val required = setOf("id", "option") + override val optional = setOf("delay", "success", "radius", "heal_percent", "loot_over_value") + + override fun parse(map: Map): BotAction { + val option = map["option"] as String + val id = map["id"] as String + val delay = map["delay"] as? Int ?: 0 + val success = requirement(map, "success").singleOrNull() + val radius = map["radius"] as? Int ?: 10 + return if (option == "Attack") { + val healPercent = map["heal_percent"] as? Int ?: 20 + val lootOverValue = map["loot_over_value"] as? Int ?: 0 + BotAction.FightNpc(id, delay, success, radius, healPercent, lootOverValue) + } else { + BotAction.InteractNpc(option, id, delay, success, radius) + } + } + } + + object InterfaceParser : ActionParser() { + override val required = setOf("option", "id") + override val optional = setOf("success") + + override fun parse(map: Map): BotAction { + val option = map["option"] as String + val id = map["id"] as String + val success = requirement(map, "success").singleOrNull() + return BotAction.InterfaceOption(option, id, success) + } + } + + object Firemaking : ActionParser() { + override val required = setOf("id", "area") + + override fun parse(map: Map): BotAction { + val area = map["area"] as String + val id = map["id"] as String + return BotAction.Firemaking(id, area) + } + } + + object CloseInterfaceParser : ActionParser() { + override val required = setOf("id") + override fun parse(map: Map): BotAction = BotAction.CloseInterface + } + + object DialogueParser : ActionParser() { + override val required = setOf("id") + override val optional = setOf("option", "success") + + override fun parse(map: Map): BotAction { + val option = map["option"] as? String ?: "" + val id = map["id"] as String + val success = requirement(map, "success").singleOrNull() + return BotAction.DialogueContinue(option, id, success) + } + } + + object ItemOnObjectParser : ActionParser() { + override val required = setOf("id", "object") + override val optional = setOf("success") + + override fun parse(map: Map): BotAction { + val id = map["id"] as String + val obj = map["object"] as String + val success = requirement(map, "success").singleOrNull() + return BotAction.ItemOnObject(id, obj, success) + } + } + + object ItemOnItemParser : ActionParser() { + override val required = setOf("id", "on") + override val optional = setOf("success") + + override fun parse(map: Map): BotAction { + val id = map["id"] as String + val item = map["on"] as String + val success = requirement(map, "success").singleOrNull() + return BotAction.ItemOnItem(id, item, success) + } + } + + object InteractObjectParser : ActionParser() { + override val required = setOf("option", "id", "success") + override val optional = setOf("delay", "radius", "x", "y") + + override fun parse(map: Map): BotAction { + val option = map["option"] as String + val id = map["id"] as String + val delay = map["delay"] as? Int ?: 0 + val success = requirement(map, "success").singleOrNull() + val radius = map["radius"] as? Int ?: 10 + val x = map["x"] as? Int + val y = map["y"] as? Int + return BotAction.InteractObject(option, id, delay, success, radius, x, y) + } + } + + object InteractFloorItemParser : ActionParser() { + override val required = setOf("option", "id", "success") + override val optional = setOf("delay", "radius", "x", "y") + + override fun parse(map: Map): BotAction { + val option = map["option"] as String + val id = map["id"] as String + val delay = map["delay"] as? Int ?: 0 + val success = requirement(map, "success").singleOrNull() + val radius = map["radius"] as? Int ?: 10 + val x = map["x"] as? Int + val y = map["y"] as? Int + return BotAction.InteractFloorItem(option, id, delay, success, radius, x, y) + } + } + + object GoToParser : ActionParser() { + override val optional = setOf("area", "nearest") + override fun parse(map: Map) = when { + map.containsKey("area") -> BotAction.GoTo(map["area"] as String) + map.containsKey("nearest") -> BotAction.GoToNearest(map["nearest"] as String) + else -> error("Expected field 'area' or 'nearest', but got '${map["area"]}'") + } + } + + object WalkToParser : ActionParser() { + override val required = setOf("x", "y") + override val optional = setOf("radius") + override fun parse(map: Map) = BotAction.WalkTo(map["x"] as Int, map["y"] as Int, map["radius"] as? Int ?: 4) + } + + object WaitParser : ActionParser() { + override val required = setOf("ticks") + override fun parse(map: Map) = BotAction.Wait(map["ticks"] as Int) + } + + object EnterParser : ActionParser() { + override val optional = setOf("int", "string") + override fun parse(map: Map) = when { + map.containsKey("int") -> BotAction.IntEntry(map["int"] as Int) + map.containsKey("string") -> BotAction.StringEntry(map["string"] as String) + else -> error("Expected field 'int' or 'string', but got '${map["area"]}'") + } + } + + object RestartParser : ActionParser() { + override val required = setOf("success") + override val optional = setOf("wait_if") + + @Suppress("UNCHECKED_CAST") + override fun parse(map: Map): BotAction { + val requirement = requirement(map, "success").single() + val wait = map["wait_if"] + val waitIf = if (wait is List<*>) { + val requirements = mutableListOf() + wait as List> + for (element in wait) { + requirements.addAll(requirement(element)) + } + requirements + } else { + requirement(map, "wait_if") + } + return BotAction.Restart(wait = waitIf, success = requirement) + } + } + + companion object { + @Suppress("UNCHECKED_CAST") + private fun requirement(map: Map, key: String): List { + val parent = map[key] as? Map ?: return listOf() + return requirement(parent) + } + + @Suppress("UNCHECKED_CAST") + private fun requirement(map: Map): List { + val key = map.keys.singleOrNull() ?: error("Collection $map has more than one element.") + val value = map[key] ?: return listOf() + val list = when (value) { + is Map<*, *> -> listOf(value as Map) + is List<*> -> value as List> + else -> return listOf() + } + return Condition.parse(listOf(key to list), "ActionParser.$key in $map") + } + + fun parse(list: List>>, name: String): List { + val actions = mutableListOf() + for ((type, map) in list) { + val parser = parsers[type] ?: error("No action parser for '$type' in $name.") + val error = parser.check(map) + if (error != null) { + error("Action '$type' $error in $name.") + } + val action = parser.parse(map) + actions.add(action) + } + return actions + } + + private val parsers = mapOf( + "npc" to InteractNpcParser, + "object" to InteractObjectParser, + "floor_item" to InteractFloorItemParser, + "item_on_object" to ItemOnObjectParser, + "item_on_item" to ItemOnItemParser, + "go_to" to GoToParser, + "tile" to WalkToParser, + "wait" to WaitParser, + "restart" to RestartParser, + "interface" to InterfaceParser, + "interface_close" to CloseInterfaceParser, + "continue" to DialogueParser, + "enter" to EnterParser, + "firemaking" to Firemaking, + ) + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/action/BotAction.kt b/game/src/main/kotlin/content/bot/behaviour/action/BotAction.kt new file mode 100644 index 0000000000..9b1ab3a075 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/action/BotAction.kt @@ -0,0 +1,639 @@ +package content.bot.behaviour.action + +import content.bot.Bot +import content.bot.behaviour.BehaviourFrame +import content.bot.behaviour.BehaviourState +import content.bot.behaviour.Condition +import content.bot.behaviour.Reason +import content.bot.behaviour.navigation.NavigationGraph +import content.bot.behaviour.navigation.NavigationShortcut +import content.bot.behaviour.setup.Resolver +import content.entity.combat.attackers +import content.entity.combat.dead +import org.rsmod.game.pathfinder.StepValidator +import world.gregs.voidps.engine.GameLoop +import world.gregs.voidps.engine.client.instruction.InstructionHandlers +import world.gregs.voidps.engine.client.instruction.InterfaceHandler +import world.gregs.voidps.engine.client.ui.menu +import world.gregs.voidps.engine.data.definition.Areas +import world.gregs.voidps.engine.data.definition.InterfaceDefinitions +import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.entity.character.mode.EmptyMode +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnFloorItemInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnNPCInteract +import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnObjectInteract +import world.gregs.voidps.engine.entity.character.mode.move.canTravel +import world.gregs.voidps.engine.entity.character.npc.NPCs +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.item.Item +import world.gregs.voidps.engine.entity.item.floor.FloorItem +import world.gregs.voidps.engine.entity.item.floor.FloorItems +import world.gregs.voidps.engine.entity.obj.GameObject +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.entity.obj.ObjectLayer +import world.gregs.voidps.engine.event.wildcardEquals +import world.gregs.voidps.engine.get +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.map.Spiral +import world.gregs.voidps.engine.timer.toTicks +import world.gregs.voidps.network.client.instruction.* +import world.gregs.voidps.type.Tile +import java.util.concurrent.TimeUnit +import kotlin.collections.indexOf +import kotlin.collections.iterator + +sealed interface BotAction { + fun start(bot: Bot, frame: BehaviourFrame): BehaviourState = BehaviourState.Running + fun update(bot: Bot, frame: BehaviourFrame): BehaviourState? = null + + data class Wait(val ticks: Int, val state: BehaviourState = BehaviourState.Success) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame) = BehaviourState.Wait(ticks, state) + } + + data class GoTo(val target: String) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame): BehaviourState { + val def = Areas.getOrNull(target) ?: return BehaviourState.Failed(Reason.Invalid("No areas found with id '$target'.")) + if (bot.tile in def.area) { + return BehaviourState.Success + } + return BehaviourState.Running + } + + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (bot.mode != EmptyMode) { + return BehaviourState.Running + } + val def = Areas.getOrNull(target) ?: return BehaviourState.Failed(Reason.Invalid("No areas found with id '$target'.")) + if (bot.tile in def.area) { + return BehaviourState.Success + } + + val graph = get() + val list = mutableListOf() + val success = graph.find(bot.player, list, target) + return queueRoute(success, list, graph, bot, target) + } + + companion object { + internal fun queueRoute(success: Boolean, list: MutableList, graph: NavigationGraph, bot: Bot, target: String): BehaviourState { + if (!success) { + return BehaviourState.Failed(Reason.NoRoute) + } + val actions = mutableListOf() + var nav: NavigationShortcut? = null + for (edge in list) { + val shortcut = graph.shortcut(edge) + if (shortcut != null) { + nav = shortcut + } else { + actions.addAll(graph.actions(edge) ?: continue) + } + } + if (actions.isNotEmpty()) { + bot.queue(BehaviourFrame(Resolver("go_to_$target", 0, TimeUnit.SECONDS.toTicks(60), actions = actions))) + } + if (nav != null) { + bot.queue(BehaviourFrame(nav)) + } + if (bot.frames.isEmpty()) { + return BehaviourState.Failed(Reason.NoRoute) + } + return BehaviourState.Running + } + } + } + + data class GoToNearest(val tag: String) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame) = inArea(bot) ?: BehaviourState.Running + + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (bot.mode != EmptyMode) { + return BehaviourState.Running + } + val state = inArea(bot) + if (state != null) { + return state + } + val graph = get() + val list = mutableListOf() + val success = graph.findNearest(bot.player, list, tag) + return GoTo.queueRoute(success, list, graph, bot, tag) + } + + private fun inArea(bot: Bot): BehaviourState? { + val set = Areas.tagged(tag) + if (set.isEmpty()) { + return BehaviourState.Failed(Reason.Invalid("No areas tagged with tag '$tag'.")) + } + if (set.any { bot.tile in it.area }) { + return BehaviourState.Success + } + return null + } + } + + data class InteractNpc( + val option: String, + val id: String, + val delay: Int = 0, + val success: Condition? = null, + val radius: Int = 10, + ) : BotAction { + + override fun start(bot: Bot, frame: BehaviourFrame) = BehaviourState.Running + + override fun update(bot: Bot, frame: BehaviourFrame) = when { + success?.check(bot.player) == true -> BehaviourState.Success + bot.mode is PlayerOnNPCInteract -> if (success == null) BehaviourState.Success else BehaviourState.Running + bot.mode is EmptyMode -> search(bot) + else -> null + } + + private fun search(bot: Bot): BehaviourState { + val player = bot.player + val ids = if (id.contains(",")) id.split(",") else listOf(id) + for (tile in Spiral.spiral(player.tile, radius)) { + for (npc in NPCs.at(tile)) { + if (ids.none { wildcardEquals(it, npc.id) }) { + continue + } + val index = npc.def(player).options.indexOf(option) + if (index == -1) { + continue + } + val valid = get().handle(bot.player, InteractNPC(npc.index, index + 1)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid npc interaction: ${npc.index} ${index + 1}")) + } + return BehaviourState.Running + } + } + return handleNoTarget() + } + + private fun handleNoTarget(): BehaviourState { + if (success == null) { + return BehaviourState.Failed(Reason.NoTarget) + } + if (delay > 0) { + return BehaviourState.Wait(delay, BehaviourState.Running) + } + return BehaviourState.Running + } + } + + data class FightNpc( + val id: String, + val delay: Int = 0, + val success: Condition? = null, + val radius: Int = 10, + val healPercentage: Int = 20, + val lootOverValue: Int = 0, + ) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame) = BehaviourState.Running + + override fun update(bot: Bot, frame: BehaviourFrame) = when { + healPercentage > 0 && bot.levels.get(Skill.Constitution) <= bot.levels.getMax(Skill.Constitution) / healPercentage -> eat(bot) + success?.check(bot.player) == true -> BehaviourState.Success + bot.mode is PlayerOnNPCInteract -> if (success == null) BehaviourState.Success else BehaviourState.Running + bot.mode is PlayerOnFloorItemInteract -> BehaviourState.Running + bot.mode is EmptyMode -> search(bot) + else -> null + } + + private fun eat(bot: Bot): BehaviourState { + val inventory = bot.player.inventory + for (index in inventory.indices) { + val item = inventory[index] + val option = item.def.options.indexOf("Eat") + if (option == -1) { + continue + } + val valid = get().handle(bot.player, InteractInterface(149, 0, item.def.id, index, option)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid inventory interaction: ${item.def.id} $index $option")) + } + return BehaviourState.Wait(1, BehaviourState.Running) + } + return BehaviourState.Running + } + + private fun search(bot: Bot): BehaviourState { + val player = bot.player + for (tile in Spiral.spiral(player.tile, radius)) { + for (item in FloorItems.at(tile)) { + if (item.owner != player.accountName) { + continue + } + if (item.def.cost <= lootOverValue) { + continue + } + val index = item.def.floorOptions.indexOf("Take") + val valid = get().handle(bot.player, InteractFloorItem(item.def.id, item.tile.x, item.tile.y, index)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid floor item interaction: $item $index")) + } + return BehaviourState.Running + } + for (npc in NPCs.at(tile)) { + if (!wildcardEquals(id, npc.id)) { + continue + } + val index = npc.def(player).options.indexOf("Attack") + if (index == -1) { + continue + } + if (npc.dead || npc.attackers.isNotEmpty() && !npc.attackers.contains(player)) { + continue + } + val valid = get().handle(bot.player, InteractNPC(npc.index, index + 1)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid npc interaction: ${npc.index} ${index + 1}")) + } + return BehaviourState.Running + } + } + return handleNoTarget() + } + + private fun handleNoTarget(): BehaviourState { + if (success == null) { + return BehaviourState.Failed(Reason.NoTarget) + } + if (delay > 0) { + return BehaviourState.Wait(delay, BehaviourState.Running) + } + return BehaviourState.Running + } + } + + data class InteractObject( + val option: String, + val id: String, + val delay: Int = 0, + val success: Condition? = null, + val radius: Int = 10, + val x: Int? = null, + val y: Int? = null, + ) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame) = BehaviourState.Running + + override fun update(bot: Bot, frame: BehaviourFrame) = when { + success?.check(bot.player) == true -> BehaviourState.Success + bot.mode is PlayerOnObjectInteract -> if (success == null) BehaviourState.Success else BehaviourState.Running + bot.mode is EmptyMode -> search(bot) + else -> null + } + + private fun search(bot: Bot): BehaviourState { + val player = bot.player + val start = if (x != null && y != null) player.tile.copy(x = x, y = y) else player.tile + for (tile in Spiral.spiral(start, radius)) { + for (obj in GameObjects.at(tile)) { + return interact(player, obj) ?: continue + } + } + return handleNoTarget() + } + + private fun interact(player: Player, obj: GameObject): BehaviourState? { + if (!wildcardEquals(id, obj.id)) { + return null + } + val index = obj.def(player).options?.indexOf(option) + if (index == null || index == -1) { + return null + } + val valid = get().handle(player, InteractObject(obj.intId, obj.x, obj.y, index + 1)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid object interaction: $obj ${index + 1}")) + } + return BehaviourState.Running + } + + private fun handleNoTarget(): BehaviourState { + if (success == null) { + return BehaviourState.Failed(Reason.NoTarget) + } + if (delay > 0) { + return BehaviourState.Wait(delay, BehaviourState.Running) + } + return BehaviourState.Running + } + } + + data class InteractFloorItem( + val option: String, + val id: String, + val delay: Int = 0, + val success: Condition? = null, + val radius: Int = 10, + val x: Int? = null, + val y: Int? = null, + ) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame) = BehaviourState.Running + + override fun update(bot: Bot, frame: BehaviourFrame) = when { + success?.check(bot.player) == true -> BehaviourState.Success + bot.mode is PlayerOnFloorItemInteract -> if (success == null) BehaviourState.Success else BehaviourState.Running + bot.mode is EmptyMode -> search(bot) + else -> null + } + + private fun search(bot: Bot): BehaviourState { + val player = bot.player + val start = if (x != null && y != null) player.tile.copy(x = x, y = y) else player.tile + for (tile in Spiral.spiral(start, radius)) { + for (obj in FloorItems.at(tile)) { + return interact(player, obj) ?: continue + } + } + return handleNoTarget() + } + + private fun interact(player: Player, item: FloorItem): BehaviourState? { + if (!wildcardEquals(id, item.id)) { + return null + } + val index = item.def.floorOptions.indexOf(option) + if (index == -1) { + return null + } + val valid = get().handle(player, InteractFloorItem(item.def.id, item.tile.x, item.tile.y, index + 1)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid floor item interaction: $item ${index + 1}")) + } + return BehaviourState.Running + } + + private fun handleNoTarget(): BehaviourState { + if (success == null) { + return BehaviourState.Failed(Reason.NoTarget) + } + if (delay > 0) { + return BehaviourState.Wait(delay, BehaviourState.Running) + } + return BehaviourState.Running + } + } + + data class ItemOnItem(val item: String, val on: String, val success: Condition? = null) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState? { + if (success != null && success.check(bot.player)) { + return BehaviourState.Success + } + val state = itemOnItem(bot.player, item, on) + if (state != null) { + return state + } + return when { + success == null -> BehaviourState.Wait(1, BehaviourState.Success) + success.check(bot.player) -> BehaviourState.Success + else -> BehaviourState.Running + } + } + + companion object { + fun itemOnItem(player: Player, item: String, on: String): BehaviourState? { + val inventory = player.inventory + val fromSlot = inventory.indexOf(item) + if (fromSlot == -1) { + return BehaviourState.Failed(Reason.Invalid("No inventory item '$item'.")) + } + val toSlot = inventory.indexOf(on) + if (toSlot == -1) { + return BehaviourState.Failed(Reason.Invalid("No inventory item '$on'.")) + } + val from = inventory[fromSlot] + val to = inventory[toSlot] + val valid = get().handle(player, InteractInterfaceItem(from.def.id, to.def.id, fromSlot, toSlot, 149, 0, 149, 0)) + if (valid) { + return null + } + return BehaviourState.Failed(Reason.Invalid("Invalid item on item: ${from.def.id}:$fromSlot -> ${to.def.id}:$toSlot.")) + } + } + } + + data class ItemOnObject(val item: String, val id: String, val success: Condition? = null) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (success != null && success.check(bot.player)) { + return BehaviourState.Success + } + val inventory = bot.player.inventory + val slot = inventory.indexOf(this@ItemOnObject.item) + if (slot == -1) { + return BehaviourState.Failed(Reason.Invalid("No inventory item '${this@ItemOnObject.item}'.")) + } + val item = inventory[slot] + return search(bot, item, slot) + } + + private fun search(bot: Bot, item: Item, slot: Int): BehaviourState { + val player = bot.player + val ids = if (id.contains(",")) id.split(",") else listOf(id) + for (tile in Spiral.spiral(player.tile, 10)) { + for (obj in GameObjects.at(tile)) { + if (ids.none { wildcardEquals(it, obj.id) }) { + continue + } + val valid = get().handle(bot.player, InteractInterfaceObject(obj.intId, obj.x, obj.y, 149, 0, item.def.id, slot)) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid item on object: ${item.def.id}:$slot -> $obj.")) + } + return BehaviourState.Running + } + } + if (success == null) { + return BehaviourState.Failed(Reason.NoTarget) + } + if (success.check(bot.player)) { + return BehaviourState.Success + } + return BehaviourState.Running + } + } + + data class InterfaceOption(val option: String, val id: String, val success: Condition? = null) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState? { + if (success != null && success.check(bot.player)) { + return BehaviourState.Success + } + val definitions = get() + val split = id.split(":") + if (split.size < 2) { + return BehaviourState.Failed(Reason.Invalid("Invalid interface id '$id'.")) + } + val (id, component) = split + val item = split.getOrNull(2) + val def = definitions.getOrNull(id) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface id $id:$component:$item.")) + val componentId = definitions.getComponentId(id, component) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface component $id:$component:$item.")) + val componentDef = definitions.getComponent(id, component) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface component definition $id:$component:$item.")) + var options = componentDef.options + if (options == null) { + options = componentDef.getOrNull("options") ?: emptyArray() + } + val index = options.indexOf(option) + if (index == -1) { + return BehaviourState.Failed(Reason.Invalid("No interface option $option for $id:$component:$item options=${options.contentToString()}.")) + } + val itemDef = if (item != null) ItemDefinitions.getOrNull(item) else null + + var inv = InterfaceHandler.getInventory(bot.player, id, component, componentDef) + if (inv != null && component == "sample") { + inv = "${inv}_sample" + } + var itemSlot = if (item != null && inv != null) bot.player.inventories.inventory(inv).indexOf(item) else -1 + var itemId = itemDef?.id ?: -1 + if (id == "shop") { + itemId = -1 + itemSlot *= 6 + } + val valid = get().handle( + bot.player, + InteractInterface( + interfaceId = def.id, + componentId = componentId, + itemId = itemId, + itemSlot = itemSlot, + option = index, + ), + ) + return when { + !valid -> BehaviourState.Failed(Reason.Invalid("Invalid interaction: ${def.id}:$componentId:${itemDef?.stringId} slot $itemSlot option $index.")) + success == null -> BehaviourState.Wait(1, BehaviourState.Success) + success.check(bot.player) -> BehaviourState.Success + else -> BehaviourState.Running + } + } + } + + data class DialogueContinue(val option: String, val id: String, val success: Condition? = null) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (success != null && success.check(bot.player)) { + return BehaviourState.Success + } + val definitions = get() + val split = id.split(":") + if (split.size < 2) { + return BehaviourState.Failed(Reason.Invalid("Invalid interface id '$id'.")) + } + val (id, component) = split + val item = split.getOrNull(2) + val def = definitions.getOrNull(id) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface id $id:$component:$item.")) + val componentId = definitions.getComponentId(id, component) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface component $id:$component:$item.")) + val componentDef = definitions.getComponent(id, component) ?: return BehaviourState.Failed(Reason.Invalid("Invalid interface component definition $id:$component:$item.")) + var options = componentDef.options + if (options == null) { + options = componentDef.getOrNull("options") ?: emptyArray() + } + val index = options.indexOf(option) + val valid = get().handle( + bot.player, + InteractDialogue( + interfaceId = def.id, + componentId = componentId, + option = index, + ), + ) + if (!valid) { + return BehaviourState.Failed(Reason.Invalid("Invalid interaction: ${def.id}:$componentId option=$index.")) + } + return BehaviourState.Wait(1, BehaviourState.Success) + } + } + + data class IntEntry(val value: Int) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame): BehaviourState { + bot.player.instructions.trySend(EnterInt(value)) + return BehaviourState.Wait(1, BehaviourState.Success) + } + } + + object CloseInterface : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (bot.player.menu == null) { + return BehaviourState.Success + } + if (get().handle(bot.player, InterfaceClosedInstruction)) { + return BehaviourState.Success + } + return BehaviourState.Failed(Reason.NoTarget) + } + } + + /** + * Restarts the current action when [check] doesn't hold true (or bot has no mode) and success state isn't matched. + */ + data class Restart( + val wait: List, + val success: Condition, + ) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + if (success.check(bot.player)) { + return BehaviourState.Success + } + if (wait.isEmpty() && bot.mode !is EmptyMode) { + return BehaviourState.Running + } else if (wait.any { it.check(bot.player) }) { + return BehaviourState.Running + } + frame.index = 0 + return BehaviourState.Running + } + } + + data class StringEntry(val value: String) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame): BehaviourState { + bot.player.instructions.trySend(EnterString(value)) + return BehaviourState.Wait(1, BehaviourState.Success) + } + } + + data class WalkTo(val x: Int, val y: Int, val radius: Int = 4) : BotAction { + override fun start(bot: Bot, frame: BehaviourFrame): BehaviourState { + bot.player.instructions.trySend(Walk(x, y)) + return BehaviourState.Running + } + + override fun update(bot: Bot, frame: BehaviourFrame) = when { + bot.tile.within(x, y, bot.tile.level, radius) -> BehaviourState.Success + bot.mode is EmptyMode && GameLoop.tick - bot.steps.last > 10 -> BehaviourState.Failed(Reason.Stuck) + else -> BehaviourState.Running + } + } + + data class Firemaking(val item: String, val area: String) : BotAction { + override fun update(bot: Bot, frame: BehaviourFrame): BehaviourState { + when { + bot.mode != EmptyMode -> return BehaviourState.Running + cantLightOn(bot.tile) -> { + val handlers = get() + val steps = get() + if (steps.canTravel(bot, -1, 0)) { + handlers.handle(bot.player, Walk(bot.tile.x - 1, bot.tile.y)) + return BehaviourState.Wait(1, BehaviourState.Running) + } + if (steps.canTravel(bot.tile.level, bot.tile.x - 1, bot.tile.y, -1, 0)) { + handlers.handle(bot.player, Walk(bot.tile.x - 2, bot.tile.y)) + return BehaviourState.Wait(1, BehaviourState.Running) + } + val area = Areas[area] + for (tile in area) { + if (cantLightOn(tile)) { + continue + } + handlers.handle(bot.player, Walk(tile.x, tile.y)) + return BehaviourState.Wait(1, BehaviourState.Running) + } + return BehaviourState.Failed(Reason.Stuck) + } + bot.player.inventory.contains(item) -> return ItemOnItem.itemOnItem(bot.player, "tinderbox", item) ?: BehaviourState.Running + else -> return BehaviourState.Success + } + } + + private fun cantLightOn(tile: Tile): Boolean = GameObjects.getLayer(tile, ObjectLayer.GROUND) != null + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/activity/ActivitySlots.kt b/game/src/main/kotlin/content/bot/behaviour/activity/ActivitySlots.kt new file mode 100644 index 0000000000..12b86165c1 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/activity/ActivitySlots.kt @@ -0,0 +1,18 @@ +package content.bot.behaviour.activity + +/** + * Track bots occupying and releasing access to a fixed number of slots per activity + */ +class ActivitySlots { + private val occupied = mutableMapOf() + + fun hasFree(activity: BotActivity): Boolean = occupied.getOrDefault(activity.id, 0) < activity.capacity + + fun occupy(activity: BotActivity) { + occupied[activity.id] = (occupied.getOrDefault(activity.id, 0) + 1).coerceAtMost(activity.capacity) + } + + fun release(activity: BotActivity) { + occupied[activity.id] = ((occupied[activity.id] ?: 1) - 1).coerceAtLeast(0) + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/activity/BotActivity.kt b/game/src/main/kotlin/content/bot/behaviour/activity/BotActivity.kt new file mode 100644 index 0000000000..6e1591b1a7 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/activity/BotActivity.kt @@ -0,0 +1,21 @@ +package content.bot.behaviour.activity + +import content.bot.behaviour.Behaviour +import content.bot.behaviour.Condition +import content.bot.behaviour.action.BotAction +import world.gregs.voidps.engine.timer.toTicks +import java.util.concurrent.TimeUnit + +/** + * An activity with a limited number of slots that bots can perform + * E.g. cutting oak trees in varrock, mining copper ore in lumbridge + */ +data class BotActivity( + override val id: String, + val capacity: Int, + override val timeout: Int = TimeUnit.MINUTES.toTicks(1), + override val requires: List = emptyList(), + override val setup: List = emptyList(), + override val actions: List = emptyList(), + override val produces: Set = emptySet(), +) : Behaviour diff --git a/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationGraph.kt b/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationGraph.kt new file mode 100644 index 0000000000..b644889873 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationGraph.kt @@ -0,0 +1,323 @@ +package content.bot.behaviour.navigation + +import content.bot.behaviour.Condition +import content.bot.behaviour.action.ActionParser +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.actions +import content.bot.behaviour.requirements +import content.bot.bot +import content.bot.isBot +import world.gregs.config.Config +import world.gregs.config.ConfigReader +import world.gregs.voidps.engine.data.definition.Areas +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.timedLoad +import world.gregs.voidps.type.Distance +import world.gregs.voidps.type.Tile +import java.util.PriorityQueue + +/** + * Weighted navigation graph for bot pathfinding. + * Represents a directed graph of nodes and edges with: + * - Per-edge weights, actions, and traversal conditions. + * - Optional shortcuts (e.g. teleports) treated as virtual edges. + * - Node metadata such as tiles and area tags. + */ +class NavigationGraph( + private val endNodes: IntArray = intArrayOf(), + private val edgeWeights: IntArray = intArrayOf(), + private val edgeConditions: Array?> = emptyArray(), + private val actions: Array?> = emptyArray(), + private val adjacentEdges: Array = emptyArray(), + private val tiles: IntArray = intArrayOf(), + private val tags: Array?> = emptyArray(), + private val shortcuts: Map = emptyMap(), + var nodeCount: Int = 0, +) { + + fun shortcut(edge: Int) = shortcuts[edge] + + fun actions(edge: Int): List? = actions[edge] + + fun weight(edge: Int): Int = edgeWeights[edge] + + fun edges(node: Int): IntArray? = adjacentEdges[node] + + fun tile(node: Int): Tile = Tile(tiles[node]) + + fun endTile(edge: Int): Tile { + val nodeIndex = endNodes[edge] + return Tile(tiles[nodeIndex]) + } + + /** + * Find a path to the nearest area with a [tag]. + */ + fun findNearest(player: Player, output: MutableList, tag: String): Boolean { + val start = startingPoints(player) + return find(player, output, start, target = { + tags[it]?.contains(tag) ?: false + }) + } + + /** + * Find a path to [area]. + */ + fun find(player: Player, output: MutableList, area: String): Boolean { + val start = startingPoints(player) + return find(player, output, start, target = { Tile(tiles[it]) in Areas[area] }) + } + + internal fun startingPoints(player: Player): Set = buildSet { + // Append all nodes within 10 tiles + for (index in 1 until tiles.size) { + val tile = Tile(tiles[index]) + if (player.tile.level != tile.level) { + continue + } + val distance = player.tile.distanceTo(tile) + if (distance > 10) { + continue + } + add(Node(index, distance.coerceAtLeast(0))) + } + // Check for shortcuts (i.e. item teleports) + val blocked = if (player.isBot) player.bot.blocked else emptySet() + for (shortcut in shortcuts.values) { + if (blocked.contains(shortcut.id)) { + continue + } + if (shortcut.requires.any { !it.check(player) }) { + continue + } + add(Node(0, 0)) + break + } + } + + internal fun find(player: Player, output: MutableList, start: Node, target: Int) = find(player, output, setOf(start)) { it == target } + + internal fun find(player: Player, output: MutableList, start: Set, target: Int) = find(player, output, start) { it == target } + + internal fun find(player: Player, output: MutableList, start: Node, target: (Int) -> Boolean) = find(player, output, setOf(start), target) + + /** + * Dijkstra algorithm + * Searches from a virtual starting node traverses all [adjacentEdges] until a [target] is found. + * Appends the completed route of edge indices to [output]. + * @return successful route found. + */ + internal fun find(player: Player, output: MutableList, startingPoints: Set, target: (Int) -> Boolean): Boolean { + output.clear() + val queue = PriorityQueue() + val visited = BooleanArray(nodeCount) + val distance = IntArray(nodeCount) { Int.MAX_VALUE } + val parentNode = IntArray(nodeCount) { -1 } + val previousEdge = IntArray(nodeCount) { -1 } + + for (start in startingPoints) { + if (target(start.index)) { + // Don't select target starting points, otherwise we'll have no edges to traverse. + // Not an issue as we queue all nearby points - normal dijkstra's would produce points not edges. + continue + } + distance[start.index] = -1 + queue.add(start) + } + while (queue.isNotEmpty()) { + val (node, cost) = queue.poll() + if (target(node)) { + // Reconstruct the path + var previous = node + while (parentNode[previous] != -1) { + output.add(0, previousEdge[previous]) + previous = parentNode[previous] + } + return true + } + if (visited[node]) { + continue + } + visited[node] = true + for (edge in adjacentEdges[node] ?: continue) { + val to = endNodes[edge] + if (visited[to]) { + continue + } + val weight = edgeWeights[edge] + if (cost + weight >= distance[to]) { + continue + } + val conditions = edgeConditions[edge] + if (conditions != null && conditions.any { !it.check(player) }) { + continue + } + distance[to] = cost + weight + parentNode[to] = node + previousEdge[to] = edge + queue.add(Node(to, cost + weight)) + } + } + return false + } + + internal data class Node(val index: Int, val cost: Int = 0) : Comparable { + override fun compareTo(other: Node) = cost.compareTo(other.cost) + } + + internal class Builder { + // Nodes + val tiles = LinkedHashSet() + val nodes = mutableSetOf() + val tags = mutableListOf?>() + + // Edges + val endNodes = mutableListOf() + val weights = mutableListOf() + val conditions = mutableListOf?>() + val actions = mutableListOf?>() + val edges = mutableMapOf>() + var edgeCount = 0 + + val shortcuts = mutableMapOf() + + init { + tiles.add(Tile.EMPTY) // Virtual + tags.add(null) + nodes.add(0) + } + + fun add(shortcut: NavigationShortcut): Int { + val name = shortcut.produces.firstOrNull { it.startsWith("area:") }?.removePrefix("area:") ?: throw IllegalArgumentException("Shortcut requires location product ${shortcut.id}") + val area = Areas[name] + val end = tiles.indexOfFirst { it in area } + if (end == -1) { + throw IllegalArgumentException("Unable to find nav graph tile in shortcut area '$name'.") + } + val index = addEdge(0, end, shortcut.weight, shortcut.actions, shortcut.requires) + shortcuts[index] = shortcut + return index + } + + fun addBiEdge(from: Tile, to: Tile, weight: Int, actions: List) { + val start = add(from) + val end = add(to) + addEdge(start, end, weight, actions) + addEdge(end, start, weight, actions) + } + + fun addEdge(from: Tile, to: Tile, weight: Int, actions: List, conditions: List?) { + val start = add(from) + val end = add(to) + addEdge(start, end, weight, actions, conditions) + } + + fun add(tile: Tile): Int { + if (tiles.add(tile)) { + val tags = Areas.get(tile.zone).filter { it.area.contains(tile) }.flatMap { it.tags } + this.tags.add(if (tags.isNotEmpty()) tags.toSet() else null) + return tiles.size - 1 + } + return tiles.indexOf(tile) + } + + fun addEdge(start: Int, end: Int, weight: Int, actions: List? = null, conditions: List? = null): Int { + val edgeIndex = edgeCount++ + nodes.add(start) + nodes.add(end) + edges.getOrPut(start) { mutableListOf() }.add(edgeIndex) + weights.add(weight) + endNodes.add(end) + this.conditions.add(conditions) + this.actions.add(actions) + return edgeIndex + } + + fun build() = NavigationGraph( + endNodes = endNodes.toIntArray(), + edgeWeights = weights.toIntArray(), + edgeConditions = conditions.toTypedArray(), + actions = actions.toTypedArray(), + adjacentEdges = Array(nodes.size) { edges[it]?.toIntArray() }, + nodeCount = nodes.size, + tiles = tiles.map { it.id }.toIntArray(), + tags = tags.toTypedArray(), + shortcuts = shortcuts, + ) + + fun print() { + for (start in edges.keys.sorted()) { + val adj = edges[start] ?: continue + for (edge in adj.sorted()) { + val end = endNodes[edge] + val weight = weights[edge] + println("Edge $edge: $start -> $end ($weight)") + } + } + println("Nodes: ${nodes.size} edges: $edgeCount") + } + } + + companion object { + fun loadGraph(paths: List, shortcuts: List): NavigationGraph { + val builder = Builder() + timedLoad("nav graph edge") { + for (path in paths) { + Config.fileReader(path) { + while (nextPair()) { + val list = key() + assert(list == "edges") { "Expected edges list, got: $list ${exception()}" } + while (nextElement()) { + var from = Tile.EMPTY + var to = Tile.EMPTY + var cost = 0 + val actions: MutableList>> = mutableListOf() + val requirements = mutableListOf>>>() + while (nextEntry()) { + when (val key = key()) { + "from" -> from = readTile() + "to" -> to = readTile() + "cost" -> cost = int() + "actions" -> actions(actions) + "requires" -> requirements(requirements) + else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + } + } + when { + actions.isEmpty() -> { + val cost = Distance.manhattan(from.x, from.y, to.x, to.y) + builder.addEdge(Tile(from.x, from.y, from.level), Tile(to.x, to.y, to.level), cost, listOf(BotAction.WalkTo(to.x, to.y)), null) + builder.addEdge(Tile(to.x, to.y, to.level), Tile(from.x, from.y, from.level), cost, listOf(BotAction.WalkTo(from.x, from.y)), null) + } + requirements.isEmpty() -> builder.addEdge(Tile(from.x, from.y, from.level), Tile(to.x, to.y, to.level), cost, ActionParser.parse(actions, exception()), null) + else -> builder.addEdge(Tile(from.x, from.y, from.level), Tile(to.x, to.y, to.level), cost, ActionParser.parse(actions, exception()), Condition.parse(requirements, exception())) + } + } + } + } + } + for (shortcut in shortcuts) { + builder.add(shortcut) + } + builder.edgeCount + } +// builder.print() + return builder.build() + } + + fun ConfigReader.readTile(): Tile { + var x = 0 + var y = 0 + var level = 0 + while (nextEntry()) { + when (val key = key()) { + "x" -> x = int() + "y" -> y = int() + "level" -> level = int() + else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + } + } + return Tile(x, y, level) + } + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationShortcut.kt b/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationShortcut.kt new file mode 100644 index 0000000000..2ff088125f --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/navigation/NavigationShortcut.kt @@ -0,0 +1,15 @@ +package content.bot.behaviour.navigation + +import content.bot.behaviour.Behaviour +import content.bot.behaviour.Condition +import content.bot.behaviour.action.BotAction + +data class NavigationShortcut( + override val id: String, + val weight: Int, + override val timeout: Int, + override val requires: List = emptyList(), + override val setup: List = emptyList(), + override val actions: List = emptyList(), + override val produces: Set = emptySet(), +) : Behaviour diff --git a/game/src/main/kotlin/content/bot/behaviour/setup/DynamicResolvers.kt b/game/src/main/kotlin/content/bot/behaviour/setup/DynamicResolvers.kt new file mode 100644 index 0000000000..9e3d35d2bb --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/setup/DynamicResolvers.kt @@ -0,0 +1,231 @@ +package content.bot.behaviour.setup + +import content.bot.behaviour.Condition +import content.bot.behaviour.Condition.Entry +import content.bot.behaviour.action.BotAction +import content.entity.npc.shop.stock.Price +import content.entity.player.bank.bank +import content.entity.player.bank.ownsItem +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.equip.equipped +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirements +import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirementsToUse +import world.gregs.voidps.engine.entity.item.Item +import world.gregs.voidps.engine.entity.item.slot +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot + +object DynamicResolvers { + + fun ids() = setOf("withdraw_from_bank", "equip_from_bank", "go_to_area", "buy_from_shop") + + // location, npc + val shopItems = mutableMapOf>>() + val sampleItems = mutableMapOf>>() + + fun resolver(player: Player, condition: Condition): Resolver? = when (condition) { + is Condition.InArea -> Resolver("go_to_area", -1, actions = listOf(BotAction.GoTo(condition.id))) + is Condition.Equipment -> resolveEquipment(player, condition.items) + is Condition.Inventory -> resolveInventory(player, condition.items) + else -> null + } + + private fun resolveInventory(player: Player, items: List): Resolver? { + var resolver = withdraw(player, items) + if (resolver != null) { + return resolver + } + resolver = buyItems(player, items) + if (resolver != null) { + return resolver + } + resolver = depositItems(player, items) + if (resolver != null) { + return resolver + } + return null + } + + private fun buyItems(player: Player, items: List): Resolver? { + for (entry in items) { + val amount = entry.min ?: 1 + if (entry.ids.any { id -> player.inventory.contains(id, amount) }) { + continue + } + for (id in entry.ids) { + for ((location, npc) in sampleItems[id] ?: continue) { + val actions = mutableListOf() + actions.add(BotAction.GoTo(location)) + actions.add(BotAction.InteractNpc("Trade", npc, success = Condition.InterfaceOpen("shop"))) + var remaining = amount + while (remaining > 0) { + val amount = when { + remaining >= 50 -> 50 + remaining >= 10 -> 10 + remaining >= 5 -> 5 + else -> 1 + } + actions.add(BotAction.InterfaceOption("Take-$amount", "shop:sample:$id")) + remaining -= amount + } + actions.add(BotAction.CloseInterface) + val spaces = if (player.inventory.stackable(id)) 1 else amount + return Resolver( + id = "take_from_shop", + weight = 20, + setup = listOf(Condition.Inventory(listOf(Entry(setOf("empty"), min = spaces)))), + actions = actions, + produces = setOf("item:$id"), + ) + } + for ((location, npc) in shopItems[id] ?: continue) { + val actions = mutableListOf() + val price = Price.of(id) + if (!player.ownsItem("coins", price * amount)) { + continue + } + actions.add(BotAction.GoTo(location)) + actions.add(BotAction.InteractNpc("Trade", npc, success = Condition.InterfaceOpen("shop"))) + var remaining = amount + while (remaining > 0) { + val amount = when { + remaining >= 500 -> 500 + remaining >= 50 -> 50 + remaining >= 10 -> 10 + remaining >= 5 -> 5 + else -> 1 + } + actions.add(BotAction.InterfaceOption("Buy-$amount", "shop:stock:$id")) + remaining -= amount + } + actions.add(BotAction.CloseInterface) + val spaces = if (player.inventory.stackable(id)) 1 else amount + return Resolver( + id = "buy_from_shop", + weight = 25, + setup = listOf(Condition.Inventory(listOf(Entry(setOf("coins"), min = price * amount), Entry(setOf("empty"), min = spaces)))), + actions = actions, + produces = setOf("item:$id"), + ) + } + } + } + return null + } + + private fun valid(player: Player, item: Item, entry: Entry): Boolean { + if (item.isEmpty()) { + return false + } + if (!entry.ids.contains(item.id)) { + return false + } + if (entry.usable && !player.hasRequirementsToUse(item)) { + return false + } + if (entry.equippable && !player.hasRequirements(item)) { + return false + } + return true + } + + private fun resolveEquipment(player: Player, equipment: Map): Resolver? { + val equipment = equipment.toMutableMap() + var resolver = unequipItems(player, equipment) + if (resolver != null) { + return resolver + } + resolver = equipItems(player, equipment) + if (resolver != null) { + return resolver + } + resolver = resolveInventory(player, equipment.values.toList()) + if (resolver != null) { + return resolver + } + return null + } + + private fun withdraw(player: Player, items: List): Resolver? { + val actions = mutableListOf() + actions.add(BotAction.GoToNearest("bank")) + actions.add(BotAction.InteractObject("Use-quickly", "bank_booth*", success = Condition.InterfaceOpen("bank"))) + actions.add(BotAction.InterfaceOption("Deposit carried items", "bank:carried", success = Condition.Inventory(listOf(Entry(setOf("empty"), min = 28))))) + var found = false + for (entry in items) { + val item = player.bank.items.firstOrNull { item -> valid(player, item, entry) } + if (item == null) { + continue + } + withdraw(actions, entry, item) + found = true + } + if (found) { + actions.add(BotAction.CloseInterface) + return Resolver("withdraw_from_bank", weight = 20, actions = actions) + } + return null + } + + private fun depositItems(player: Player, items: List): Resolver? { + val empty = items.firstOrNull { it.ids.contains("empty") } ?: return null + val spaces = player.inventory.spaces + if (empty.min != null && spaces > empty.min) { + return null + } + val actions = mutableListOf( + BotAction.GoToNearest("bank"), + BotAction.InteractObject("Use-quickly", "bank_booth*", success = Condition.InterfaceOpen("bank")), + BotAction.InterfaceOption("Deposit carried items", "bank:carried", success = Condition.Inventory(listOf(Entry(setOf("empty"), min = 28)))), + BotAction.CloseInterface, + ) + return Resolver("deposit_all_bank", weight = 20, actions = actions) + } + + private fun equipItems(player: Player, equipment: Map): Resolver? { + val actions = mutableListOf() + val produces = mutableSetOf() + for (item in player.inventory.items) { + val slot = item.slot + if (slot == EquipSlot.None) { + continue + } + val entry = equipment[slot] ?: continue + if (!entry.ids.contains(item.id)) { + continue + } + actions.add(BotAction.InterfaceOption("Equip", "inventory:inventory:${item.id}", success = Condition.Equipment(mapOf(slot to entry)))) + produces.add("equipment:${item.id}") + } + if (actions.isNotEmpty()) { + return Resolver("equip_items", weight = 15, actions = actions, produces = produces) + } + return null + } + + private fun unequipItems(player: Player, equipment: Map): Resolver? { + val actions = mutableListOf() + for ((slot, entry) in equipment) { + if (!entry.ids.contains("empty")) { + continue + } + val item = player.equipped(slot) + if (item.isNotEmpty()) { + actions.add(BotAction.InterfaceOption("Remove", "worn_equipment:${slot.name.lowercase()}_slot:$item")) + } + } + if (actions.isNotEmpty()) { + return Resolver("unequip_items", weight = 10, actions = actions) + } + return null + } + + private fun withdraw(actions: MutableList, entry: Entry, item: Item) { + if (entry.min != null && entry.min > 1) { + actions.add(BotAction.InterfaceOption("Withdraw-X", "bank:inventory:${item.id}")) + actions.add(BotAction.IntEntry(entry.min)) + } else { + actions.add(BotAction.InterfaceOption("Withdraw-1", "bank:inventory:${item.id}")) + } + } +} diff --git a/game/src/main/kotlin/content/bot/behaviour/setup/Resolver.kt b/game/src/main/kotlin/content/bot/behaviour/setup/Resolver.kt new file mode 100644 index 0000000000..37a64a2c43 --- /dev/null +++ b/game/src/main/kotlin/content/bot/behaviour/setup/Resolver.kt @@ -0,0 +1,22 @@ +package content.bot.behaviour.setup + +import content.bot.behaviour.Behaviour +import content.bot.behaviour.Condition +import content.bot.behaviour.action.BotAction +import world.gregs.voidps.engine.timer.toTicks +import java.util.concurrent.TimeUnit + +/** + * A behaviour that can be performed to resolve a requirement of another [Resolver] or [BotAction] + * E.g. buying a pickaxe from a shop, getting items out of the bank, picking up an item off of the floor + * [weight] specifies resolver preference; lower is more likely to be chosen. + */ +data class Resolver( + override val id: String, + val weight: Int, + override val timeout: Int = TimeUnit.MINUTES.toTicks(1), + override val requires: List = emptyList(), + override val setup: List = emptyList(), + override val actions: List = emptyList(), + override val produces: Set = emptySet(), +) : Behaviour diff --git a/game/src/main/kotlin/content/bot/interact/bank/BankBot.kt b/game/src/main/kotlin/content/bot/interact/bank/BankBot.kt deleted file mode 100644 index c6220cd99a..0000000000 --- a/game/src/main/kotlin/content/bot/interact/bank/BankBot.kt +++ /dev/null @@ -1,142 +0,0 @@ -package content.bot.interact.bank - -import content.bot.* -import content.bot.bot -import content.bot.interact.navigation.await -import content.bot.interact.navigation.cancel -import content.bot.interact.navigation.goToNearest -import content.bot.interact.navigation.resume -import content.bot.isBot -import content.entity.player.bank.bank -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.menu -import world.gregs.voidps.engine.data.definition.ItemDefinitions -import world.gregs.voidps.engine.inv.equipment -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.EnterInt -import world.gregs.voidps.network.client.instruction.InteractInterface - -private fun getItemId(id: String): Int? = ItemDefinitions.getOrNull(id)?.id - -suspend fun Bot.openBank() { - if (player.menu == "bank") { - return - } - goToNearest("bank") - val bank = getObject { it.def.containsOption(1, "Use-quickly") } ?: return cancel() - objectOption(bank, "Use-quickly") - await("bank") -} - -suspend fun Bot.depositAll() { - if (player.inventory.isEmpty()) { - return - } - clickInterface(762, 33, 0) - await("tick") - await("tick") -} - -suspend fun Bot.depositWornItems() { - if (player.equipment.isEmpty()) { - return - } - clickInterface(762, 35, 0) - await("tick") - await("tick") -} - -suspend fun Bot.depositAll(item: String, slot: Int = player.inventory.indexOf(item)) { - if (slot == -1) { - return - } - player.instructions.send(InteractInterface(interfaceId = 763, componentId = 0, itemId = getItemId(item) ?: return, itemSlot = slot, option = 5)) - await("tick") -} - -suspend fun Bot.deposit(item: String, slot: Int = player.inventory.indexOf(item), amount: Int = 1) { - if (slot == -1) { - return - } - val option = when (amount) { - 1 -> 0 - 5 -> 1 - 10 -> 2 - else -> 4 - } - player.instructions.send(InteractInterface(interfaceId = 763, componentId = 0, itemId = getItemId(item) ?: return, itemSlot = slot, option = option)) - if (option == 4) { - await("tick") - player.instructions.send(EnterInt(value = amount)) - } - await("tick") -} - -suspend fun Bot.withdraw(item: String, slot: Int = player.bank.indexOf(item), amount: Int = 1) { - if (slot == -1) { - return - } - val option = when (amount) { - 1 -> 0 - 5 -> 1 - 10 -> 2 - else -> 4 - } - player.instructions.send(InteractInterface(interfaceId = 762, componentId = 93, itemId = getItemId(item) ?: return, itemSlot = slot, option = option)) - if (option == 4) { - await("tick") - player.instructions.send(EnterInt(value = amount)) - } - await("tick") -} - -suspend fun Bot.withdrawAll(vararg items: String) { - var open = false - for (item in items) { - if (player.bank.contains(item) && !open) { - openBank() - open = true - } - withdrawAll(item) - } - if (open) { - closeBank() - } -} - -suspend fun Bot.withdrawAll(item: String, slot: Int = player.bank.indexOf(item)) { - if (slot == -1) { - return - } - player.instructions.send(InteractInterface(interfaceId = 762, componentId = 93, itemId = getItemId(item) ?: return, itemSlot = slot, option = 5)) - await("tick") -} - -suspend fun Bot.withdrawAllButOne(item: String, slot: Int = player.bank.indexOf(item)) { - if (slot == -1) { - return - } - player.instructions.send(InteractInterface(interfaceId = 762, componentId = 93, itemId = getItemId(item) ?: return, itemSlot = slot, option = 6)) - await("tick") -} - -suspend fun Bot.closeBank() = closeInterface(762, 43) - -suspend fun Bot.withdrawCoins() { - if (!player.inventory.contains("coins")) { - openBank() - withdrawAllButOne("coins") - closeBank() - } -} - -class BankBot : Script { - - init { - interfaceOpened("bank") { - if (isBot) { - bot.resume("bank") - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/interact/item/FloorItemBot.kt b/game/src/main/kotlin/content/bot/interact/item/FloorItemBot.kt deleted file mode 100644 index 83fa73f73c..0000000000 --- a/game/src/main/kotlin/content/bot/interact/item/FloorItemBot.kt +++ /dev/null @@ -1,22 +0,0 @@ -package content.bot.interact.item - -import content.bot.Bot -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import world.gregs.voidps.engine.entity.item.floor.FloorItem -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.timer.TICKS -import world.gregs.voidps.network.client.instruction.InteractFloorItem - -suspend fun Bot.pickup(floorItem: FloorItem) { - player.instructions.send(InteractFloorItem(floorItem.def.id, floorItem.tile.x, floorItem.tile.y, 2)) - if (player.inventory.isFull()) { - return - } - withTimeoutOrNull(TICKS.toMillis(2)) { - suspendCancellableCoroutine { cont -> - this@pickup["floor_item_job"] = cont - this@pickup["floor_item_hash"] = floorItem.hashCode() - } - } -} diff --git a/game/src/main/kotlin/content/bot/interact/item/PickupBot.kt b/game/src/main/kotlin/content/bot/interact/item/PickupBot.kt deleted file mode 100644 index 0f1d6c5dbf..0000000000 --- a/game/src/main/kotlin/content/bot/interact/item/PickupBot.kt +++ /dev/null @@ -1,24 +0,0 @@ -package content.bot.interact.item - -import content.bot.isBot -import kotlinx.coroutines.CancellableContinuation -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.entity.character.player.Players -import kotlin.coroutines.resume - -class PickupBot : Script { - - init { - floorItemDespawn { - val hash = hashCode() - for (bot in Players) { - if (!bot.isBot || !bot.contains("floor_item_job") || bot["floor_item_hash", -1] != hash) { - continue - } - val job: CancellableContinuation = bot.remove("floor_item_job") ?: return@floorItemDespawn - bot.clear("floor_item_hash") - job.resume(Unit) - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/interact/navigation/GoTo.kt b/game/src/main/kotlin/content/bot/interact/navigation/GoTo.kt deleted file mode 100644 index ea319f52d3..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/GoTo.kt +++ /dev/null @@ -1,130 +0,0 @@ -package content.bot.interact.navigation - -import com.github.michaelbull.logging.InlineLogger -import content.bot.Bot -import content.bot.interact.navigation.graph.Edge -import content.bot.interact.navigation.graph.NavigationGraph -import content.bot.interact.navigation.graph.waypoints -import content.bot.interact.path.* -import content.entity.player.effect.energy.energyPercent -import kotlinx.coroutines.withTimeoutOrNull -import world.gregs.voidps.engine.client.update.view.Viewport.Companion.VIEW_RADIUS -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.entity.character.move.running -import world.gregs.voidps.engine.entity.character.npc.NPCs -import world.gregs.voidps.engine.entity.distanceTo -import world.gregs.voidps.engine.entity.obj.GameObjects -import world.gregs.voidps.engine.get -import world.gregs.voidps.engine.timer.TICKS -import world.gregs.voidps.network.client.instruction.InteractInterface -import world.gregs.voidps.network.client.instruction.InteractNPC -import world.gregs.voidps.network.client.instruction.InteractObject -import world.gregs.voidps.network.client.instruction.Walk -import world.gregs.voidps.type.Tile - -private val logger = InlineLogger() - -suspend fun Bot.goToNearest(tag: String) = goToNearest { it.tags.contains(tag) } - -suspend fun Bot.goToNearest(block: (AreaDefinition) -> Boolean): Boolean { - val current: AreaDefinition? = this["area"] - if (current != null && block.invoke(current)) { - return true - } - val graph: NavigationGraph = get() - val strategy = ConditionalStrategy(graph, block) - val result = goTo(strategy) - val area: AreaDefinition? = strategy.area - assert(result != null) { "Unable to find path." } - assert(area != null) { "Unable to find path target." } - if (result != null && area != null) { - this["area"] = area - return true - } - return false -} - -suspend fun Bot.goToArea(map: AreaDefinition) { - if (map.area.contains(player.tile)) { - return - } - val result = goTo(AreaStrategy(map.area)) - if (result != null) { - this["area"] = map - } else { - throw IllegalStateException("Failed to find path to ${map.name} from ${player.tile}") - } -} - -private suspend fun Bot.goTo(strategy: NodeTargetStrategy): Tile? { - player.waypoints.clear() - if (strategy.reached(player.tile)) { - return player.tile - } - - updateGraph(this) - val result = get().find(player, strategy, EdgeTraversal()) - this["navigating"] = result == null - if (result != null) { - navigate() - } - return result -} - -private fun updateGraph(bot: Bot) { - val graph: NavigationGraph = get() - val edges = graph.get(bot.player) - edges.clear() - graph.nodes.filter { it is Tile && it.within(bot.tile, 20) }.forEach { - val tile = it as Tile - val distance = tile.distanceTo(bot.tile) - edges.add(Edge("", bot, tile, distance, listOf(Walk(tile.x, tile.y)))) - } -} - -private suspend fun Bot.rest() { - val musician = NPCs.firstOrNull { it.tile.within(player.tile, VIEW_RADIUS) && it.def.options.contains("Listen-to") } - if (musician != null && player.tile.distanceTo(musician) < 10) { - player.instructions.send(InteractNPC(npcIndex = 49, option = musician.def.options.indexOfFirst { it == "Listen-to" } + 1)) - repeat(32) { - await("tick") - } - } else { - player.instructions.send(InteractInterface(interfaceId = 750, componentId = 1, itemId = -1, itemSlot = -1, option = 1)) - repeat(50) { - await("tick") - } - } -} - -private suspend fun Bot.run() { - player.instructions.send(InteractInterface(interfaceId = 750, componentId = 1, itemId = -1, itemSlot = -1, option = 0)) -} - -private suspend fun Bot.navigate() { - val waypoints = player.waypoints.toMutableList().iterator() - while (waypoints.hasNext()) { - val waypoint = waypoints.next() - for (step in waypoint.steps) { - if (player.energyPercent() <= 25) { - rest() - } else if (!player.running) { - run() - } - this.step = step - player.instructions.send(step) - val timeout = withTimeoutOrNull(TICKS.toMillis(20)) { - if (step is InteractObject && GameObjects.findOrNull(player.tile.copy(step.x, step.y), step.objectId) == null) { - await("tick") - } else { - await("move") - } - } - if (timeout == null && player["debug", false]) { - logger.debug { "Bot $player got stuck at $step $waypoint" } - } - } - waypoints.remove() - } - player["navigating"] = false -} diff --git a/game/src/main/kotlin/content/bot/interact/navigation/Navigation.kt b/game/src/main/kotlin/content/bot/interact/navigation/Navigation.kt deleted file mode 100644 index 1fe35d3ef1..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/Navigation.kt +++ /dev/null @@ -1,85 +0,0 @@ -package content.bot.interact.navigation - -import content.bot.Bot -import content.bot.bot -import content.bot.isBot -import content.entity.obj.door.Door -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.variable.hasClock -import world.gregs.voidps.engine.entity.character.mode.EmptyMode -import world.gregs.voidps.engine.entity.character.mode.interact.Interact -import world.gregs.voidps.engine.entity.character.mode.move.Movement -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.timer.TICKS -import kotlin.coroutines.resume - -suspend fun Bot.await(type: Any, timeout: Int = -1) { - if (timeout > 0) { - withTimeoutOrNull(TICKS.toMillis(timeout)) { - suspendCancellableCoroutine { cont -> - player["suspension"] = type - player["cont"] = cont - } - } - } else { - suspendCancellableCoroutine { cont -> - player["suspension"] = type - player["cont"] = cont - } - } -} - -suspend fun Bot.awaitInteract(timeout: Int = -1) { - await("tick", timeout) - while (player.mode is Interact || player.hasClock("movement_delay")) { - await("tick", timeout) - } -} - -suspend inline fun Bot.await( - noinline condition: Player.() -> Boolean = { true }, -) { - suspendCancellableCoroutine { cont -> - player.getOrPut("bot_new_suspensions") { mutableListOf Boolean, CancellableContinuation>>() }.add(condition to cont) - } -} - -fun Bot.resume(type: Any) = resume(type, Unit) - -fun Bot.resume(type: Any, value: T) { - if (player.get("suspension") == type) { - val cont: CancellableContinuation? = player.remove("cont") - cont?.resume(value) - } -} - -fun Bot.cancel(cause: Throwable? = null) { - val cont: CancellableContinuation<*>? = player.remove("cont") - cont?.cancel(cause) -} - -class Navigation : Script { - - init { - moved { - if (isBot && ((mode is Movement && steps.size <= 1) || mode == EmptyMode)) { - bot.resume("move") - } - } - - Door.opened = { - if (isBot) { - bot.resume("move") - } - } - - objTeleportLand { _, _ -> - if (isBot) { - bot.resume("move") - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/interact/navigation/graph/Condition.kt b/game/src/main/kotlin/content/bot/interact/navigation/graph/Condition.kt deleted file mode 100644 index 4273d6dd27..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/graph/Condition.kt +++ /dev/null @@ -1,7 +0,0 @@ -package content.bot.interact.navigation.graph - -import world.gregs.voidps.engine.entity.character.player.Player - -interface Condition { - fun has(player: Player): Boolean -} diff --git a/game/src/main/kotlin/content/bot/interact/navigation/graph/Edge.kt b/game/src/main/kotlin/content/bot/interact/navigation/graph/Edge.kt deleted file mode 100644 index 88324cd0e3..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/graph/Edge.kt +++ /dev/null @@ -1,23 +0,0 @@ -package content.bot.interact.navigation.graph - -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.name -import world.gregs.voidps.network.client.Instruction -import java.util.* - -data class Edge( - val name: String, - val start: Any, - val end: Any, - val cost: Int, - val steps: List = emptyList(), - val requirements: List = emptyList(), -) : Comparable { - - override fun compareTo(other: Edge): Int = cost.compareTo(other.cost) - - override fun toString(): String = "Edge(${"$name ".trimStart()}${if (start is Player) start.name else start} - $end)" -} - -val Player.waypoints: LinkedList - get() = getOrPut("waypoints") { LinkedList() } diff --git a/game/src/main/kotlin/content/bot/interact/navigation/graph/HasInventoryItem.kt b/game/src/main/kotlin/content/bot/interact/navigation/graph/HasInventoryItem.kt deleted file mode 100644 index 0e327ab2fd..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/graph/HasInventoryItem.kt +++ /dev/null @@ -1,8 +0,0 @@ -package content.bot.interact.navigation.graph - -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.inv.inventory - -class HasInventoryItem(val id: String, val amount: Int) : Condition { - override fun has(player: Player): Boolean = player.inventory.contains(id, amount) -} diff --git a/game/src/main/kotlin/content/bot/interact/navigation/graph/NavigationGraph.kt b/game/src/main/kotlin/content/bot/interact/navigation/graph/NavigationGraph.kt deleted file mode 100644 index b0ac93d99e..0000000000 --- a/game/src/main/kotlin/content/bot/interact/navigation/graph/NavigationGraph.kt +++ /dev/null @@ -1,165 +0,0 @@ -package content.bot.interact.navigation.graph - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import it.unimi.dsi.fastutil.objects.ObjectArrayList -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet -import world.gregs.config.Config -import world.gregs.config.ConfigReader -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.ObjectDefinitions -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.timedLoad -import world.gregs.voidps.network.client.Instruction -import world.gregs.voidps.network.client.instruction.InteractObject -import world.gregs.voidps.network.client.instruction.Walk -import world.gregs.voidps.type.Distance -import world.gregs.voidps.type.Tile - -class NavigationGraph { - - private var adjacencyList: Object2ObjectOpenHashMap> = Object2ObjectOpenHashMap>() - private val tags = Object2ObjectOpenHashMap>() - - val nodes: Set - get() = adjacencyList.keys - val size: Int - get() = adjacencyList.size - - fun contains(node: Any) = adjacencyList.containsKey(node) - - fun getAdjacent(node: Any): Set = adjacencyList.getOrDefault(node, empty) - - fun get(node: Any): ObjectOpenHashSet = adjacencyList.getOrPut(node) { ObjectOpenHashSet() } - - fun areas(node: Any): Set = tags[node] ?: emptyTags - - fun add(node: Any, set: ObjectOpenHashSet) { - adjacencyList[node] = set - } - - fun remove(node: Any) { - adjacencyList.remove(node) - } - - fun load(path: String): NavigationGraph { - timedLoad("ai nav graph edge") { - val map = Object2ObjectOpenHashMap>() - var count = 0 - Config.fileReader(path) { - while (nextSection()) { - val name = section() - var from = Tile.EMPTY - var to = Tile.EMPTY - var cost = 0 - val steps = ObjectArrayList() - val conditions = ObjectArrayList() - while (nextPair()) { - when (val key = key()) { - "from" -> from = readTile() - "to" -> to = readTile() - "cost" -> cost = int() - "steps" -> { - while (nextElement()) { - var option = "" - var objectId = "" - var transform: Int? = null - var tile = Tile.EMPTY - while (nextEntry()) { - when (val stepKey = key()) { - "option" -> option = string() - "object" -> objectId = string() - "transform" -> transform = int() - "tile" -> tile = readTile() - else -> throw IllegalArgumentException("Unexpected key: '$stepKey' ${exception()}") - } - } - val instruction = when { - objectId != "" -> { - var def = ObjectDefinitions.getOrNull(objectId) ?: continue - if (transform != null) { - val id = def.transforms?.get(transform) ?: continue - def = ObjectDefinitions.getOrNull(id) ?: continue - } - val optionIndex = def.optionsIndex(option) + 1 - InteractObject(def.id, tile.x, tile.y, optionIndex) - } - else -> Walk(tile.x, tile.y) - } - steps.add(instruction) - } - } - "conditions" -> { - while (nextElement()) { - require(nextEntry()) { "Expected condition type. ${exception()}" } - require(key() == "type") { "Expected condition type. ${exception()}" } - when (val type = string()) { - "inventory_item" -> { - var item = "" - var amount = 0 - while (nextEntry()) { - when (val k = key()) { - "item" -> item = string() - "amount" -> amount = int() - else -> throw IllegalArgumentException("Unexpected key: '$k' ${exception()}") - } - } - conditions.add(HasInventoryItem(item, amount)) - } - else -> throw IllegalArgumentException("Unexpected type: '$type' ${exception()}") - } - } - } - else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") - } - } - val walk = steps.isEmpty - if (steps.isEmpty) { - cost = Distance.manhattan(from.x, from.y, to.x, to.y) - steps.add(Walk(to.x, to.y)) - } - count++ - map.getOrPut(from) { ObjectOpenHashSet(1) }.add(Edge(name, from, to, cost, steps, conditions)) - if (walk) { // Bidirectional - map.getOrPut(to) { ObjectOpenHashSet(1) }.add(Edge(name, to, from, cost, listOf(Walk(from.x, from.y)), conditions)) - } - } - } - this.adjacencyList = map - tagAreas() - count - } - return this - } - - private fun tagAreas() { - adjacencyList.forEach { (node, _) -> - val tile = when (node) { - is Tile -> node - is GameObject -> node.tile - else -> return@forEach - } - tags[node] = Areas.getAll().filter { it.area.contains(tile) }.toSet() - } - } - - companion object { - private val empty = emptySet() - private val emptyTags = emptySet() - } -} - -fun ConfigReader.readTile(): Tile { - var x = 0 - var y = 0 - var level = 0 - while (nextEntry()) { - when (val key = key()) { - "x" -> x = int() - "y" -> y = int() - "level" -> level = int() - else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") - } - } - return Tile(x, y, level) -} diff --git a/game/src/main/kotlin/content/bot/interact/path/AreaStrategy.kt b/game/src/main/kotlin/content/bot/interact/path/AreaStrategy.kt deleted file mode 100644 index a4983c511e..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/AreaStrategy.kt +++ /dev/null @@ -1,11 +0,0 @@ -package content.bot.interact.path - -import world.gregs.voidps.type.Area -import world.gregs.voidps.type.Tile - -class AreaStrategy( - val area: Area, -) : NodeTargetStrategy() { - - override fun reached(node: Any): Boolean = node is Tile && node in area -} diff --git a/game/src/main/kotlin/content/bot/interact/path/ConditionalStrategy.kt b/game/src/main/kotlin/content/bot/interact/path/ConditionalStrategy.kt deleted file mode 100644 index 2ec472ed01..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/ConditionalStrategy.kt +++ /dev/null @@ -1,26 +0,0 @@ -package content.bot.interact.path - -import content.bot.interact.navigation.graph.NavigationGraph -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.type.Tile - -class ConditionalStrategy( - val graph: NavigationGraph, - val block: (AreaDefinition) -> Boolean, -) : NodeTargetStrategy() { - - var area: AreaDefinition? = null - - override fun reached(node: Any): Boolean { - if (node !is Tile) { - return false - } - for (area in graph.areas(node)) { - if (block(area)) { - this.area = area - return true - } - } - return false - } -} diff --git a/game/src/main/kotlin/content/bot/interact/path/Dijkstra.kt b/game/src/main/kotlin/content/bot/interact/path/Dijkstra.kt deleted file mode 100644 index 77d33270a6..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/Dijkstra.kt +++ /dev/null @@ -1,62 +0,0 @@ -package content.bot.interact.path - -import content.bot.interact.navigation.graph.Edge -import content.bot.interact.navigation.graph.NavigationGraph -import content.bot.interact.navigation.graph.waypoints -import content.bot.interact.path.DijkstraFrontier.Companion.MAX_COST -import kotlinx.io.pool.ObjectPool -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.type.Tile -import java.util.* - -class Dijkstra( - private val graph: NavigationGraph, - private val pool: ObjectPool, -) { - - fun find(player: Player, strategy: NodeTargetStrategy, traversal: EdgeTraversal): Tile? { - val frontier = pool.borrow() - frontier.reset(player) - var target: Edge? = null - while (frontier.isNotEmpty()) { - val (parent, parentEdge, parentCost) = frontier.poll() - if (strategy.reached(parent)) { - target = parentEdge - break - } - for (edge in graph.getAdjacent(parent)) { - if (traversal.blocked(player, edge)) { - continue - } - val cost = parentCost + edge.cost - if (frontier.cost(edge) > cost) { - frontier.visit(edge.end, edge, parentEdge, cost) - } - } - } - val result = backtrace(frontier, player.waypoints, player, target) - pool.recycle(frontier) - return result - } - - private fun backtrace(frontier: DijkstraFrontier, waypoints: LinkedList, start: Any, target: Edge?): Tile? { - if (target != null && frontier.cost(target) != MAX_COST) { - var edge: Edge? = target - waypoints.clear() - while (edge != null) { - waypoints.add(0, edge) - if (edge.start == start) { - break - } - edge = frontier.parent(edge) - } - val end = target.end - if (end is Int) { - return Tile(end) - } else if (end is Tile) { - return end - } - } - return null - } -} diff --git a/game/src/main/kotlin/content/bot/interact/path/DijkstraFrontier.kt b/game/src/main/kotlin/content/bot/interact/path/DijkstraFrontier.kt deleted file mode 100644 index 694d92e26b..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/DijkstraFrontier.kt +++ /dev/null @@ -1,47 +0,0 @@ -package content.bot.interact.path - -import content.bot.interact.navigation.graph.Edge -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap -import java.util.* - -/** - * All the graph nodes visited or to be visited by the [Dijkstra] algorithm - */ -class DijkstraFrontier(size: Int) { - private val queue = PriorityQueue() - private val visited = Object2ObjectOpenHashMap>(size + 1) - - fun isNotEmpty() = queue.isNotEmpty() - - fun add(node: Any, edge: Edge, cost: Int) { - queue.add(Weighted(node, edge, cost)) - } - - fun poll(): Triple = queue.poll().let { Triple(it.node, it.edge, it.cost) } - - fun visit(node: Any, edge: Edge, parent: Edge?, cost: Int) { - add(node, edge, cost) - visited[edge] = Pair(parent, cost) - } - - fun cost(edge: Edge): Int { - return visited[edge]?.second ?: return MAX_COST - } - - fun parent(edge: Edge): Edge? = visited[edge]?.first - - fun reset(node: Any) { - queue.clear() - visited.clear() - queue.add(Weighted(node, null, 0)) - } - - private class Weighted(val node: Any, val edge: Edge?, val cost: Int) : Comparable { - - override fun compareTo(other: Weighted): Int = cost.compareTo(other.cost) - } - - companion object { - const val MAX_COST = 0xffff - } -} diff --git a/game/src/main/kotlin/content/bot/interact/path/EdgeTraversal.kt b/game/src/main/kotlin/content/bot/interact/path/EdgeTraversal.kt deleted file mode 100644 index 0c26943fc8..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/EdgeTraversal.kt +++ /dev/null @@ -1,8 +0,0 @@ -package content.bot.interact.path - -import content.bot.interact.navigation.graph.Edge -import world.gregs.voidps.engine.entity.character.player.Player - -class EdgeTraversal { - fun blocked(player: Player, edge: Edge): Boolean = edge.requirements.any { !it.has(player) } -} diff --git a/game/src/main/kotlin/content/bot/interact/path/NodeTargetStrategy.kt b/game/src/main/kotlin/content/bot/interact/path/NodeTargetStrategy.kt deleted file mode 100644 index cceb42951e..0000000000 --- a/game/src/main/kotlin/content/bot/interact/path/NodeTargetStrategy.kt +++ /dev/null @@ -1,5 +0,0 @@ -package content.bot.interact.path - -abstract class NodeTargetStrategy { - abstract fun reached(node: Any): Boolean -} diff --git a/game/src/main/kotlin/content/bot/interact/shop/ShopBot.kt b/game/src/main/kotlin/content/bot/interact/shop/ShopBot.kt deleted file mode 100644 index 2f971e1a4e..0000000000 --- a/game/src/main/kotlin/content/bot/interact/shop/ShopBot.kt +++ /dev/null @@ -1,80 +0,0 @@ -package content.bot.interact.shop - -import content.bot.Bot -import content.bot.bot -import content.bot.closeInterface -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.goToNearest -import content.bot.interact.navigation.resume -import content.bot.isBot -import content.entity.npc.shop.shopInventory -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.update.view.Viewport -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.entity.character.npc.NPC -import world.gregs.voidps.engine.entity.character.npc.NPCs -import world.gregs.voidps.network.client.instruction.InteractInterface -import world.gregs.voidps.network.client.instruction.InteractNPC - -suspend fun Bot.openShop(id: String): NPC = openShop(Areas.getOrNull(id)!!) - -suspend fun Bot.openNearestShop(id: String): Boolean { - val reached = goToNearest { it["items", emptyList()].contains(id) } - openShop() - return reached -} - -suspend fun Bot.openShop(map: AreaDefinition): NPC { - goToArea(map) - return openShop() -} - -private suspend fun Bot.openShop(): NPC { - val shop = NPCs.first { it.tile.within(player.tile, Viewport.VIEW_RADIUS) && it.def.options.contains("Trade") } - player.instructions.send(InteractNPC(npcIndex = shop.index, option = shop.def.options.indexOfFirst { it == "Trade" } + 1)) - await("shop") - return shop -} - -suspend fun Bot.closeShop() = closeInterface(620, 18) - -suspend fun Bot.buy(item: String, amount: Int = 1) { - val shop = player.shopInventory() - val index = shop.indexOf(item) - if (index == -1) { - throw IllegalArgumentException("Shop doesn't contain item '$item'") - } - val slot = index * 6 - var remaining = amount - while (remaining > 0) { - val option = when { - remaining >= 500 -> 5 - remaining >= 50 -> 4 - remaining >= 10 -> 3 - remaining >= 5 -> 2 - else -> 1 - } - player.instructions.send(InteractInterface(interfaceId = 620, componentId = 25, itemId = -1, itemSlot = slot, option = option)) - remaining -= when { - remaining >= 500 -> 500 - remaining >= 50 -> 50 - remaining >= 10 -> 10 - remaining >= 5 -> 5 - else -> 1 - } - } - await("tick") -} - -class ShopBot : Script { - - init { - interfaceOpened("shop") { - if (isBot) { - bot.resume("shop") - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/skill/SkillBot.kt b/game/src/main/kotlin/content/bot/skill/SkillBot.kt deleted file mode 100644 index 767a730eec..0000000000 --- a/game/src/main/kotlin/content/bot/skill/SkillBot.kt +++ /dev/null @@ -1,16 +0,0 @@ -package content.bot.skill - -import content.bot.isBot -import world.gregs.voidps.engine.Script -import world.gregs.voidps.network.client.instruction.InteractDialogue - -class SkillBot : Script { - - init { - interfaceOpened("dialogue_level_up") { - if (isBot) { - instructions.trySend(InteractDialogue(interfaceId = 740, componentId = 3, option = -1)) - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/skill/combat/CombatBot.kt b/game/src/main/kotlin/content/bot/skill/combat/CombatBot.kt deleted file mode 100644 index 7b49b8c05f..0000000000 --- a/game/src/main/kotlin/content/bot/skill/combat/CombatBot.kt +++ /dev/null @@ -1,209 +0,0 @@ -package content.bot.skill.combat - -import content.bot.* -import content.bot.Bot -import content.bot.interact.item.pickup -import content.bot.interact.navigation.await -import content.bot.interact.navigation.cancel -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.entity.combat.attackers -import content.entity.combat.underAttack -import content.entity.death.weightedSample -import content.skill.magic.spell.removeSpellItems -import content.skill.magic.spell.spell -import content.skill.magic.spell.spellBook -import content.skill.slayer.categories -import net.pearx.kasechange.toLowerSpaceCase -import net.pearx.kasechange.toSnakeCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.toIntRange -import world.gregs.voidps.engine.client.update.view.Viewport -import world.gregs.voidps.engine.data.definition.AmmoDefinitions -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.InterfaceDefinitions -import world.gregs.voidps.engine.entity.character.npc.NPC -import world.gregs.voidps.engine.entity.character.npc.NPCs -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.combatLevel -import world.gregs.voidps.engine.entity.character.player.equip.equipped -import world.gregs.voidps.engine.entity.character.player.equip.has -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirements -import world.gregs.voidps.engine.entity.distanceTo -import world.gregs.voidps.engine.entity.item.floor.FloorItems -import world.gregs.voidps.engine.get -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractInterface -import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot -import world.gregs.voidps.type.Tile -import world.gregs.voidps.type.random - -suspend fun Bot.setAttackStyle(skill: Skill) { - setAttackStyle( - when (skill) { - Skill.Strength -> 1 - Skill.Defence -> 3 - else -> 0 - }, - ) -} - -suspend fun Bot.setAutoCast(spell: String) { - val definitions = get() - val def = definitions.get(player.spellBook) - player.instructions.send(InteractInterface(def.id, definitions.getComponentId(player.spellBook, spell) ?: return, -1, -1, 0)) -} - -suspend fun Bot.setAttackStyle(style: Int) { - player.instructions.send(InteractInterface(interfaceId = 884, componentId = style + 11, itemId = -1, itemSlot = -1, option = 0)) -} - -class CombatBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("combat_training")) { - val spaces: Int = area["spaces", 1] - val types = area["npcs", emptyList()].toSet() - val range = area["levels", "1-5"].toIntRange() - val skills = listOf(Skill.Attack, Skill.Strength, Skill.Defence, Skill.Ranged, Skill.Magic).shuffled().take(spaces) - for (skill in skills) { - val task = Task( - name = "train ${skill.name} killing ${types.joinToString(", ")} at ${area.name}".toLowerSpaceCase(), - block = { - while (levels.getMax(skill) < range.last + 1) { - bot.fight(area, skill, types) - } - }, - area = area.area, - spaces = 1, - requirements = listOf( - { levels.getMax(skill) in range }, - { bot.hasExactGear(skill) || bot.hasCoins(2000) }, - ), - ) - tasks.register(task) - } - } - } - - levelChanged(Skill.Constitution, ::eat) - - variableSet("under_attack") { _, _, to -> - if (to == 1 && isBot) { - bot.resume("combat") - } - } - - playerDeath { - if (isBot) { - clear("area") - bot.cancel() - } - } - } - - fun eat(player: Player, skill: Skill, from: Int, to: Int) { - if (player.isBot && player.levels.getPercent(Skill.Constitution) < 50.0) { - val food = player.inventory.items.firstOrNull { it.def.contains("heals") } ?: return - player.bot.inventoryOption(food.id, "Eat") - } - } - - suspend fun Bot.fight(map: AreaDefinition, skill: Skill, races: Set) { - setupGear(skill) - goToArea(map) - setAttackStyle(skill) - while (player.inventory.spaces > 0 && player.isRangedNotOutOfAmmo(skill) && player.isMagicNotOutOfRunes(skill)) { - val targets = NPCs - .filter { isAvailableTarget(map, it, races) } - .map { it to tile.distanceTo(it) } - val target = weightedSample(targets, invert = true) - if (target == null) { - await("tick") - if (player.inventory.spaces < 4) { - break - } - continue - } - npcOption(target, "Attack") - await("combat", timeout = 30) - target.get("death_tile")?.let { - pickupItems(it, 4) - } - equipAmmo(skill) - // TODO on death run back and pickup stuff - } - } - - fun Player.isRangedNotOutOfAmmo(skill: Skill): Boolean { - if (skill != Skill.Ranged) { - return true - } - return has(EquipSlot.Ammo) - } - - fun Player.isMagicNotOutOfRunes(skill: Skill): Boolean { - if (skill != Skill.Magic) { - return true - } - val spell = spell - return removeSpellItems(spell) - } - - suspend fun Bot.pickupItems(tile: Tile, amount: Int) { - repeat(random.nextInt(2, 8)) { - if (player.inventory.contains("bones")) { - inventoryOption("bones", "Bury") - await("tick") - } - await("tick") - } - repeat(amount) { - val item = FloorItems.at(tile).firstOrNull() ?: return@repeat - pickup(item) - } - } - - fun Bot.isAvailableTarget(map: AreaDefinition, npc: NPC, races: Set): Boolean { - if (!npc.tile.within(player.tile, Viewport.VIEW_RADIUS)) { - return false - } - if (player.attackers.isNotEmpty()) { - return player.attackers.contains(npc) - } - if (npc.underAttack) { - return false - } - if (!npc.def.options.contains("Attack")) { - return false - } - if (!races.contains(npc.def.name.toSnakeCase()) && npc.categories.none { races.contains(it) }) { - return false - } - if (!map.area.contains(npc.tile)) { - return false - } - val difference = npc.def.combat - player.combatLevel - return difference < 5 - } - - fun Bot.equipAmmo(skill: Skill) { - if (skill == Skill.Ranged) { - val ammo = player.equipped(EquipSlot.Ammo) - if (ammo.isEmpty()) { - val weapon = player.equipped(EquipSlot.Weapon) - val ammoDefinitions: AmmoDefinitions = get() - player.inventory.items - .firstOrNull { player.hasRequirements(it) && ammoDefinitions.get(weapon.def["ammo_group", ""]).items.contains(it.id) } - ?.let { - equip(it.id) - } - } else if (player.inventory.contains(ammo.id)) { - equip(ammo.id) - } - } - } -} diff --git a/game/src/main/kotlin/content/bot/skill/combat/CombatGear.kt b/game/src/main/kotlin/content/bot/skill/combat/CombatGear.kt deleted file mode 100644 index 7be88463b8..0000000000 --- a/game/src/main/kotlin/content/bot/skill/combat/CombatGear.kt +++ /dev/null @@ -1,143 +0,0 @@ -package content.bot.skill.combat - -import content.bot.Bot -import content.bot.buyItem -import content.bot.equip -import content.bot.interact.bank.* -import content.bot.interact.navigation.await -import content.entity.player.bank.bank -import content.entity.player.bank.ownsItem -import kotlinx.coroutines.CancellationException -import world.gregs.voidps.engine.data.config.GearDefinition -import world.gregs.voidps.engine.data.definition.GearDefinitions -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirements -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirementsToUse -import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.get -import world.gregs.voidps.engine.inv.inventory - -suspend fun Bot.setupGear(gear: GearDefinition, buy: Boolean = true) { - openBank() - depositAll() - if (gear.equipment.isNotEmpty()) { - depositWornItems() - } - setupGearAndInv(gear, buy) -} - -suspend fun Bot.setupGear(skill: Skill, buy: Boolean = true) { - val gear = getGear(skill) ?: return - setupGear(gear, buy) -} - -fun Bot.getGear(skill: Skill): GearDefinition? { - val style = when (skill) { - Skill.Attack, Skill.Strength, Skill.Defence -> "melee" - else -> skill.name.lowercase() - } - return getGear(style, skill) -} - -fun Bot.getGear(type: String, skill: Skill): GearDefinition? { - val setups = get().get(type) - val level = player.levels.getMax(skill) - return setups - .filter { it.levels.contains(level) } - .sortedWith(compareBy({ player.gearScore(it) }, { it.inventory.size + it.equipment.size })) - .lastOrNull() -} - -fun Bot.getSuitableItem(items: List): Item = items.first { item -> player.hasRequirements(item) && player.ownsItem(item.id, item.amount) } - -private fun Player.gearScore(definition: GearDefinition): Double { - val total = definition.inventory.size + definition.equipment.size - if (total <= 0) { - return 0.0 - } - var count = 0 - for (items in definition.inventory) { - if (items.any { item -> hasRequirements(item) && ownsItem(item.id, item.amount) }) { - count++ - } - } - for ((_, equipment) in definition.equipment) { - if (equipment.any { item -> hasRequirements(item) && ownsItem(item.id, item.amount) }) { - count++ - } - } - return count / total.toDouble() -} - -fun Bot.hasExactGear(skill: Skill): Boolean { - val gear = getGear(skill) - if (gear != null) { - return hasExactGear(gear) - } - return false -} - -fun Bot.hasExactGear(type: String, skill: Skill): Boolean { - val gear = getGear(type, skill) - if (gear != null) { - return hasExactGear(gear) - } - return false -} - -fun Bot.hasExactGear(gear: GearDefinition): Boolean = player.gearScore(gear) == 1.0 - -private suspend fun Bot.setupGearAndInv(gear: GearDefinition, buy: Boolean) { - // Pick one of each item to equip for each required slot - for ((_, equipmentList) in gear.equipment) { - val items = equipmentList - .filter { player.hasRequirements(it) || player.hasRequirementsToUse(it) || player.bank.contains(it.id, it.amount) } - if (items.isEmpty()) { - continue - } - withdrawOrBuy(items, buy) - } - await("tick") - if (!player.interfaces.contains("bank")) { - openBank() - await("tick") - } - for (items in gear.inventory) { - withdrawOrBuy(items, buy) - } - - if (player.inventory.contains("coins")) { - openBank() - depositAll("coins") - } - if (gear.type == "magic") { - setAutoCast(gear["spell"]) - } - closeBank() - - await("tick") - await("tick") - if (!hasExactGear(gear)) { - throw CancellationException("Doesn't have all the gear required.") - } -} - -suspend fun Bot.withdrawOrBuy(items: List, buy: Boolean): Boolean { - for (item in items) { - if (player.bank.contains(item.id, item.amount)) { - withdraw(item.id, amount = item.amount) - equip(item.id) - return true - } - } - if (buy) { - for (item in items) { - if (buyItem(item.id, item.amount)) { - equip(item.id) - return true - } - } - } - return false -} diff --git a/game/src/main/kotlin/content/bot/skill/combat/TrainingBot.kt b/game/src/main/kotlin/content/bot/skill/combat/TrainingBot.kt deleted file mode 100644 index dcc294db9c..0000000000 --- a/game/src/main/kotlin/content/bot/skill/combat/TrainingBot.kt +++ /dev/null @@ -1,180 +0,0 @@ -package content.bot.skill.combat - -import content.bot.* -import content.bot.interact.bank.withdrawAll -import content.bot.interact.navigation.await -import content.bot.interact.navigation.cancel -import content.bot.interact.navigation.goToArea -import content.entity.combat.attackers -import content.entity.combat.attacking -import content.entity.combat.underAttack -import content.entity.player.bank.ownsItem -import content.skill.magic.spell.spellBook -import content.skill.melee.weapon.attackRange -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.dialogue -import world.gregs.voidps.engine.client.update.view.Viewport -import world.gregs.voidps.engine.client.variable.remaining -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.entity.character.mode.interact.PlayerOnObjectInteract -import world.gregs.voidps.engine.entity.character.npc.NPC -import world.gregs.voidps.engine.entity.character.npc.NPCs -import world.gregs.voidps.engine.entity.character.player.equip.has -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.engine.timer.epochSeconds -import world.gregs.voidps.network.login.protocol.visual.update.player.EquipSlot - -class TrainingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - val area = Areas.getOrNull("lumbridge_combat_tutors") ?: return@worldSpawn - val range = 1..5 - val skills = listOf(Skill.Attack, Skill.Magic, Skill.Ranged) - val melees = listOf(Skill.Attack, Skill.Strength, Skill.Defence) - for (skill in skills) { - val melee = skill == Skill.Attack - val task = Task( - name = "train ${if (melee) "melee" else skill.name} at ${area.name}".toLowerSpaceCase(), - block = { - val actualSkill = if (melee) melees.filter { levels.getMax(it) in range }.random() else skill - bot.train(area, actualSkill, range) - }, - area = area.area, - spaces = if (melee) 3 else 2, - requirements = listOf( - { if (melee) melees.any { levels.getMax(it) in range } else levels.getMax(skill) in range }, - { bot.canGetGearAndAmmo(skill) }, - ), - ) - tasks.register(task) - } - } - } - - suspend fun Bot.train(map: AreaDefinition, skill: Skill, range: IntRange) { - setupGear(map, skill) - if (skill == Skill.Magic) { - setAutoCast("wind_strike") - } else { - player.clear("autocast") - setAttackStyle(skill) - } - var target: Any? = null - while (target == null) { - await("tick") - target = if (skill == Skill.Ranged) { - getObjects { it.id == "archery_target" } - .randomOrNull() - } else { - NPCs - .filter { isAvailableTarget(map, it, skill) } - .randomOrNull() - } - } - if (target is NPC) { - if (!player.tile.within(target.tile, player.attackRange + 1)) { - player.walkTo(target.tile) - await("move") - } - } - while (player.levels.getMax(skill) < range.last + 1 && hasAmmo(skill)) { - if (target is GameObject) { - objectOption(target, "Shoot-at") - await { mode is PlayerOnObjectInteract } - await("tick") - } else if (target is NPC) { - npcOption(target, "Attack") - while (player.attacking) { - await("tick") - } - await("tick") - } - } - } - - suspend fun Bot.setupGear(area: AreaDefinition, skill: Skill) { - when (skill) { - Skill.Magic -> { - withdrawAll("air_rune", "mind_rune") - goToArea(area) - if (!player.inventory.contains("air_rune") && !player.inventory.contains("mind_rune")) { - claim("mikasi") - } - if (!player.inventory.contains("air_rune") || !player.inventory.contains("mind_rune")) { - cancel() - return - } - } - Skill.Ranged -> { - withdrawAll("training_bow", "training_arrows") - goToArea(area) - if (!player.inventory.contains("training_bow") || !player.inventory.contains("training_arrows")) { - claim("nemarti") - } - equip("training_bow") - equip("training_arrows") - } - else -> { - withdrawAll("training_sword", "training_shield") - goToArea(area) - if (!player.inventory.contains("training_sword")) { - val tutor = NPCs.first { it.tile.within(player.tile, Viewport.VIEW_RADIUS) && it.id == "harlan" } - npcOption(tutor, "Talk-to") - await { dialogue != null } - await("tick") - dialogueOption("continue") - dialogueOption("line4") - dialogueOption("continue") - dialogueOption("continue") - dialogueOption("continue") - } - equip("training_sword") - equip("training_shield") - } - } - } - - suspend fun Bot.claim(npc: String) { - val tutor = NPCs.first { it.tile.within(player.tile, Viewport.VIEW_RADIUS) && it.id == npc } - npcOption(tutor, "Talk-to") - await { dialogue != null } - await("tick") - dialogueOption("continue") - dialogueOption("line3") - dialogueOption("continue") - dialogueOption("continue") - } - - fun Bot.isAvailableTarget(map: AreaDefinition, npc: NPC, skill: Skill): Boolean { - if (!npc.tile.within(player.tile, Viewport.VIEW_RADIUS)) { - return false - } - if (npc.underAttack && !npc.attackers.contains(player)) { - return false - } - if (!npc.def.options.contains("Attack")) { - return false - } - if (!map.area.contains(npc.tile)) { - return false - } - return npc.id == if (skill == Skill.Magic) "magic_dummy" else "melee_dummy" - } - - fun Bot.canGetGearAndAmmo(skill: Skill): Boolean = when (skill) { - Skill.Magic -> (player.ownsItem("air_rune") && player.ownsItem("mind_rune")) || player.remaining("claimed_tutor_consumables", epochSeconds()) <= 0 && player.spellBook == "modern_spellbook" - Skill.Ranged -> (player.ownsItem("training_bow") && (player.ownsItem("training_arrows")) || player.remaining("claimed_tutor_consumables", epochSeconds()) <= 0) - else -> true - } - - fun Bot.hasAmmo(skill: Skill): Boolean = when (skill) { - Skill.Ranged -> player.has(EquipSlot.Ammo) - Skill.Magic -> player.inventory.contains("air_rune") && player.inventory.contains("mind_rune") - else -> true - } -} diff --git a/game/src/main/kotlin/content/bot/skill/cooking/CookingBot.kt b/game/src/main/kotlin/content/bot/skill/cooking/CookingBot.kt deleted file mode 100644 index c88898239d..0000000000 --- a/game/src/main/kotlin/content/bot/skill/cooking/CookingBot.kt +++ /dev/null @@ -1,93 +0,0 @@ -package content.bot.skill.cooking - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.getGear -import content.bot.skill.combat.getSuitableItem -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.data.config.GearDefinition -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractDialogue -import world.gregs.voidps.network.client.instruction.InteractInterfaceObject - -class CookingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("cooking")) { - val spaces: Int = area["spaces", 1] - val type: String = area.getOrNull("type") ?: "" - val task = Task( - name = "cook on ${type.plural(2)} at ${area.name}".toLowerSpaceCase(), - block = { - val gear = bot.getGear(Skill.Cooking) ?: return@Task - val item = bot.getSuitableItem(gear.inventory.first()) - while (levels.getMax(Skill.Cooking) < gear.levels.last + 1) { - bot.cook(area, item, gear) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf { bot.hasExactGear(Skill.Cooking) }, - ) - tasks.register(task) - } - } - - timerStop("cooking") { - if (isBot) { - bot.resume("cooking") - } - } - } - - suspend fun Bot.cook(map: AreaDefinition, rawItem: Item, set: GearDefinition) { - setupGear(set, buy = false) - goToArea(map) - if (player.inventory.contains(rawItem.id)) { - val range = getObject { isRange(map, it) } - if (range == null) { - await("tick") - return - } - // Use item on range - player.instructions.send(InteractInterfaceObject(range.def.id, range.tile.x, range.tile.y, 149, 0, rawItem.def.id, player.inventory.indexOf(rawItem.id))) - await("tick") - await("tick") - if (rawItem.id == "raw_beef") { - player.instructions.send(InteractDialogue(228, 3, -1)) - await("tick") - } - // Select all - clickInterface(916, 8, 0) - await("tick") - // First option - player.instructions.send(InteractDialogue(905, 14, -1)) - } - var count = 0 - while (player.inventory.contains(rawItem.id)) { - await("cooking") - if (count++ > 28) { - break - } - } - } - - fun isRange(map: AreaDefinition, obj: GameObject): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - return obj.id.startsWith("cooking_range") - } -} diff --git a/game/src/main/kotlin/content/bot/skill/firemaking/FiremakingBot.kt b/game/src/main/kotlin/content/bot/skill/firemaking/FiremakingBot.kt deleted file mode 100644 index d8a665d044..0000000000 --- a/game/src/main/kotlin/content/bot/skill/firemaking/FiremakingBot.kt +++ /dev/null @@ -1,83 +0,0 @@ -package content.bot.skill.firemaking - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.getGear -import content.bot.skill.combat.getSuitableItem -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.entity.obj.GameObjects -import world.gregs.voidps.engine.entity.obj.ObjectLayer -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractInterfaceItem - -class FiremakingBot( - val tasks: TaskManager, -) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("fire_making")) { - val spaces: Int = area["spaces", 1] - val task = Task( - name = "make fires at ${area.name}".toLowerSpaceCase(), - block = { - val gear = bot.getGear(Skill.Firemaking) ?: return@Task - val lighter = bot.getSuitableItem(gear.inventory.first()) - val logs = bot.getSuitableItem(gear.inventory.last()) - while (levels.getMax(Skill.Firemaking) < gear.levels.last + 1) { - bot.light(area, lighter, logs) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf { bot.hasExactGear(Skill.Firemaking) }, - ) - tasks.register(task) - } - } - - timerStop("firemaking") { - if (isBot) { - bot.resume("firemaking") - } - } - } - - suspend fun Bot.light(map: AreaDefinition, lighter: Item, logs: Item) { - setupGear(Skill.Firemaking, buy = false) - goToArea(map) - val lighterIndex = player.inventory.indexOf(lighter.id) - while (player.inventory.contains(logs.id)) { - if (GameObjects.getLayer(player.tile, ObjectLayer.GROUND) != null) { - val spot = player.tile - .toCuboid(1) - .firstOrNull { GameObjects.getLayer(it, ObjectLayer.GROUND) == null } - if (spot == null) { - await("tick") - if (player.inventory.spaces < 4) { - break - } - continue - } - player.queue.clearWeak() - player.walkTo(spot) - await("tick") - } - val logIndex = player.inventory.indexOf(logs.id) - if (logIndex == -1) { - break - } - player.instructions.send(InteractInterfaceItem(lighter.def.id, logs.def.id, lighterIndex, logIndex, 149, 0, 149, 0)) - await("firemaking") - } - } -} diff --git a/game/src/main/kotlin/content/bot/skill/fishing/FishingBot.kt b/game/src/main/kotlin/content/bot/skill/fishing/FishingBot.kt deleted file mode 100644 index 941a79ae53..0000000000 --- a/game/src/main/kotlin/content/bot/skill/fishing/FishingBot.kt +++ /dev/null @@ -1,106 +0,0 @@ -package content.bot.skill.fishing - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import content.entity.death.weightedSample -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.client.update.view.Viewport -import world.gregs.voidps.engine.data.config.GearDefinition -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.GearDefinitions -import world.gregs.voidps.engine.data.definition.ItemDefinitions -import world.gregs.voidps.engine.data.definition.data.Catch -import world.gregs.voidps.engine.data.definition.data.Spot -import world.gregs.voidps.engine.entity.character.npc.NPC -import world.gregs.voidps.engine.entity.character.npc.NPCs -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has -import world.gregs.voidps.engine.entity.distanceTo -import world.gregs.voidps.engine.inv.holdsItem -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractNPC - -class FishingBot( - val tasks: TaskManager, - val gear: GearDefinitions, -) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("fish")) { - val spaces: Int = area["spaces", 1] - val type: String = area.getOrNull("type") ?: continue - val sets = gear.get("fishing").filter { it["spot", ""] == type } - for (set in sets) { - val option = set["action", ""] - val bait = set.inventory.firstOrNull { it.first().amount > 1 }?.first()?.id ?: "none" - val task = Task( - name = "fish ${type.plural(2)} at ${area.name}".toLowerSpaceCase(), - block = { - while (levels.getMax(Skill.Fishing) < set.levels.last + 1) { - bot.fish(area, option, bait, set) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf( - { levels.getMax(Skill.Fishing) in set.levels }, - { bot.hasExactGear(set) || bot.hasCoins(2000) }, - ), - ) - tasks.register(task) - } - } - } - - timerStop("fishing") { - if (isBot) { - bot.resume("fishing") - } - } - } - - suspend fun Bot.fish(map: AreaDefinition, option: String, bait: String, set: GearDefinition) { - setupGear(set) - goToArea(map) - while (player.inventory.spaces > 0 && (bait == "none" || player.holdsItem(bait))) { - val spots = NPCs - .filter { isAvailableSpot(map, it, option, bait) } - .map { it to tile.distanceTo(it) } - val spot = weightedSample(spots, invert = true) - if (spot == null) { - await("tick") - if (player.inventory.spaces < 4) { - break - } - continue - } - player.instructions.send(InteractNPC(spot.index, spot.def.options.indexOf(option) + 1)) - await("fishing") - } - } - - fun Bot.isAvailableSpot(map: AreaDefinition, npc: NPC, option: String, bait: String): Boolean { - if (!npc.tile.within(player.tile, Viewport.VIEW_RADIUS)) { - return false - } - if (!map.area.contains(npc.tile)) { - return false - } - if (!npc.def.options.contains(option)) { - return false - } - val spot: Spot = npc.def.getOrNull("fishing_${option.lowercase()}") ?: return false - val level = spot.bait[bait] - ?.minOf { ItemDefinitions.get(it)["fishing", Catch.EMPTY].level } - ?: return false - return player.has(Skill.Fishing, level, false) - } -} diff --git a/game/src/main/kotlin/content/bot/skill/mining/MiningBot.kt b/game/src/main/kotlin/content/bot/skill/mining/MiningBot.kt deleted file mode 100644 index d5c2310260..0000000000 --- a/game/src/main/kotlin/content/bot/skill/mining/MiningBot.kt +++ /dev/null @@ -1,89 +0,0 @@ -package content.bot.skill.mining - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import content.entity.death.weightedSample -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.client.ui.chat.toIntRange -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.data.Rock -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has -import world.gregs.voidps.engine.entity.distanceTo -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractObject - -class MiningBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("mine")) { - val spaces: Int = area["spaces", 1] - val type = area["rocks", emptyList()].firstOrNull() ?: continue - val range: IntRange = area["levels", "1-5"].toIntRange() - val task = Task( - name = "mine ${type.plural(2)} at ${area.name}".toLowerSpaceCase(), - block = { - while (levels.getMax(Skill.Mining) < range.last + 1) { - bot.mineRocks(area, type) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf( - { levels.getMax(Skill.Mining) in range }, - { bot.hasExactGear(Skill.Woodcutting) || bot.hasCoins(1000) }, - ), - ) - tasks.register(task) - } - } - - timerStop("mining") { - if (isBot) { - bot.resume("mining") - } - } - } - - suspend fun Bot.mineRocks(map: AreaDefinition, type: String) { - setupGear(Skill.Mining) - goToArea(map) - while (player.inventory.spaces > 0) { - val rocks = getObjects { isAvailableRock(map, it, type) } - .map { rock -> rock to tile.distanceTo(rock) } - val rock = weightedSample(rocks, invert = true) - if (rock == null) { - await("tick") - if (player.inventory.spaces < 4) { - break - } - continue - } - player.instructions.send(InteractObject(rock.def.id, rock.tile.x, rock.tile.y, 1)) - await("mining") - } - } - - fun Bot.isAvailableRock(map: AreaDefinition, obj: GameObject, type: String): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - if (!obj.def.containsOption("Mine")) { - return false - } - if (!obj.id.contains(type)) { - return false - } - val rock: Rock = obj.def.getOrNull("mining") ?: return false - return player.has(Skill.Mining, rock.level, false) - } -} diff --git a/game/src/main/kotlin/content/bot/skill/runecrafting/RunecraftingBot.kt b/game/src/main/kotlin/content/bot/skill/runecrafting/RunecraftingBot.kt deleted file mode 100644 index 2b786a190a..0000000000 --- a/game/src/main/kotlin/content/bot/skill/runecrafting/RunecraftingBot.kt +++ /dev/null @@ -1,60 +0,0 @@ -package content.bot.skill.runecrafting - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.awaitInteract -import content.bot.interact.navigation.goToArea -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.toIntRange -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.network.client.instruction.InteractObject - -class RunecraftingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("altar")) { - val type: String = area["type"] - val spaces: Int = area["spaces", 1] - val range: IntRange = area["levels", "1-5"].toIntRange() - val task = Task( - name = "craft $type runes at ${area.name}", - block = { - while (levels.getMax(Skill.Runecrafting) < range.last + 1) { - bot.craftRunes(area) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf( - { levels.getMax(Skill.Runecrafting) in range }, - { bot.hasExactGear(Skill.Runecrafting) }, - ), - ) - tasks.register(task) - } - } - } - - suspend fun Bot.craftRunes(map: AreaDefinition) { - setupGear(Skill.Runecrafting) - goToArea(map) - await("tick") - val altar = getObjects { isAltar(map, it) } - .first() - player.instructions.send(InteractObject(altar.def.id, altar.tile.x, altar.tile.y, 1)) - awaitInteract() - } - - fun isAltar(map: AreaDefinition, obj: GameObject): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - return obj.def.containsOption("Craft-rune") - } -} diff --git a/game/src/main/kotlin/content/bot/skill/smithing/SmeltingBot.kt b/game/src/main/kotlin/content/bot/skill/smithing/SmeltingBot.kt deleted file mode 100644 index c849b78d5a..0000000000 --- a/game/src/main/kotlin/content/bot/skill/smithing/SmeltingBot.kt +++ /dev/null @@ -1,95 +0,0 @@ -package content.bot.skill.smithing - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.getGear -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import content.skill.smithing.oreToBar -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.data.config.GearDefinition -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.ItemDefinitions -import world.gregs.voidps.engine.entity.character.mode.interact.Interact -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractDialogue - -class SmeltingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("smelting")) { - val spaces: Int = area["spaces", 1] - val task = Task( - name = "smelt bars at ${area.name}".toLowerSpaceCase(), - block = { - val gear = bot.getGear("smelting", Skill.Smithing) ?: return@Task - while (levels.getMax(Skill.Smithing) < gear.levels.last + 1) { - bot.smelt(area, gear) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf { bot.hasExactGear("smelting", Skill.Smithing) }, - ) - tasks.register(task) - } - } - - timerStop("smelting") { - if (isBot) { - bot.resume("smelting") - } - } - } - - suspend fun Bot.smelt(map: AreaDefinition, set: GearDefinition) { - setupGear(set, buy = false) - goToArea(map) - val furnace = getObject { isFurnace(map, it) } - if (furnace == null) { - await("tick") - return - } - val ore = player.inventory.items.first { it.id.endsWith("_ore") } - var bar = oreToBar(ore.id) - if (bar == "iron_bar" && player.inventory.contains("coal")) { - bar = "steel_bar" - } - val barId = ItemDefinitions.get(bar).id - await("tick") - while (player.inventory.contains(ore.id)) { - itemOnObject(ore, furnace) - await("tick") - while (player.mode is Interact) { - await("tick") - } - // Select All - clickInterface(916, 8) - // Make All - var index = 0 - for (i in 0 until 10) { - val id: Int = player["skill_creation_item_$i"] ?: continue - if (id == barId) { - index = i - break - } - } - player.instructions.send(InteractDialogue(905, 14 + index, -1)) - await("smelting") - } - } - - fun isFurnace(map: AreaDefinition, obj: GameObject): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - return obj.id.startsWith("furnace") - } -} diff --git a/game/src/main/kotlin/content/bot/skill/smithing/SmithingBot.kt b/game/src/main/kotlin/content/bot/skill/smithing/SmithingBot.kt deleted file mode 100644 index 6a65ba5426..0000000000 --- a/game/src/main/kotlin/content/bot/skill/smithing/SmithingBot.kt +++ /dev/null @@ -1,92 +0,0 @@ -package content.bot.skill.smithing - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.getGear -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import net.pearx.kasechange.toLowerSpaceCase -import world.gregs.voidps.cache.definition.data.InterfaceDefinition -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.data.config.GearDefinition -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.InterfaceDefinitions -import world.gregs.voidps.engine.data.definition.ItemDefinitions -import world.gregs.voidps.engine.data.definition.data.Smithing -import world.gregs.voidps.engine.entity.character.mode.interact.Interact -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory - -class SmithingBot( - val interfaceDefinitions: InterfaceDefinitions, - val tasks: TaskManager, -) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("smithing")) { - val spaces: Int = area["spaces", 1] - val task = Task( - name = "smith on anvil at ${area.name}".toLowerSpaceCase(), - block = { - val gear = bot.getGear(Skill.Smithing) ?: return@Task - val types: List = gear.getOrNull("types") ?: return@Task - while (levels.getMax(Skill.Smithing) < gear.levels.last + 1) { - bot.smith(area, types, gear) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf { bot.hasExactGear(Skill.Smithing) }, - ) - tasks.register(task) - } - } - - timerStop("smithing") { - if (isBot) { - bot.resume("smithing") - } - } - } - - suspend fun Bot.smith(map: AreaDefinition, types: List, set: GearDefinition) { - setupGear(set, buy = false) - goToArea(map) - val anvil = getObject { isAnvil(map, it) } - if (anvil == null) { - await("tick") - return - } - val bar = player.inventory.items.first { it.id.endsWith("_bar") } - val type = types.filter { player.has(Skill.Smithing, ItemDefinitions.get(bar.id.replace("_bar", "_$it")).getOrNull("smithing")?.level ?: Int.MAX_VALUE) }.random() - await("tick") - while (player.inventory.contains(bar.id)) { - itemOnObject(bar, anvil) - await("tick") - while (player.mode is Interact) { - await("tick") - } - // Make All - item - val component = interfaceDefinitions.getComponent("smithing", "${type}_all")!! - val bars = component.getOrNull("bars") ?: 1 - if (!player.inventory.contains(bar.id, bars)) { - break - } - clickInterface(300, InterfaceDefinition.componentId(component.id)) - await("smithing") - } - } - - fun isAnvil(map: AreaDefinition, obj: GameObject): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - return obj.id.startsWith("anvil") - } -} diff --git a/game/src/main/kotlin/content/bot/skill/woodcutting/WoodcuttingBot.kt b/game/src/main/kotlin/content/bot/skill/woodcutting/WoodcuttingBot.kt deleted file mode 100644 index 2227c3ce19..0000000000 --- a/game/src/main/kotlin/content/bot/skill/woodcutting/WoodcuttingBot.kt +++ /dev/null @@ -1,88 +0,0 @@ -package content.bot.skill.woodcutting - -import content.bot.* -import content.bot.interact.navigation.await -import content.bot.interact.navigation.goToArea -import content.bot.interact.navigation.resume -import content.bot.skill.combat.hasExactGear -import content.bot.skill.combat.setupGear -import content.entity.death.weightedSample -import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.client.ui.chat.plural -import world.gregs.voidps.engine.client.ui.chat.toIntRange -import world.gregs.voidps.engine.data.definition.AreaDefinition -import world.gregs.voidps.engine.data.definition.Areas -import world.gregs.voidps.engine.data.definition.data.Tree -import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has -import world.gregs.voidps.engine.entity.distanceTo -import world.gregs.voidps.engine.entity.obj.GameObject -import world.gregs.voidps.engine.inv.inventory -import world.gregs.voidps.network.client.instruction.InteractObject - -class WoodcuttingBot(val tasks: TaskManager) : Script { - - init { - worldSpawn { - for (area in Areas.tagged("trees")) { - val spaces: Int = area["spaces", 1] - val range: IntRange = area["levels", "1-5"].toIntRange() - val type = area["trees", emptyList()].firstOrNull() - val task = Task( - name = "cut ${(type ?: "tree").plural(2).lowercase()} at ${area.name}", - block = { - while (levels.getMax(Skill.Woodcutting) < range.last + 1) { - bot.cutTrees(area, type) - } - }, - area = area.area, - spaces = spaces, - requirements = listOf( - { levels.getMax(Skill.Woodcutting) in range }, - { bot.hasExactGear(Skill.Woodcutting) || bot.hasCoins(1000) }, - ), - ) - tasks.register(task) - } - } - - timerStop("woodcutting") { - if (isBot) { - bot.resume("woodcutting") - } - } - } - - suspend fun Bot.cutTrees(map: AreaDefinition, type: String? = null) { - setupGear(Skill.Woodcutting) - goToArea(map) - while (player.inventory.spaces > 0) { - val trees = getObjects { isAvailableTree(map, it, type) } - .map { tree -> tree to tile.distanceTo(tree) } - val tree = weightedSample(trees, invert = true) - if (tree == null) { - await("tick") - if (player.inventory.spaces < 4) { - break - } - continue - } - player.instructions.send(InteractObject(tree.def.id, tree.tile.x, tree.tile.y, 1)) - await("woodcutting") - } - } - - fun Bot.isAvailableTree(map: AreaDefinition, obj: GameObject, type: String?): Boolean { - if (!map.area.contains(obj.tile)) { - return false - } - if (!obj.def.containsOption("Chop down")) { - return false - } - if (type != null && !obj.id.contains(type)) { - return false - } - val tree: Tree = obj.def.getOrNull("woodcutting") ?: return false - return player.has(Skill.Woodcutting, tree.level, false) - } -} diff --git a/game/src/main/kotlin/content/entity/npc/Sheep.kt b/game/src/main/kotlin/content/entity/npc/Sheep.kt index c3c7ed82fa..5f6ec4ed35 100644 --- a/game/src/main/kotlin/content/entity/npc/Sheep.kt +++ b/game/src/main/kotlin/content/entity/npc/Sheep.kt @@ -15,7 +15,7 @@ import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.queue.softQueue import world.gregs.voidps.engine.timer.Timer @@ -64,7 +64,7 @@ class Sheep : Script { } private suspend fun Player.shear(target: NPC, colour: String) { - if (!holdsItem("shears")) { + if (!carriesItem("shears")) { message("You need a set of shears to do this.") return } diff --git a/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt b/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt index 1afede665d..c82aa0f465 100644 --- a/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt +++ b/game/src/main/kotlin/content/entity/npc/shop/stock/Price.kt @@ -53,4 +53,23 @@ object Price { } return max(price, 1) } + + fun of(item: String, currency: String = "coins"): Int { + val itemId = getRealItem(item) + val enums = get() + var price = enums.get("price_runes").getInt(itemId) + if (currency == "tokkul" && price != -1 && price > 0) { + return price + } + price = enums.get("price_garden").getInt(itemId) + if (price != -1 && price > 0) { + return price + } + val def = ItemDefinitions.get(itemId) + if (def.contains("skill_cape") || def.contains("skill_cape_t")) { + return 99000 + } + price = def.cost + return max(price, 1) + } } diff --git a/game/src/main/kotlin/content/entity/obj/MilkCow.kt b/game/src/main/kotlin/content/entity/obj/MilkCow.kt index 8e335ade62..400f0cc7ea 100644 --- a/game/src/main/kotlin/content/entity/obj/MilkCow.kt +++ b/game/src/main/kotlin/content/entity/obj/MilkCow.kt @@ -11,7 +11,7 @@ import content.quest.quest import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.entity.character.sound -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.replace @@ -19,14 +19,14 @@ class MilkCow : Script { init { objectOperate("Milk", "prized_dairy_cow") { - if (!holdsItem("bucket")) { + if (!carriesItem("bucket")) { message("You'll need an empty bucket to collect the milk.") return@objectOperate } if (quest("cooks_assistant") != "started") { statement("If you're after ordinary milk, you should use an ordinary dairy cow.") } - if (holdsItem("top_quality_milk") || bank.contains("top_quality_milk")) { + if (carriesItem("top_quality_milk") || bank.contains("top_quality_milk")) { message("You've already got some top-quality milk; you should take it to the cook.") return@objectOperate } @@ -38,7 +38,7 @@ class MilkCow : Script { } objectOperate("Milk", "dairy_cow") { - if (holdsItem("bucket")) { + if (carriesItem("bucket")) { anim("milk_cow") sound("milk_cow") delay(5) diff --git a/game/src/main/kotlin/content/entity/obj/Mill.kt b/game/src/main/kotlin/content/entity/obj/Mill.kt index da5437971e..248d108f88 100644 --- a/game/src/main/kotlin/content/entity/obj/Mill.kt +++ b/game/src/main/kotlin/content/entity/obj/Mill.kt @@ -51,7 +51,7 @@ class Mill : Script { player("Hmm. I should probably ask that lady downstairs how I can make extra fine flour.") return@itemOnObjectOperate } - if (holdsItem("extra_fine_flour")) { + if (carriesItem("extra_fine_flour")) { message("It'd be best to take the extra fine flour you already have to the cook first.") return@itemOnObjectOperate } @@ -70,13 +70,13 @@ class Mill : Script { } objectOperate("Take-flour", "flour_bin") { - if (!holdsItem("empty_pot")) { + if (!carriesItem("empty_pot")) { message("You need an empty pot to hold the flour in.") return@objectOperate } if (quest("cooks_assistant") == "started" && get("cooks_assistant_talked_to_millie", 0) == 1) { inventory.remove("empty_pot") - if (holdsItem("extra_fine_flour") || bank.contains("extra_fine_flour")) { + if (carriesItem("extra_fine_flour") || bank.contains("extra_fine_flour")) { inventory.add("pot_of_flour") message("You fill a pot with flour from the bin.") } else { diff --git a/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt b/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt index eebdab7326..ef3adc1851 100644 --- a/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt +++ b/game/src/main/kotlin/content/entity/obj/ObjectTeleports.kt @@ -1,6 +1,6 @@ package content.entity.obj -import content.bot.interact.navigation.graph.readTile +import content.bot.behaviour.navigation.NavigationGraph.Companion.readTile import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import world.gregs.config.Config diff --git a/game/src/main/kotlin/content/entity/player/bank/Bank.kt b/game/src/main/kotlin/content/entity/player/bank/Bank.kt index 0249ff3d72..ea2b2ef16c 100644 --- a/game/src/main/kotlin/content/entity/player/bank/Bank.kt +++ b/game/src/main/kotlin/content/entity/player/bank/Bank.kt @@ -4,7 +4,7 @@ import world.gregs.voidps.engine.data.definition.ItemDefinitions import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.item.Item import world.gregs.voidps.engine.inv.Inventory -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem object Bank { private const val TAB_COUNT = 8 @@ -55,9 +55,9 @@ object Bank { val Player.bank: Inventory get() = inventories.inventory("bank") -fun Player.ownsItem(id: String) = holdsItem(id) || bank.contains(id) +fun Player.ownsItem(id: String) = carriesItem(id) || bank.contains(id) -fun Player.ownsItem(id: String, amount: Int) = holdsItem(id, amount) || bank.contains(id, amount) +fun Player.ownsItem(id: String, amount: Int) = carriesItem(id, amount) || bank.contains(id, amount) val Item.isNote: Boolean get() = def.notedTemplateId != -1 diff --git a/game/src/main/kotlin/content/entity/player/command/MetaCommands.kt b/game/src/main/kotlin/content/entity/player/command/MetaCommands.kt index e43d47dc1f..287a7f0704 100644 --- a/game/src/main/kotlin/content/entity/player/command/MetaCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/MetaCommands.kt @@ -224,9 +224,6 @@ class MetaCommands( } } } - for (line in list) { - println(line) - } player.questJournal("Commands List", list) } diff --git a/game/src/main/kotlin/content/entity/player/command/PathFindingCommands.kt b/game/src/main/kotlin/content/entity/player/command/PathFindingCommands.kt index 81aa1392b1..f3fc4293c2 100644 --- a/game/src/main/kotlin/content/entity/player/command/PathFindingCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/PathFindingCommands.kt @@ -1,8 +1,13 @@ package content.entity.player.command -import content.bot.interact.path.Dijkstra -import content.bot.interact.path.EdgeTraversal -import content.bot.interact.path.NodeTargetStrategy +import content.bot.Bot +import content.bot.BotManager +import content.bot.behaviour.BehaviourFrame +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.navigation.NavigationGraph +import content.bot.behaviour.setup.Resolver +import content.bot.bot +import content.bot.isBot import content.entity.gfx.areaGfx import org.rsmod.game.pathfinder.PathFinder import org.rsmod.game.pathfinder.StepValidator @@ -11,6 +16,7 @@ import org.rsmod.game.pathfinder.flag.CollisionFlag import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.command.adminCommand import world.gregs.voidps.engine.client.command.stringArg +import world.gregs.voidps.engine.data.definition.Areas import world.gregs.voidps.engine.data.definition.PatrolDefinitions import world.gregs.voidps.engine.entity.character.mode.Patrol import world.gregs.voidps.engine.entity.character.move.tele @@ -118,37 +124,27 @@ class PathFindingCommands(val patrols: PatrolDefinitions) : Script { // println(pf.findPath(3205, 3220, 3205, 3223, 2)) } - adminCommand("walk_to_bank") { - val east = Tile(3179, 3433).toCuboid(15, 14) - val west = Tile(3250, 3417).toCuboid(7, 8) - val dijkstra: Dijkstra = get() - val strategy = object : NodeTargetStrategy() { - override fun reached(node: Any): Boolean = if (node is Tile) east.contains(node) || west.contains(node) else false + adminCommand("go_to", stringArg("area-id", autofill = Areas.getAll().map { it.stringId }.toSet(), optional = true), desc = "Bot walk to a location") { args -> + val area = args.getOrNull(0) ?: "varrock_teleport" + if (!isBot) { + val manager = get() + val bot = Bot(this) + set("bot", bot) + manager.add(bot) } + bot.queue(BehaviourFrame(Resolver("bot_to_$area", 0, actions = listOf(BotAction.GoTo(area))))) + } + + adminCommand("walk_to_bank") { + val graph: NavigationGraph = get() + val output = mutableListOf() println( "Path took ${ measureNanoTime { - dijkstra.find(this, strategy, EdgeTraversal()) + graph.findNearest(this, output, "bank") } }ns", ) - /*action { FIXME - var first = true - while (waypoints.isNotEmpty()) { - val next = waypoints.poll() - suspendCoroutine { cont -> - val tile = if (first && !tile.within(next.end as Tile, 20)) { - next.start - } else { - next.end - } as Tile - first = false - scheduler.add { - walkTo(tile) - } - } - } - }*/ } timerTick("show_path") { diff --git a/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt b/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt index 38f7db82b3..fd683e9fa4 100644 --- a/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt +++ b/game/src/main/kotlin/content/entity/player/command/ServerCommands.kt @@ -1,6 +1,6 @@ package content.entity.player.command -import content.bot.interact.navigation.graph.NavigationGraph +import content.bot.BotManager import content.entity.obj.ObjectTeleports import content.entity.obj.ship.CharterShips import content.entity.player.modal.book.Books @@ -64,7 +64,7 @@ class ServerCommands(val accountLoader: PlayerAccountLoader) : Script { handler = ::update, ) val configs = setOf( - "books", "teleports", "music_tracks", "fairy_rings", "ships", "objects", "items", "nav_graph", "npcs", "areas", "emotes", "anims", "containers", "graphics", + "books", "teleports", "music_tracks", "fairy_rings", "ships", "objects", "items", "bots", "npcs", "areas", "emotes", "anims", "containers", "graphics", "item_on_item", "sounds", "quests", "midis", "variables", "music", "interfaces", "spells", "patrols", "prayers", "drops", "client_scripts", "settings", ) adminCommand( @@ -94,7 +94,6 @@ class ServerCommands(val accountLoader: PlayerAccountLoader) : Script { ItemDefinitions.load(files.list(Settings["definitions.items"])) loadItemSpawns(itemSpawns, files.list(Settings["spawns.items"])) } - "nav_graph", "ai_graph" -> get().load(files.find(Settings["map.navGraph"])) "npcs" -> { NPCDefinitions.load(files.list(Settings["definitions.npcs"])) loadNpcSpawns(files, reload = true) @@ -129,6 +128,7 @@ class ServerCommands(val accountLoader: PlayerAccountLoader) : Script { Settings.load() SettingsReload.now() } + "bots" -> get().load(files) } } diff --git a/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt b/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt index 4484d0f866..cbb0b07aab 100644 --- a/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt +++ b/game/src/main/kotlin/content/entity/player/dialogue/DialogueInput.kt @@ -2,6 +2,7 @@ package content.entity.player.dialogue import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.instruction.instruction +import world.gregs.voidps.engine.client.sendScript import world.gregs.voidps.engine.client.ui.closeDialogue import world.gregs.voidps.engine.suspend.IntSuspension import world.gregs.voidps.engine.suspend.NameSuspension @@ -44,14 +45,17 @@ class DialogueInput : Script { instruction { player -> (player.dialogueSuspension as? IntSuspension)?.resume(value) + player.sendScript("close_entry") } instruction { player -> (player.dialogueSuspension as? StringSuspension)?.resume(value) + player.sendScript("close_entry") } instruction { player -> (player.dialogueSuspension as? NameSuspension)?.resume(value) + player.sendScript("close_entry") } continueDialogue("dialogue_confirm_destroy:*") { @@ -61,6 +65,7 @@ class DialogueInput : Script { continueDialogue("dialogue_skill_creation:choice*") { val choice = it.substringAfter(":choice").toIntOrNull() ?: 0 (dialogueSuspension as? IntSuspension)?.resume(choice - 1) + closeDialogue() } } } diff --git a/game/src/main/kotlin/content/entity/world/RegionLoading.kt b/game/src/main/kotlin/content/entity/world/RegionLoading.kt index 6f2d51ae90..e9759c6a8d 100644 --- a/game/src/main/kotlin/content/entity/world/RegionLoading.kt +++ b/game/src/main/kotlin/content/entity/world/RegionLoading.kt @@ -1,6 +1,5 @@ package content.entity.world -import content.bot.isBot import world.gregs.voidps.engine.Script import world.gregs.voidps.engine.client.instruction.instruction import world.gregs.voidps.engine.client.update.view.Viewport @@ -112,7 +111,7 @@ class RegionLoading(val dynamicZones: DynamicZones) : Script { if ((dynamic || wasDynamic) && !initial) { viewport.npcs.clear() } - if (!player.isBot) { + if (player.networked) { viewport.loaded = false } viewport.lastLoadZone = player.tile.zone diff --git a/game/src/main/kotlin/content/quest/free/cooks_assistant/CooksAssistant.kt b/game/src/main/kotlin/content/quest/free/cooks_assistant/CooksAssistant.kt index cf236f8185..39c2e90563 100644 --- a/game/src/main/kotlin/content/quest/free/cooks_assistant/CooksAssistant.kt +++ b/game/src/main/kotlin/content/quest/free/cooks_assistant/CooksAssistant.kt @@ -4,7 +4,7 @@ import content.entity.player.bank.bank import content.quest.quest import content.quest.questJournal import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class CooksAssistant : Script { @@ -32,7 +32,7 @@ class CooksAssistant : Script { ) if (get("cooks_assistant_milk", 0) == 1) { list.add("I have given the cook a bucket of top-quality milk.") - } else if (holdsItem("top_quality_milk")) { + } else if (carriesItem("top_quality_milk")) { list.add("I have found a bucket of top-quality milk to give to the cook.") } else if (bank.contains("top_quality_milk")) { list.add("I have a bucket of top-quality milk to give to the cook. it's in my bank.") @@ -42,7 +42,7 @@ class CooksAssistant : Script { if (get("cooks_assistant_flour", 0) == 1) { list.add("I have given the cook a pot of extra fine flour.") - } else if (holdsItem("extra_fine_flour")) { + } else if (carriesItem("extra_fine_flour")) { list.add("I have found a pot of extra fine flour to give to the cook.") } else if (bank.contains("extra_fine_flour")) { list.add("I have a pot of extra fine flour to give to the cook. it's in my bank.") @@ -52,7 +52,7 @@ class CooksAssistant : Script { if (get("cooks_assistant_egg", 0) == 1) { list.add("I have given the cook a super large egg.") - } else if (holdsItem("super_large_egg")) { + } else if (carriesItem("super_large_egg")) { list.add("I have found a super large egg to give to the cook.") } else if (bank.contains("super_large_egg")) { list.add("I have a super large egg to give to the cook. it's in my bank.") diff --git a/game/src/main/kotlin/content/quest/free/gunnars_ground/GunnarsGround.kt b/game/src/main/kotlin/content/quest/free/gunnars_ground/GunnarsGround.kt index aa3c8f740d..979054ba8e 100644 --- a/game/src/main/kotlin/content/quest/free/gunnars_ground/GunnarsGround.kt +++ b/game/src/main/kotlin/content/quest/free/gunnars_ground/GunnarsGround.kt @@ -4,7 +4,7 @@ import content.quest.letterScroll import content.quest.quest import content.quest.questJournal import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class GunnarsGround : Script { @@ -285,7 +285,7 @@ class GunnarsGround : Script { "Gold ring he Specifically wants a ring from Jeffery in Edgeville.", "Items I need:", ) - if (holdsItem("ring_from_jeffery")) { + if (carriesItem("ring_from_jeffery")) { list.add("Ring from Jeffery") } else { list.add("Ring from Jeffery") diff --git a/game/src/main/kotlin/content/quest/free/rune_mysteries/RuneMysteries.kt b/game/src/main/kotlin/content/quest/free/rune_mysteries/RuneMysteries.kt index 12a095b207..8ef7166590 100644 --- a/game/src/main/kotlin/content/quest/free/rune_mysteries/RuneMysteries.kt +++ b/game/src/main/kotlin/content/quest/free/rune_mysteries/RuneMysteries.kt @@ -3,7 +3,7 @@ package content.quest.free.rune_mysteries import content.quest.quest import content.quest.questJournal import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class RuneMysteries : Script { @@ -41,7 +41,7 @@ class RuneMysteries : Script { "Lumbridge, across the bridge from Draynor Village.", ) - if (!holdsItem("air_talisman")) { + if (!carriesItem("air_talisman")) { list.add("If I lose the Strange Talisman , I'll need to ask Duke Horacio for") list.add("another.") } @@ -72,7 +72,7 @@ class RuneMysteries : Script { "Runecrafting. I can find him in his Rune Shop in south east", "Varrock.", ) - if (!holdsItem("research_package_rune_mysteries")) { + if (!carriesItem("research_package_rune_mysteries")) { list.add("If I lose the Package , I'll need to ask Sedridor for") list.add("another.") } @@ -110,7 +110,7 @@ class RuneMysteries : Script { "and asked me to take some Research Notes . back to him. I", "can find Sedridor in the basement of the Wizards' Tower.", ) - if (!holdsItem("research_notes_rune_mysteries")) { + if (!carriesItem("research_notes_rune_mysteries")) { list.add("If I lose the Research Notes I'll need to ask Aubury for") list.add("some more.") } diff --git a/game/src/main/kotlin/content/quest/free/the_knights_sword/TheKnightsSword.kt b/game/src/main/kotlin/content/quest/free/the_knights_sword/TheKnightsSword.kt index a61f9f0168..b7a973516a 100644 --- a/game/src/main/kotlin/content/quest/free/the_knights_sword/TheKnightsSword.kt +++ b/game/src/main/kotlin/content/quest/free/the_knights_sword/TheKnightsSword.kt @@ -4,7 +4,7 @@ import content.entity.player.bank.ownsItem import content.quest.quest import content.quest.questJournal import world.gregs.voidps.engine.Script -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem class TheKnightsSword : Script { @@ -59,7 +59,7 @@ class TheKnightsSword : Script { "until I gave him a Redberry pie, which he gobbled up.", "Thurgo needed a picture of the sword to replace.", ) - if (holdsItem("portrait") || ownsItem("portrait")) { + if (carriesItem("portrait") || ownsItem("portrait")) { list.add("I now have a picture of the Knight's Sword - I should take it") list.add("to Thurgo so that he can duplicate it.") } else { @@ -79,7 +79,7 @@ class TheKnightsSword : Script { "start work on a replacement. I took him a portrait of it.", ) - if (holdsItem("blurite_sword") || ownsItem("blurite_sword")) { + if (carriesItem("blurite_sword") || ownsItem("blurite_sword")) { list.add("Thurgo has now smithed me a replica of Sir Vyvin's sword.") list.add("") list.add("I should return it to the Squire for my reward.") diff --git a/game/src/main/kotlin/content/quest/free/the_restless_ghost/FatherUrhney.kt b/game/src/main/kotlin/content/quest/free/the_restless_ghost/FatherUrhney.kt index 80671a0587..15e4581db7 100644 --- a/game/src/main/kotlin/content/quest/free/the_restless_ghost/FatherUrhney.kt +++ b/game/src/main/kotlin/content/quest/free/the_restless_ghost/FatherUrhney.kt @@ -9,7 +9,7 @@ import world.gregs.voidps.engine.client.message import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.item.floor.FloorItems import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory class FatherUrhney : Script { @@ -42,7 +42,7 @@ class FatherUrhney : Script { if (stage == "ghost" || stage == "mining_spot" || stage == "found_skull" || stage == "completed") { option("I've lost the Amulet of Ghostspeak.") { statement("Father Urhney sighs.") - if (holdsItem("ghostspeak_amulet")) { + if (carriesItem("ghostspeak_amulet")) { npc("What are you talking about? I can see you've got it with you!") return@option } diff --git a/game/src/main/kotlin/content/quest/member/fairy_tale_part_2/fairy_ring/FairyRingCodes.kt b/game/src/main/kotlin/content/quest/member/fairy_tale_part_2/fairy_ring/FairyRingCodes.kt index 2abf1a1f79..15d2b4a48a 100644 --- a/game/src/main/kotlin/content/quest/member/fairy_tale_part_2/fairy_ring/FairyRingCodes.kt +++ b/game/src/main/kotlin/content/quest/member/fairy_tale_part_2/fairy_ring/FairyRingCodes.kt @@ -1,6 +1,6 @@ package content.quest.member.fairy_tale_part_2.fairy_ring -import content.bot.interact.navigation.graph.readTile +import content.bot.behaviour.navigation.NavigationGraph.Companion.readTile import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import world.gregs.config.Config import world.gregs.voidps.engine.timedLoad diff --git a/game/src/main/kotlin/content/quest/member/plague_city/PlagueCity.kt b/game/src/main/kotlin/content/quest/member/plague_city/PlagueCity.kt index f276e06096..97fd19c42c 100644 --- a/game/src/main/kotlin/content/quest/member/plague_city/PlagueCity.kt +++ b/game/src/main/kotlin/content/quest/member/plague_city/PlagueCity.kt @@ -14,7 +14,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.chat.noInterest import world.gregs.voidps.engine.entity.character.player.equip.equipped import world.gregs.voidps.engine.entity.character.sound -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.queue.queue @@ -53,7 +53,7 @@ class PlagueCity : Script { "Edmond told me that his wife, Alrena, can make me a gas", "Mask to protect myself from the plague.", ) - if (holdsItem("dwellberries")) { + if (carriesItem("dwellberries")) { list.add("I need to get some Dwellberries for Alrena so she can make") list.add("me a Gas Mask to protect myself from the Plague. According") list.add("to Edmond, I can find some in McGrubor's Wood, west of") @@ -200,7 +200,7 @@ class PlagueCity : Script { list.add("I entered West Ardougne and found Jethick, an old friend") list.add("of Edmond. He seemed willing to help me find Elena but") list.add("didn't know what she looked like.") - if (holdsItem("picture_plague_city")) { + if (carriesItem("picture_plague_city")) { list.add("I have a picture of her which might help. I should show it to Jethick.") } } else { @@ -235,13 +235,13 @@ class PlagueCity : Script { list.add("Rehnison Family. According to him, they live in a timber") list.add("house in the north of the city. He asked me to return a") list.add("book to them while I was there.") - if (!holdsItem("book_turnip_growing_for_beginners")) { + if (!carriesItem("book_turnip_growing_for_beginners")) { list.add("but I don't have it with me.") } } else { list.add("I entered West Ardougne and found Jethick, an old friend of") list.add("Edmond. He seemed willing to help me find Elena but didn't") - if (holdsItem("picture_plague_city")) { + if (carriesItem("picture_plague_city")) { list.add("know what she looked like. I have a picture of her which might") list.add("help. I should show it to Jethick.") } else { diff --git a/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt b/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt index 65f2633c31..ab08148e95 100644 --- a/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt +++ b/game/src/main/kotlin/content/skill/constitution/drink/Potions.kt @@ -15,7 +15,7 @@ import world.gregs.voidps.engine.client.ui.chat.plural import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.equip.equipped import world.gregs.voidps.engine.entity.character.player.skill.Skill -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.timer.toTicks @@ -25,7 +25,10 @@ import java.util.concurrent.TimeUnit class Potions : Script { init { - consumed("*_4,*_3,*_2,*_1") { item, slot -> + consumed("*") { item, slot -> + if (!item.id.endsWith("_1") && !item.id.endsWith("_2") && !item.id.endsWith("_3") && !item.id.endsWith("_4")) { + return@consumed + } val doses = item.id.last().digitToInt() if (doses != 1) { message("You have ${doses - 1} ${"dose".plural(doses - 1)} of the potion left.") @@ -41,7 +44,7 @@ class Potions : Script { } } - fun Player.hasHolyItem() = equipped(EquipSlot.Cape).id.startsWith("prayer_cape") || holdsItem("holy_wrench") + fun Player.hasHolyItem() = equipped(EquipSlot.Cape).id.startsWith("prayer_cape") || carriesItem("holy_wrench") fun Player.effects(potion: String) { when { diff --git a/game/src/main/kotlin/content/skill/crafting/SilverCasting.kt b/game/src/main/kotlin/content/skill/crafting/SilverCasting.kt index 4d1aa24816..80c15b079b 100644 --- a/game/src/main/kotlin/content/skill/crafting/SilverCasting.kt +++ b/game/src/main/kotlin/content/skill/crafting/SilverCasting.kt @@ -43,12 +43,12 @@ class SilverCasting : Script { val item = silver.item val quest = silver.quest interfaces.sendVisibility(id, mould.id, quest == null || quest(quest) != "unstarted") - val has = holdsItem(mould.id) + val has = carriesItem(mould.id) interfaces.sendText( id, "${mould.id}_text", if (has) { - val colour = if (holdsItem("silver_bar")) "green" else "orange" + val colour = if (carriesItem("silver_bar")) "green" else "orange" "<$colour>Make ${ItemDefinitions.get(item).name.toTitleCase()}" } else { "You need a ${silver.name ?: mould.def.name.lowercase()} to make this item." diff --git a/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt b/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt index 8ca78b32f7..5e6449996a 100644 --- a/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt +++ b/game/src/main/kotlin/content/skill/farming/FarmingPatchPick.kt @@ -17,7 +17,7 @@ import world.gregs.voidps.engine.entity.character.sound import world.gregs.voidps.engine.entity.item.Item import world.gregs.voidps.engine.entity.obj.GameObject import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.queue.weakQueue @@ -128,7 +128,7 @@ class FarmingPatchPick(val variableDefinitions: VariableDefinitions) : Script { } fun saveLife(player: Player, chance: IntRange, obj: GameObject): Boolean { - if (player.holdsItem("magic_secateurs") && !obj.id.startsWith("farming_belladonna") && !obj.id.startsWith("farming_cactus") && !obj.id.startsWith("farming_mushroom") && !obj.id.startsWith("farming_fruit_tree") && !obj.id.startsWith("farming_calquat") || (obj.id.startsWith("farming_flower") && obj.def(player).stringId.startsWith("limpwurt")) && !obj.id.endsWith("_stump")) { + if (player.carriesItem("magic_secateurs") && !obj.id.startsWith("farming_belladonna") && !obj.id.startsWith("farming_cactus") && !obj.id.startsWith("farming_mushroom") && !obj.id.startsWith("farming_fruit_tree") && !obj.id.startsWith("farming_calquat") || (obj.id.startsWith("farming_flower") && obj.def(player).stringId.startsWith("limpwurt")) && !obj.id.endsWith("_stump")) { return Level.success(player.levels.get(Skill.Farming), chance.first + (chance.first / 10)..chance.last + (chance.last / 10)) } return Level.success(player.levels.get(Skill.Farming), chance) diff --git a/game/src/main/kotlin/content/skill/fishing/Fishing.kt b/game/src/main/kotlin/content/skill/fishing/Fishing.kt index b800ecc697..e7592dfa6f 100644 --- a/game/src/main/kotlin/content/skill/fishing/Fishing.kt +++ b/game/src/main/kotlin/content/skill/fishing/Fishing.kt @@ -22,7 +22,7 @@ import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level.has import world.gregs.voidps.engine.entity.character.player.skill.level.Level.success import world.gregs.voidps.engine.inv.add -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem import world.gregs.voidps.engine.inv.inventory import world.gregs.voidps.engine.inv.remove import world.gregs.voidps.engine.inv.transact.TransactionError @@ -78,13 +78,13 @@ class Fishing : Script { break } - val tackle = data.tackle.firstOrNull { tackle -> player.holdsItem(tackle) } + val tackle = data.tackle.firstOrNull { tackle -> player.carriesItem(tackle) } if (tackle == null) { player.message("You need a ${data.tackle.first().toTitleCase()} to catch these fish.") break@fishing } - val bait = data.bait.keys.firstOrNull { bait -> bait == "none" || player.holdsItem(bait) } + val bait = data.bait.keys.firstOrNull { bait -> bait == "none" || player.carriesItem(bait) } val catches = data.bait[bait] if (bait == null || catches == null) { player.message("You don't have any ${data.bait.keys.first().toTitleCase().plural(2)}.") diff --git a/game/src/main/kotlin/content/skill/fletching/FletchUnfinished.kt b/game/src/main/kotlin/content/skill/fletching/FletchUnfinished.kt index 5c151de542..29562eb093 100644 --- a/game/src/main/kotlin/content/skill/fletching/FletchUnfinished.kt +++ b/game/src/main/kotlin/content/skill/fletching/FletchUnfinished.kt @@ -38,12 +38,10 @@ class FletchUnfinished : Script { fun Player.fletch(addItem: String, addItemDef: Fletching, removeItem: String, amount: Int) { if (amount <= 0) { - softTimers.stop("fletching") return } if (!inventory.contains("knife") || !inventory.contains(removeItem)) { - softTimers.stop("fletching") return } diff --git a/game/src/main/kotlin/content/skill/mining/Pickaxe.kt b/game/src/main/kotlin/content/skill/mining/Pickaxe.kt index d45ee6c31e..cae2265009 100644 --- a/game/src/main/kotlin/content/skill/mining/Pickaxe.kt +++ b/game/src/main/kotlin/content/skill/mining/Pickaxe.kt @@ -4,7 +4,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirementsToUse import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem object Pickaxe { private val pickaxes = listOf( @@ -20,5 +20,5 @@ object Pickaxe { Item("bronze_pickaxe"), ) - fun best(player: Player): Item? = pickaxes.firstOrNull { pickaxe -> player.hasRequirementsToUse(pickaxe, skills = setOf(Skill.Mining, Skill.Firemaking)) && player.holdsItem(pickaxe.id) } + fun best(player: Player): Item? = pickaxes.firstOrNull { pickaxe -> player.hasRequirementsToUse(pickaxe, skills = setOf(Skill.Mining, Skill.Firemaking)) && player.carriesItem(pickaxe.id) } } diff --git a/game/src/main/kotlin/content/skill/runecrafting/Runecrafting.kt b/game/src/main/kotlin/content/skill/runecrafting/Runecrafting.kt index 496159f305..c34d94fcf4 100644 --- a/game/src/main/kotlin/content/skill/runecrafting/Runecrafting.kt +++ b/game/src/main/kotlin/content/skill/runecrafting/Runecrafting.kt @@ -54,11 +54,11 @@ class Runecrafting : Script { } val combination = list[0] as String val xp = list[1] as Double - if (!holdsItem("pure_essence")) { + if (!carriesItem("pure_essence")) { message("You need pure essence to bind $combination runes.") return@itemOnObjectOperate } - if (!holdsItem("${element}_talisman") && !hasClock("magic_imbue")) { + if (!carriesItem("${element}_talisman") && !hasClock("magic_imbue")) { message("You need a $element talisman to bind $combination runes.") return@itemOnObjectOperate } diff --git a/game/src/main/kotlin/content/skill/woodcutting/Hatchet.kt b/game/src/main/kotlin/content/skill/woodcutting/Hatchet.kt index 740974d58c..cda0751c94 100644 --- a/game/src/main/kotlin/content/skill/woodcutting/Hatchet.kt +++ b/game/src/main/kotlin/content/skill/woodcutting/Hatchet.kt @@ -4,7 +4,7 @@ import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level.hasRequirementsToUse import world.gregs.voidps.engine.entity.item.Item -import world.gregs.voidps.engine.inv.holdsItem +import world.gregs.voidps.engine.inv.carriesItem object Hatchet { private val hatchets = listOf( @@ -21,7 +21,7 @@ object Hatchet { Item("bronze_hatchet"), ) - fun best(player: Player): Item? = hatchets.firstOrNull { hasRequirements(player, it) && player.holdsItem(it.id) } + fun best(player: Player): Item? = hatchets.firstOrNull { hasRequirements(player, it) && player.carriesItem(it.id) } fun hasRequirements(player: Player, hatchet: Item, message: Boolean = false): Boolean = player.hasRequirementsToUse(hatchet, message, setOf(Skill.Firemaking, Skill.Woodcutting)) } diff --git a/game/src/main/resources/game.properties b/game/src/main/resources/game.properties index a8249883eb..5f5651853e 100644 --- a/game/src/main/resources/game.properties +++ b/game/src/main/resources/game.properties @@ -236,14 +236,34 @@ fightCave.startWave = 1 #=================================== # The number of AI-controlled bots spawned on startup -bots.count=10 +bots.count=30 -# Frequently between spawning bots on startup +# Frequency between spawning bots on startup bots.spawnSeconds=60 -# What tasks to give AI-controlled bots with no tasks (options: nothing, randomWalk) -bots.idle=randomWalk +# Use bot names instead of randomised +bots.numberedNames=false +# Location of a text file containing bot names to pick from +bots.names=./data/bot_names.txt + +# Prefix at the start of bot's names (blank to hide) +bots.namePrefix="" + +# File ending for bot definitions +bots.definitions=bots.toml + +# File ending for bot definition templates +bots.templates=templates.toml + +# File ending for bot definition templates +bots.setups=setups.toml + +# File ending for bot definition templates +bots.shortcuts=shortcuts.toml + +# File ending for bot navigation graph definitions +bots.nav.definitions=nav-edges.toml #=================================== # Storage & File System @@ -357,9 +377,6 @@ map.teleports=teles.toml # Path to the music track location data map.music=music_tracks.toml -# Path to the navigation graph data -map.navGraph=nav_graph.toml - # Canoe station information map.canoes=canoe_stations.toml diff --git a/game/src/test/kotlin/WorldTest.kt b/game/src/test/kotlin/WorldTest.kt index 92ac3d659c..4533b93356 100644 --- a/game/src/test/kotlin/WorldTest.kt +++ b/game/src/test/kotlin/WorldTest.kt @@ -48,6 +48,7 @@ import world.gregs.voidps.engine.map.instance.Instances import world.gregs.voidps.engine.timer.setCurrentTime import world.gregs.voidps.network.client.Client import world.gregs.voidps.network.client.ConnectionQueue +import world.gregs.voidps.network.client.DummyClient import world.gregs.voidps.type.Tile import world.gregs.voidps.type.setRandom import java.io.File @@ -99,7 +100,7 @@ abstract class WorldTest : KoinTest { fun createPlayer(tile: Tile = Tile.EMPTY, name: String = "player"): Player { val player = Player(tile = tile, accountName = name, passwordHash = "") - assertTrue(accounts.setup(player, null, 0)) + assertTrue(accounts.setup(player, null, 0, viewport = true)) accountDefs.add(player) tick() player["creation"] = -1 @@ -108,7 +109,6 @@ abstract class WorldTest : KoinTest { player.softTimers.clear("restore_stats") player.softTimers.clear("restore_hitpoints") tick() - player.viewport = Viewport() player.viewport?.loaded = true return player } @@ -263,7 +263,7 @@ abstract class WorldTest : KoinTest { properties["storage.grand.exchange.offers.claim.path"] = "../temp/data/test-grand_exchange/claimable_offers.toml" properties["storage.grand.exchange.offers.path"] = "../temp/data/test-grand_exchange/offers.toml" properties["storage.grand.exchange.history.path"] = "../temp/data/test-grand_exchange/price_history/" - properties["storage.caching.path"] = "../data/.temp" + properties["storage.caching.path"] = "../data/.temp/" properties["quests.requirements.skipMissing"] = false properties["grandExchange.priceLimit"] = true properties["world.npcs.randomWalk"] = false diff --git a/game/src/test/kotlin/content/bot/BotManagerTest.kt b/game/src/test/kotlin/content/bot/BotManagerTest.kt new file mode 100644 index 0000000000..ed8ff407c7 --- /dev/null +++ b/game/src/test/kotlin/content/bot/BotManagerTest.kt @@ -0,0 +1,388 @@ +package content.bot + +import content.bot.behaviour.BehaviourFrame +import content.bot.behaviour.BehaviourState +import content.bot.behaviour.Condition +import content.bot.behaviour.Reason +import content.bot.behaviour.SoftReason +import content.bot.behaviour.action.BotAction +import content.bot.behaviour.activity.BotActivity +import content.bot.behaviour.setup.Resolver +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill + +class BotManagerTest { + + fun testBot(vararg activities: BotActivity, name: String = "bot") = Bot(Player(accountName = name)).also { it.available.addAll(activities.map { a -> a.id }) } + + @Test + fun `Taskless bot gets assigned an activity`() { + val activity = testActivity( + id = "woodcutting", + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + + manager.tick(bot) + + assertEquals(1, bot.frames.size) + assertEquals(activity, bot.previous) + } + + @Test + fun `Activity capacity is respected`() { + val activity = testActivity( + id = "mine", + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager(mutableMapOf(activity.id to activity)) + + val bot1 = testBot(activity, name = "bot1") + val bot2 = testBot(activity, name = "bot2") + + manager.tick(bot1) + manager.tick(bot2) + + assertEquals(1, bot1.frames.size) + assertEquals("idle", bot2.frames.first().behaviour.id) + } + + @Test + fun `Pending frame starts running`() { + val activity = testActivity( + id = "walk", + plan = listOf(BotAction.Wait(1, BehaviourState.Running)), + ) + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + + manager.tick(bot) + manager.tick(bot) + manager.tick(bot) + + assertEquals(BehaviourState.Running, bot.frame().state) + } + + @Test + fun `Success advances frame index`() { + val activity = testActivity( + id = "task", + plan = listOf( + BotAction.Wait(1), + BotAction.Wait(1), + ), + ) + val frame = BehaviourFrame(activity) + frame.start(testBot(activity)) + frame.success() + + val advanced = frame.next() + + assertTrue(advanced) + assertEquals(1, frame.index) + assertEquals(BehaviourState.Running, frame.state) + } + + @Test + fun `Activity slot released on success`() { + val activity = testActivity( + id = "cook", + plan = listOf(BotAction.Wait(1)), + ) + + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + + manager.tick(bot) + manager.tick(bot) + + bot.frame().success() + manager.tick(bot) + assertTrue(manager.slots.hasFree(activity)) + assertEquals(0, bot.frames.size) + } + + @Test + fun `Completed activity most likely to be reassigned on success`() { + val activity = testActivity( + id = "cook", + plan = listOf(BotAction.Wait(1)), + ) + val test = testActivity( + id = "test", + plan = listOf(BotAction.Wait(1)), + ) + + val activities = mutableMapOf(activity.id to activity, test.id to test) + val manager = BotManager(activities) + val bot = testBot(activity, test) + + bot.previous = activity + + manager.tick(bot) + + assertFalse(manager.slots.hasFree(activity)) + assertEquals(activity, bot.previous) + assertEquals(activity, bot.frames.peek().behaviour) + assertEquals(1, bot.frames.size) + } + + @Test + fun `Activity slot released on failure`() { + val activity = testActivity( + id = "smith", + plan = listOf(BotAction.Wait(1)), + ) + + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + + manager.tick(bot) + manager.tick(bot) + + bot.frame().fail(Reason.Cancelled) + manager.tick(bot) + + assertTrue(bot.frames.isEmpty()) + } + + @Test + fun `Failed activity is blocked`() { + val activity = testActivity( + id = "fish", + plan = listOf(BotAction.Wait(1)), + ) + + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + + manager.tick(bot) + manager.tick(bot) + bot.frame().fail(Reason.Requirement(Condition.AtTile())) + manager.tick(bot) + manager.tick(bot) + + assertEquals("idle", bot.frames.first().behaviour.id) + assertTrue("fish" in bot.blocked) + } + + @Test + fun `Behaviour without requirements isn't started`() { + val activity = testActivity( + id = "test", + requires = listOf( + Condition.SkillLevel(Skill.Attack, 99), + ), + plan = listOf(BotAction.Wait(4)), + ) + + val manager = BotManager(mutableMapOf(activity.id to activity)) + val bot = testBot(activity) + bot.frames.add(BehaviourFrame(activity)) + + manager.tick(bot) + assertTrue(bot.frame().state is BehaviourState.Failed) + manager.tick(bot) + + assertTrue(bot.frames.isEmpty()) + assertTrue("test" in bot.blocked) + } + + @Test + fun `Resolvable requirement queues resolver before activity starts`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver( + id = "go_to_area", + weight = 1, + actions = listOf(BotAction.Wait(1)), + produces = setOf("tile"), + ) + val activity = testActivity( + id = "woodcut", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + manager.tick(bot) + manager.tick(bot) + + assertEquals(2, bot.frames.size) + assertEquals(resolver, bot.frames.peek().behaviour) + } + + @Test + fun `Lowest weight resolver is selected`() { + val condition = Condition.AtTile(100, 100, 2) + + val bad = Resolver("bad", weight = 10, actions = listOf(BotAction.Wait(1))) + val good = Resolver("good", weight = 1, actions = listOf(BotAction.Wait(1))) + + val activity = testActivity( + id = "mine", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(bad, good)), + ) + + val bot = testBot(activity) + manager.tick(bot) + manager.tick(bot) + + assertEquals("good", bot.frames.peek().behaviour.id) + } + + @Test + fun `Blocked resolver is not reselected`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver(id = "get_key", weight = 1, actions = listOf(BotAction.Wait(1))) + val activity = testActivity( + id = "open_door", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + manager.tick(bot) + assertEquals(1, bot.frames.size) + val frame = bot.frames.last() + frame.blocked.add("get_key") + manager.tick(bot) + manager.tick(bot) + + assertTrue(bot.frames.isEmpty()) + assertTrue("open_door" in bot.blocked) + } + + @Test + fun `Hard failure in resolver stops bot`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver( + id = "walk", + weight = 1, + actions = listOf(BotAction.Wait(1)), + ) + val activity = testActivity( + id = "enter_zone", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + manager.tick(bot) + manager.tick(bot) + assertEquals(2, bot.frames.size) + bot.frame().fail(Reason.Cancelled) + manager.tick(bot) + + assertTrue(bot.frames.isEmpty()) + } + + @Test + fun `Soft failure in resolver only pops resolver`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver( + id = "test", + weight = 1, + actions = listOf(BotAction.Wait(1)), + ) + val activity = testActivity( + id = "smelt", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + bot.player["debug"] = true + manager.tick(bot) + manager.tick(bot) + assertEquals(2, bot.frames.size) + bot.frame().fail(object : SoftReason {}) + manager.tick(bot) + + assertEquals(activity, bot.frame().behaviour) + } + + @Test + fun `Resolver with unmet mandatory requirements is skipped`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver( + id = "mine_gem", + weight = 1, + actions = listOf(BotAction.Wait(1)), + requires = listOf(Condition.SkillLevel(Skill.Mining, 99)), + ) + val activity = testActivity( + id = "craft", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + manager.tick(bot) + manager.tick(bot) + manager.tick(bot) + + assertTrue(bot.frames.isEmpty()) + assertTrue("craft" in bot.blocked) + } + + @Test + fun `Activity are occupied while resolver is running`() { + val condition = Condition.AtTile(100, 100, 2) + val resolver = Resolver( + id = "get_tool", + weight = 1, + actions = listOf(BotAction.Wait(1)), + ) + val activity = testActivity( + id = "work", + resolves = listOf(condition), + plan = listOf(BotAction.Wait(1)), + ) + val manager = BotManager( + mutableMapOf(activity.id to activity), + mutableMapOf(condition.keys().first() to mutableListOf(resolver)), + ) + + val bot = testBot(activity) + manager.tick(bot) + manager.tick(bot) + + assertFalse(manager.slots.hasFree(activity)) + } + + fun testActivity( + id: String, + requires: List = emptyList(), + resolves: List = emptyList(), + plan: List, + ) = BotActivity(id, 1, 50, requires, resolves, plan) +} diff --git a/game/src/test/kotlin/content/bot/behaviour/activity/ActivitySlotsTest.kt b/game/src/test/kotlin/content/bot/behaviour/activity/ActivitySlotsTest.kt new file mode 100644 index 0000000000..022de96907 --- /dev/null +++ b/game/src/test/kotlin/content/bot/behaviour/activity/ActivitySlotsTest.kt @@ -0,0 +1,41 @@ +package content.bot.behaviour.activity + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ActivitySlotsTest { + + lateinit var slots: ActivitySlots + + @BeforeEach + fun setup() { + slots = ActivitySlots() + } + + @Test + fun `Unoccupied slots are free`() { + val activity = BotActivity("test", 2) + + Assertions.assertTrue(slots.hasFree(activity)) + } + + @Test + fun `Occupied slots aren't free`() { + val activity = BotActivity("test", 1) + + slots.occupy(activity) + + Assertions.assertFalse(slots.hasFree(activity)) + } + + @Test + fun `Released slots are free`() { + val activity = BotActivity("test", 1) + + slots.occupy(activity) + slots.release(activity) + + Assertions.assertTrue(slots.hasFree(activity)) + } +} diff --git a/game/src/test/kotlin/content/bot/behaviour/navigation/NavigationGraphTest.kt b/game/src/test/kotlin/content/bot/behaviour/navigation/NavigationGraphTest.kt new file mode 100644 index 0000000000..7f02e492ed --- /dev/null +++ b/game/src/test/kotlin/content/bot/behaviour/navigation/NavigationGraphTest.kt @@ -0,0 +1,377 @@ +package content.bot.behaviour.navigation + +import content.bot.behaviour.Condition +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.data.definition.AreaDefinition +import world.gregs.voidps.engine.data.definition.Areas +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.type.Tile +import world.gregs.voidps.type.area.Rectangle +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class NavigationGraphTest { + + @Test + fun `Shortest path is found`() { + /* + + B + 10 / | + A | 1 + 1 \ | + C + + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val ab = builder.addEdge(a, b, 10) + val ac = builder.addEdge(a, c, 1) + val cb = builder.addEdge(c, b, 1) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(a), b) + assertTrue(success) + assertEquals(listOf(ac, cb), output) + } + + @Test + fun `Branching path`() { + /* + 6 + B-----E + 2 / |5 / | \ 9 + A | /3 |1 C + 8 \ | / | / 3 + D-----F + 2 + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val d = 3 + val e = 4 + val f = 5 + val ab = builder.addEdge(a, b, 2) + builder.addEdge(a, d, 8) + builder.addEdge(b, e, 6) + val bd = builder.addEdge(b, d, 5) + builder.addEdge(d, e, 3) + val df = builder.addEdge(d, f, 2) + builder.addEdge(e, f, 1) + builder.addEdge(e, c, 9) + val fc = builder.addEdge(f, c, 3) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(a), c) + assertTrue(success) + assertEquals(listOf(ab, bd, df, fc), output) + } + + @Test + fun `Differing starting location`() { + /* + 1 + ------- + / 2 \ + G---D | + 2 | | 1 | + F B--/ + 3 | / + C / 1 + 4 |/ + A + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val d = 3 + val e = 4 + val f = 5 + val g = 6 + builder.addEdge(a, b, 1) + builder.addEdge(c, a, 4) + val bd = builder.addEdge(b, d, 1) + builder.addEdge(b, e, 3) + builder.addEdge(f, c, 5) + builder.addEdge(d, g, 2) + val gb = builder.addEdge(g, b, 1) + val fg = builder.addEdge(f, g, 2) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(f), d) + assertTrue(success) + assertEquals(listOf(fg, gb, bd), output) + } + + @Test + fun `Condition is avoided if failed`() { + /* + 3 + B-----C + 7 / |8 / | + A | /2 |6 + 1 \ | / | + E-----D + 7-X + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val d = 3 + val e = 4 + builder.addEdge(a, b, 7) + val ae = builder.addEdge(a, e, 1) + builder.addEdge(b, e, 8) + builder.addEdge(b, c, 3) + val cd = builder.addEdge(c, d, 6) + val ec = builder.addEdge(e, c, 2) + builder.addEdge(e, d, 7, conditions = listOf(Condition.AtTile(100))) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(a), d) + assertTrue(success) + assertEquals(listOf(ae, ec, cd), output) + } + + @Test + fun `Can only traverse in one direction`() { + /* + B + 1 / + A + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + builder.addEdge(a, b, 10) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(b), a) + Assertions.assertFalse(success) + } + + @Test + fun `Single edge path without weights`() { + /* + B + 1 | + A + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val ab = builder.addEdge(a, b, 0) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(a), b) + assertTrue(success) + assertEquals(listOf(ab), output) + } + + @Test + fun `Multiple starting points`() { + /* + 1 + ------- + / 2 \ + G---D | + 2 | | 1 | + F B--/ + 3 | / + C / 1 + 4 |/ + A + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val d = 3 + val e = 4 + val f = 5 + val g = 6 + val ab = builder.addEdge(a, b, 1) + builder.addEdge(c, a, 4) + val bd = builder.addEdge(b, d, 1) + builder.addEdge(b, e, 3) + builder.addEdge(f, c, 5) + builder.addEdge(d, g, 2) + builder.addEdge(g, b, 1) + builder.addEdge(f, g, 2) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, setOf(NavigationGraph.Node(f), NavigationGraph.Node(a)), d) + assertTrue(success) + assertEquals(listOf(ab, bd), output) + } + + @Test + fun `Find returns shortest path`() { + val builder = NavigationGraph.Builder() + + val a = Tile(0) + val b = Tile(1) + val c = Tile(2) + + builder.addBiEdge(a, b, weight = 1, actions = emptyList()) + builder.addBiEdge(b, c, weight = 1, actions = emptyList()) + builder.addBiEdge(a, c, weight = 10, actions = emptyList()) + + val graph = builder.build() + val player = Player() + player.tile = a + + val path = mutableListOf() + val found = graph.find(player, path, start = NavigationGraph.Node(0), target = 2) + + assertTrue(found) + assertEquals(listOf(0, 2), path) + } + + @Test + fun `Find respects edge conditions`() { + val builder = NavigationGraph.Builder() + + val a = Tile(0) + val b = Tile(1) + + builder.addEdge( + from = a, + to = b, + weight = 1, + actions = emptyList(), + conditions = listOf(Condition.AtTile(100)), + ) + + val graph = builder.build() + val player = Player() + player.tile = a + + val path = mutableListOf() + val found = graph.find(player, path, start = NavigationGraph.Node(0), target = 1) + + Assertions.assertFalse(found, "Edge condition blocks traversal") + assertTrue(path.isEmpty()) + } + + @Test + fun `Starting points include nearby tiles`() { + val builder = NavigationGraph.Builder() + + val a = Tile(1, 1) + val b = Tile(20, 20) + val c = Tile(100, 100) + + builder.add(a) + builder.add(b) + builder.add(c) + + val graph = builder.build() + val player = Player() + player.tile = Tile(10, 10) + + val starts = graph.startingPoints(player) + + assertTrue(starts.contains(NavigationGraph.Node(1, 9))) + assertTrue(starts.contains(NavigationGraph.Node(2, 10))) + Assertions.assertFalse(starts.contains(NavigationGraph.Node(3, 90))) + } + + @Test + fun `Shortcut adds starting point when requirements met`() { + Areas.set(mapOf("town" to AreaDefinition("town", Rectangle(75, 75, 75, 75), setOf())), mapOf(), mapOf()) + + val shortcut = NavigationShortcut( + id = "teleport", + weight = 1, + timeout = 50, + requires = listOf(Condition.AtTile(50, 50)), + produces = setOf("area:town"), + ) + + val builder = NavigationGraph.Builder() + builder.add(Tile(75, 75)) + val edge = builder.add(shortcut) + + val graph = builder.build() + val player = Player() + player.tile = Tile(50, 50) + + val starts = graph.startingPoints(player) + + assertTrue(starts.contains(NavigationGraph.Node(edge))) + } + + @Test + fun `Path reconstruction produces correct edge order`() { + val builder = NavigationGraph.Builder() + + val a = Tile(0) + + val e1 = builder.addEdge(0, 1, weight = 1) + val e2 = builder.addEdge(1, 2, weight = 1) + + val graph = builder.build() + val player = Player() + player.tile = a + + val path = mutableListOf() + val found = graph.find(player, path, start = NavigationGraph.Node(0), target = 2) + + assertTrue(found) + assertEquals(listOf(e1, e2), path) + } + + @Test + fun `Complex route`() { + /* + */ + val builder = NavigationGraph.Builder() + val a = 0 + val b = 1 + val c = 2 + val d = 3 + val e = 4 + val f = 5 + val g = 6 + val h = 7 + val i = 8 + val j = 9 + val k = 10 + val l = 11 + val m = 12 + builder.addEdge(a, b, 12) + builder.addEdge(b, c, 13) + builder.addEdge(c, d, 13) + builder.addEdge(d, e, 18) + builder.addEdge(e, f, 11) + builder.addEdge(f, g, 10) + builder.addEdge(g, h, 8) + builder.addEdge(h, i, 7) + val aj = builder.addEdge(a, j, 2) + val jk = builder.addEdge(j, k, 5) + val kl = builder.addEdge(k, l, 7) + val lm = builder.addEdge(l, m, 9) + val mh = builder.addEdge(m, h, 6) +// builder.print() + + val output = mutableListOf() + val success = builder.build().find(Player(), output, NavigationGraph.Node(a), h) + assertTrue(success) + assertEquals(listOf(aj, jk, kl, lm, mh), output) + } +} diff --git a/game/src/test/kotlin/content/bot/path/DijkstraTest.kt b/game/src/test/kotlin/content/bot/path/DijkstraTest.kt deleted file mode 100644 index 91843f252f..0000000000 --- a/game/src/test/kotlin/content/bot/path/DijkstraTest.kt +++ /dev/null @@ -1,189 +0,0 @@ -package content.bot.path - -import content.bot.interact.navigation.graph.Condition -import content.bot.interact.navigation.graph.Edge -import content.bot.interact.navigation.graph.NavigationGraph -import content.bot.interact.navigation.graph.waypoints -import content.bot.interact.path.Dijkstra -import content.bot.interact.path.DijkstraFrontier -import content.bot.interact.path.EdgeTraversal -import content.bot.interact.path.NodeTargetStrategy -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet -import kotlinx.io.pool.DefaultPool -import kotlinx.io.pool.ObjectPool -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import world.gregs.voidps.engine.entity.character.player.Player -import world.gregs.voidps.type.Tile -import java.util.* -import kotlin.test.assertNotNull - -internal class DijkstraTest { - - private lateinit var graph: NavigationGraph - private lateinit var dij: Dijkstra - private lateinit var pool: ObjectPool - - @BeforeEach - fun setup() { - graph = NavigationGraph() - pool = object : DefaultPool(1) { - override fun produceInstance() = DijkstraFrontier(3) - } - mockkStatic("content.bot.interact.navigation.graph.EdgeKt") - dij = Dijkstra(graph, pool) - } - - /** - * P -- A -- B -- C -- D - */ - @Test - fun `Find path`() { - val player: Player = mockk() - val a = Tile(5, 10) - val b = Tile(15, 0) - val c = Tile(20, 0) - val d = Tile(25, 0) - - val e1 = Edge("", player, a, 0) - val e2 = Edge("", a, b, 0) - val e3 = Edge("", b, c, 0) - val e4 = Edge("", c, d, 0) - graph.add(player, ObjectOpenHashSet.of(e1)) - graph.add(a, ObjectOpenHashSet.of(e2)) - graph.add(b, ObjectOpenHashSet.of(e3)) - graph.add(c, ObjectOpenHashSet.of(e4)) - - val waypoints = LinkedList() - every { player.waypoints } returns waypoints - - val strategy: NodeTargetStrategy = object : NodeTargetStrategy() { - override fun reached(node: Any): Boolean = node == c - } - val traversal = EdgeTraversal() - // When - val result = dij.find(player, strategy, traversal) - // Then - assertNotNull(result) - assertEquals(c, result) - assertEquals(e1, waypoints.poll()) - assertEquals(e2, waypoints.poll()) - assertEquals(e3, waypoints.poll()) - assertTrue(waypoints.isEmpty()) - } - - /** -- A - * 10 / - * P -- - * 9 \ - * -- B - */ - @Test - fun `Find lowest cost path`() { - val player: Player = mockk() - val a = Tile(5, 10) - val b = Tile(15, 0) - - val edge = Edge("", player, b, 9) - graph.add(player, ObjectOpenHashSet.of(Edge("", player, a, 10), edge)) - - val waypoints = LinkedList() - every { player.waypoints } returns waypoints - - val strategy: NodeTargetStrategy = object : NodeTargetStrategy() { - override fun reached(node: Any): Boolean = node != player - } - val traversal = EdgeTraversal() - // When - val result = dij.find(player, strategy, traversal) - // Then - assertNotNull(result) - assertEquals(edge, waypoints.poll()) - assertTrue(waypoints.isEmpty()) - } - - /** B - * / \ - * P - A < C - * - */ - @Test - fun `Find directional twice visited node`() { - val player: Player = mockk() - val a = Tile(5, 10) - val b = Tile(15, 0) - val c = Tile(0, 0) - - val e1 = Edge("", player, a, 0) - val e2 = Edge("", a, b, 0) - val e3 = Edge("", b, c, 0) - val e4 = Edge("", c, a, 0) - graph.add(player, ObjectOpenHashSet.of(e1)) - graph.add(a, ObjectOpenHashSet.of(e2)) - graph.add(b, ObjectOpenHashSet.of(e3)) - graph.add(c, ObjectOpenHashSet.of(e4)) - - val waypoints = LinkedList() - every { player.waypoints } returns waypoints - - var first = true - val strategy: NodeTargetStrategy = object : NodeTargetStrategy() { - override fun reached(node: Any): Boolean = if (node == a) { - val answer = !first - first = false - answer - } else { - false - } - } - val traversal = EdgeTraversal() - // When - val result = dij.find(player, strategy, traversal) - // Then - assertNotNull(result) - assertEquals(e1, waypoints.poll()) - assertEquals(e2, waypoints.poll()) - assertEquals(e3, waypoints.poll()) - assertEquals(e4, waypoints.poll()) - assertTrue(waypoints.isEmpty()) - } - - /** - * P -/- A - */ - @Test - fun `Paths can be blocked`() { - val p: Player = mockk() - val a = Tile(5, 10) - - val edge = Edge( - "", - p, - a, - 9, - requirements = listOf( - object : Condition { - override fun has(player: Player): Boolean = player != p - }, - ), - ) - graph.add(p, ObjectOpenHashSet.of(edge)) - - val waypoints = LinkedList() - every { p.waypoints } returns waypoints - - val strategy: NodeTargetStrategy = object : NodeTargetStrategy() { - override fun reached(node: Any): Boolean = node == a - } - val traversal = EdgeTraversal() - // When - val result = dij.find(p, strategy, traversal) - // Then - assertNull(result) - assertTrue(waypoints.isEmpty()) - } -} diff --git a/network/src/main/kotlin/world/gregs/voidps/network/LoginServer.kt b/network/src/main/kotlin/world/gregs/voidps/network/LoginServer.kt index d4b2ec25cd..130f409536 100644 --- a/network/src/main/kotlin/world/gregs/voidps/network/LoginServer.kt +++ b/network/src/main/kotlin/world/gregs/voidps/network/LoginServer.kt @@ -107,6 +107,10 @@ class LoginServer( write.finish(response) return false } + if (username.length > 12) { + write.finish(Response.INVALID_CREDENTIALS) + return false + } if (!online.add(username)) { write.finish(Response.ACCOUNT_ONLINE) return false diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/ItemDefinitions.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/ItemDefinitions.kt index 84ceb2f9ab..f690390084 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/ItemDefinitions.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/ItemDefinitions.kt @@ -23,10 +23,11 @@ object ItemDefinitions { val decoder = ItemDefinitions.init(ItemDecoder(parameters).load(cache)).load(files.list(Settings["definitions.items"])) for (i in decoder.definitions.indices) { val def = decoder.getOrNull(i) ?: continue - if (def.stringId.contains("dragon_plate")) { + if (def.stringId.contains("rune_plate")) { // if (def.get("category", "") != "") // if (/*def.get("category", "") == "throwable" &&*/ def.contains("secondary_use_level")) - println("${def.stringId} ${def.extras}") +// println("${def.stringId} ${def.extras}") + println(def) } // if (def.contains("ammo_group")) { // println("${def.stringId} ${def.extras}") diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/MapViewer.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/MapViewer.kt index 06065fbe2b..6a94d47594 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/MapViewer.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/MapViewer.kt @@ -2,7 +2,6 @@ package world.gregs.voidps.tools.map.view import com.github.weisj.darklaf.LafManager import com.github.weisj.darklaf.LafManager.getPreferredThemeStyle -import content.bot.interact.navigation.graph.NavigationGraph import world.gregs.voidps.cache.CacheDelegate import world.gregs.voidps.cache.definition.decoder.ObjectDecoder import world.gregs.voidps.engine.data.Settings @@ -30,13 +29,14 @@ class MapViewer { val files = configFiles() ObjectDefinitions.init(decoder).load(files.list(Settings["definitions.objects"])) Areas.load(files.list(Settings["map.areas"])) - val nav = NavigationGraph().load(files.find(Settings["map.navGraph"])) if (DISPLAY_AREA_COLLISIONS || DISPLAY_ALL_COLLISIONS) { ObjectDefinitions.init(ObjectDecoder(member = true, lowDetail = false).load(cache)) .load(files.list(Settings["definitions.objects"])) MapDefinitions(CollisionDecoder(), cache).load(files) } - frame.add(MapView(nav, files.list(Settings["map.areas"]))) + val view = MapView() + view.reload(files) + frame.add(view) frame.pack() frame.setLocationRelativeTo(null) frame.isVisible = true diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/GraphDrawer.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/GraphDrawer.kt index ad0c97c054..af09bfc601 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/GraphDrawer.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/GraphDrawer.kt @@ -1,6 +1,6 @@ package world.gregs.voidps.tools.map.view.draw -import content.bot.interact.navigation.graph.NavigationGraph +import content.bot.behaviour.navigation.NavigationGraph import org.rsmod.game.pathfinder.StepValidator import org.rsmod.game.pathfinder.collision.CollisionStrategies import org.rsmod.game.pathfinder.collision.CollisionStrategy @@ -11,16 +11,15 @@ import world.gregs.voidps.tools.map.view.MapViewer.Companion.FILTER_VIEWPORT import world.gregs.voidps.tools.map.view.graph.Area import world.gregs.voidps.tools.map.view.graph.AreaSet import world.gregs.voidps.tools.map.view.graph.Link -import world.gregs.voidps.type.Distance import world.gregs.voidps.type.Tile import java.awt.* import kotlin.math.sqrt class GraphDrawer( private val view: MapView, - private val nav: NavigationGraph?, private val area: AreaSet, ) { + private var graph: NavigationGraph? = null private val steps: StepValidator = StepValidator(Collisions.map) private val linkColour = Color(0.0f, 0.0f, 1.0f, 0.5f) @@ -29,11 +28,10 @@ class GraphDrawer( private val areaColour = Color(1.0f, 0.0f, 1.0f, 0.2f) private val walkableColour = Color(0.0f, 1.0f, 0.0f, 0.3f) private val collisionColour = Color(1.0f, 0.0f, 0.0f, 0.3f) - private val distances = nav?.nodes?.map { nav.get(it) }?.flatten()?.distinct()?.mapNotNull { edge -> - val start = edge.start as? Tile ?: return@mapNotNull null - val end = edge.end as? Tile ?: return@mapNotNull null - edge to Distance.chebyshev(start.x, start.y, end.x, end.y) - }?.toMap() + + fun reload(graph: NavigationGraph?) { + this.graph = graph + } fun repaint(link: Link) { repaint(link.start) @@ -49,40 +47,41 @@ class GraphDrawer( fun draw(g: Graphics) { g.color = linkColour - nav?.nodes?.filterIsInstance()?.forEach { node -> - if (node.level != view.level) { - return@forEach - } - val viewX = view.mapToViewX(node.x) - val viewY = view.mapToViewY(view.flipMapY(node.y)) - if (!view.contains(viewX, viewY)) { - return@forEach - } - val width = view.mapToImageX(1) - val height = view.mapToImageY(1) - g.fillOval(viewX, viewY, width, height) - - val edges = nav.getAdjacent(node) - edges.forEachIndexed { index, edge -> - val start = edge.start as? Tile ?: return@forEachIndexed - val end = edge.end as? Tile ?: return@forEachIndexed - if (start.level != view.level || end.level != view.level) { - return@forEachIndexed + if (graph != null) { + val graph = graph!! + for (i in 1 until graph.nodeCount) { + val tile = graph.tile(i) + if (tile.level != view.level) { + continue + } + val viewX = view.mapToViewX(tile.x) + val viewY = view.mapToViewY(view.flipMapY(tile.y)) + if (!view.contains(viewX, viewY)) { + continue } - val distance = distances?.get(edge) - val endX = view.mapToViewX(end.x) + width / 2 - val endY = view.mapToViewY(view.flipMapY(end.y)) + height / 2 - val offset = width / 4 - val startX = viewX + width / 2 - val startY = viewY + height / 2 - if (view.scale > 10) { - g.drawArrowHead(startX, startY, endX, endY, offset * 3, width / 2, index.toString()) - if (distance != null) { + val width = view.mapToImageX(1) + val height = view.mapToImageY(1) + g.fillOval(viewX, viewY, width, height) + + val edges = graph.edges(i) + edges?.forEachIndexed { index, edge -> + val end = graph.endTile(edge) + if (tile.level != view.level || end.level != view.level) { + return@forEachIndexed + } + val distance = graph.weight(edge) + val endX = view.mapToViewX(end.x) + width / 2 + val endY = view.mapToViewY(view.flipMapY(end.y)) + height / 2 + val offset = width / 4 + val startX = viewX + width / 2 + val startY = viewY + height / 2 + if (view.scale > 10) { + g.drawArrowHead(startX, startY, endX, endY, offset * 3, width / 2, index.toString()) g.drawString(distance.toString(), startX + (endX - startX) / 2, startY + (endY - startY) / 2) } - } - if (view.contains(startX, startY) || view.contains(endX, endY)) { - g.drawLine(startX, startY, endX, endY) + if (view.contains(startX, startY) || view.contains(endX, endY)) { + g.drawLine(startX, startY, endX, endY) + } } } } @@ -179,9 +178,9 @@ class GraphDrawer( } private fun canTravel(steps: StepValidator, x: Int, y: Int, level: Int, collision: CollisionStrategy) = steps.canTravel(x = x, z = y - 1, level = level, size = 1, offsetX = 0, offsetZ = 1, extraFlag = 0, collision = collision) || - steps.canTravel(x = x, z = y + 1, level = level, size = 1, offsetX = 0, offsetZ = -1, extraFlag = 0, collision = collision) || - steps.canTravel(x = x - 1, z = y, level = level, size = 1, offsetX = 1, offsetZ = 0, extraFlag = 0, collision = collision) || - steps.canTravel(x = x + 1, z = y, level = level, size = 1, offsetX = -1, offsetZ = 0, extraFlag = 0, collision = collision) + steps.canTravel(x = x, z = y + 1, level = level, size = 1, offsetX = 0, offsetZ = -1, extraFlag = 0, collision = collision) || + steps.canTravel(x = x - 1, z = y, level = level, size = 1, offsetX = 1, offsetZ = 0, extraFlag = 0, collision = collision) || + steps.canTravel(x = x + 1, z = y, level = level, size = 1, offsetX = -1, offsetZ = 0, extraFlag = 0, collision = collision) /** * Draws an arrow of [length] at [offset] along the line [x1], [y1] -> [x2], [y2] diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/HighlightedArea.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/HighlightedArea.kt index ec994fe149..b3e961a1d4 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/HighlightedArea.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/HighlightedArea.kt @@ -47,7 +47,7 @@ class HighlightedArea(private val view: MapView, private val area: AreaSet) { is Polygon -> g.drawPolygon(shape) is Rectangle -> g.drawRect(shape.x, shape.y, shape.width, shape.height) } - g.drawString(area.name ?: "", 5, 90 + index * 15) + g.drawString(area.name ?: "", 5, 120 + index * 15) } } } diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/MapView.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/MapView.kt index 05766037f3..fe4c8cdcfa 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/MapView.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/MapView.kt @@ -1,7 +1,10 @@ package world.gregs.voidps.tools.map.view.draw -import content.bot.interact.navigation.graph.NavigationGraph +import content.bot.behaviour.navigation.NavigationGraph import kotlinx.coroutines.* +import world.gregs.voidps.engine.data.ConfigFiles +import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.configFiles import world.gregs.voidps.tools.map.view.draw.WorldMap.Companion.flipRegionY import world.gregs.voidps.tools.map.view.graph.AreaSet import world.gregs.voidps.tools.map.view.interact.MouseDrag @@ -16,10 +19,10 @@ import java.awt.Graphics import javax.swing.JPanel import javax.swing.SwingUtilities -class MapView(nav: NavigationGraph?, private val areaFiles: List) : JPanel() { +class MapView : JPanel() { private val options = OptionsPane(this) - private val areaSet = AreaSet.load(areaFiles) + private val areaSet = AreaSet() private val highlight = HighlightedTile(this, options) private val area = HighlightedArea(this, areaSet) @@ -28,7 +31,7 @@ class MapView(nav: NavigationGraph?, private val areaFiles: List) : JPan private val hover = MouseHover(highlight, area) private val map = WorldMap(this) private val resize = ResizeListener(map) - private val graph = GraphDrawer(this, nav, areaSet) + private val graph = GraphDrawer(this, areaSet) // private val click = MouseClick(this, nav, graph, area, areaSet) private val apc = AreaPointConnector(this, areaSet) @@ -87,6 +90,12 @@ class MapView(nav: NavigationGraph?, private val areaFiles: List) : JPan area.update(x, y) } + fun reload(files: ConfigFiles = configFiles()) { + val areaFiles = files.list(Settings["map.areas"]) + AreaSet.load(areaFiles, areaSet) + graph.reload(NavigationGraph.loadGraph(files.list(Settings["bots.nav.definitions"]), emptyList())) + } + fun updateLevel(level: Int) { if (this.level != level) { this.level = level diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/graph/AreaSet.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/graph/AreaSet.kt index 4380ec5a84..6092f2cb61 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/graph/AreaSet.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/graph/AreaSet.kt @@ -69,8 +69,8 @@ class AreaSet { // writer.writeValue(File(path), set.areas) } - fun load(paths: List): AreaSet { - val set = AreaSet() + fun load(paths: List, set: AreaSet) { + set.areas.clear() val areas = mutableListOf() for (path in paths) { Config.fileReader(path) { @@ -113,7 +113,6 @@ class AreaSet { } } set.areas.addAll(areas) - return set } } } diff --git a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/ui/OptionsPane.kt b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/ui/OptionsPane.kt index 6403704653..a9aa74ec60 100644 --- a/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/ui/OptionsPane.kt +++ b/tools/src/main/kotlin/world/gregs/voidps/tools/map/view/ui/OptionsPane.kt @@ -55,5 +55,17 @@ class OptionsPane(private val view: MapView) : JPanel() { ) } add(levelControls) + + val refresh = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + add( + JButton("Refresh").apply { + addActionListener { + view.reload() + } + }, + ) + } + add(refresh) } }