1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-10-30 23:47:41 +02:00

new module and changes to rpg and mtn v3

This commit is contained in:
Gerkiz
2022-07-19 00:25:42 +02:00
parent 1f03bcec9e
commit 664b400198
12 changed files with 692 additions and 23 deletions

View File

@@ -130,6 +130,9 @@ pointy_explosives=Detonate Chest
repair_aoe=Repair AOE
charge=Charge
eternal_blades=Eternal Blades
drone_enemy=Drone - Enemy
drone_mine=Drone - Mine
[allocations]

View File

@@ -13,7 +13,7 @@ local function on_entity_died(event)
end
local wagon_types = ICW.get('wagon_types')
if not wagon_types[entity.type] then
if entity and entity.valid and not wagon_types[entity.type] then
return
end
local icw = ICW.get()

View File

@@ -1,8 +1,11 @@
local WPT = require 'maps.mountain_fortress_v3.table'
local RPG = require 'modules.rpg.main'
local Event = require 'utils.event'
local Ai = require 'modules.ai'
require 'modules.check_fullness'
local Public = {}
local Public = {events = {on_entity_mined = Event.generate_event_name('on_entity_mined')}}
local random = math.random
local floor = math.floor
local sqrt = math.sqrt
@@ -337,7 +340,12 @@ local function randomness(data)
end
end
local particle = particles[harvest]
create_particles(player.surface, particle, position, 64, {x = player.position.x, y = player.position.y})
if data.script_character then
create_particles(player.surface, particle, position, 64, {x = data.script_character.position.x, y = data.script_character.position.y})
else
create_particles(player.surface, particle, position, 64, {x = player.position.x, y = player.position.y})
end
end
local function randomness_scrap(data)
@@ -390,7 +398,11 @@ local function randomness_scrap(data)
end
end
local particle = particles[harvest]
create_particles(player.surface, particle, position, 64, {x = player.position.x, y = player.position.y})
if data.script_character then
create_particles(player.surface, particle, position, 64, {x = data.script_character.position.x, y = data.script_character.position.y})
else
create_particles(player.surface, particle, position, 64, {x = player.position.x, y = player.position.y})
end
end
function Public.on_player_mined_entity(event)
@@ -413,13 +425,19 @@ function Public.on_player_mined_entity(event)
local buffer = event.buffer
if valid_rocks[entity.name] or valid_trees[entity.name] or is_scrap then
buffer.clear()
if buffer then
buffer.clear()
end
local data = {
entity = entity,
player = player
}
if event.script_character then
data.script_character = event.script_character
end
local index = player.index
local scrap_zone = RPG.get_value_from_player(index, 'scrap_zone')
@@ -433,4 +451,26 @@ function Public.on_player_mined_entity(event)
end
end
Event.add(
Public.events.on_entity_mined,
function(event)
if not event then
return
end
Public.on_player_mined_entity(event)
end
)
Event.add(
Ai.events.on_entity_mined,
function(event)
if not event then
return
end
Public.on_player_mined_entity(event)
end
)
return Public

View File

@@ -9,7 +9,7 @@ local Server = require 'utils.server'
local MapFuntions = require 'tools.map_functions'
local CommonFunctions = require 'utils.common'
local LayersFunctions = require 'maps.planet_prison.mod.layers'
local AIFunctions = require 'utils.ai'
local AIFunctions = require 'maps.planet_prison.ai'
local Blueprints = require 'maps.planet_prison.mod.bp'
local AfkFunctions = require 'maps.planet_prison.mod.afk'
local Timers = require 'utils.timers'

531
modules/ai.lua Normal file
View File

@@ -0,0 +1,531 @@
--- created by Gerkiz
local Event = require 'utils.event'
local Color = require 'utils.color_presets'
local Utils = require 'utils.common'
local Global = require 'utils.global'
local Token = require 'utils.token'
local Task = require 'utils.task'
local this = {
timers = {},
characters = {},
characters_unit_numbers = {}
}
Global.register(
this,
function(tbl)
this = tbl
end
)
local Public = {events = {on_entity_mined = Event.generate_event_name('on_entity_mined')}}
local max_keepalive = 54000 -- 15 minutes
local remove = table.remove
local round = math.round
local default_radius = 5
Public.command = {
noop = 0,
seek_and_destroy_cmd = 1,
seek_and_mine_cmd = 2
}
local clear_corpse_token =
Token.register(
function(event)
local position = event.position
local surface = game.get_surface(event.surface_index)
local search_info = {
type = 'character-corpse',
position = position,
radius = 1
}
local corpses = surface.find_entities_filtered(search_info)
if corpses and #corpses > 0 then
for _, corpse in pairs(corpses) do
if corpse and corpse.valid then
if corpse.character_corpse_player_index == 65536 then
corpse.destroy()
end
end
end
end
end
)
local function char_callback(callback)
local entities = this.characters
for i = 1, #entities do
local data = entities[i]
if data and data.entity and data.entity.valid then
callback(data)
end
end
end
local function get_near_position(entity)
return {x = round(entity.position.x, 0), y = round(entity.position.y, 0)}
end
local function is_mining_target_taken(selected)
if not selected then
return false
end
char_callback(
function(data)
local entity = data.entity
if entity.selected == selected then
return true
end
end
)
return false
end
local function add_character(player_index, entity, render_id, data)
local index = #this.characters + 1
if not this.characters[index] then
this.characters[index] = {
player_index = player_index,
index = index,
unit_number = entity.unit_number,
entity = entity,
ttl = game.tick + (data.ttl or max_keepalive),
command = data.command,
radius = default_radius,
max_radius_mine = 20,
max_radius_destroy = 150,
render_id = render_id,
search_local = data.search_local or false,
walking_position = {count = 1, position = get_near_position(entity)}
}
end
if not this.characters_unit_numbers[entity.unit_number] then
this.characters_unit_numbers[entity.unit_number] = true
end
end
local function exists_character(unit_number)
if not next(this.characters_unit_numbers) then
return
end
if this.characters_unit_numbers[unit_number] then
return true
end
return false
end
local function remove_character(unit_number)
if not next(this.characters) then
return
end
for index, data in pairs(this.characters) do
if data and data.unit_number == unit_number then
if data.entity and data.entity.valid then
data.entity.destroy()
end
if rendering.is_valid(data.render_id) then
rendering.destroy(data.render_id)
end
remove(this.characters, index)
end
end
if this.characters_unit_numbers[unit_number] then
this.characters_unit_numbers[unit_number] = nil
end
end
local function get_dir(src, dest)
local src_x = Utils.get_axis(src, 'x')
local src_y = Utils.get_axis(src, 'y')
local dest_x = Utils.get_axis(dest, 'x')
local dest_y = Utils.get_axis(dest, 'y')
local step = {
x = nil,
y = nil
}
local precision = Utils.rand_range(1, 10)
if dest_x - precision > src_x then
step.x = 1
elseif dest_x < src_x - precision then
step.x = -1
else
step.x = 0
end
if dest_y - precision > src_y then
step.y = 1
elseif dest_y < src_y - precision then
step.y = -1
else
step.y = 0
end
return Utils.direction_lookup[step.x][step.y]
end
local function move_to(entity, target, min_distance)
local state = {
walking = false
}
local distance = Utils.get_distance(target.position, entity.position)
if min_distance < distance then
local dir = get_dir(entity.position, target.position)
if dir then
state = {
walking = true,
direction = dir
}
end
end
entity.walking_state = state
return state.walking
end
local function refill_ammo(entity)
if not entity or not entity.valid then
return
end
local weapon = entity.get_inventory(defines.inventory.character_guns)[entity.selected_gun_index]
if weapon and weapon.valid_for_read then
local selected_ammo = entity.get_inventory(defines.inventory.character_ammo)[entity.selected_gun_index]
if selected_ammo then
if not selected_ammo.valid_for_read then
if weapon.name == 'shotgun' then
entity.insert({name = 'shotgun-shell', count = 5})
end
if weapon.name == 'pistol' then
entity.insert({name = 'firearm-magazine', count = 5})
end
end
end
end
end
local function shoot_at(entity, target)
entity.selected = target
entity.shooting_state = {
state = defines.shooting.shooting_enemies,
position = target.position
}
end
local function check_progress_and_raise_event(data)
if data.entity.selected and data.entity.character_mining_progress >= 0.95 then
if not data.raised_event then
data.raised_event = true
Event.raise(
Public.events.on_entity_mined,
{
player_index = data.player_index,
entity = data.entity.selected,
surface = data.entity.surface,
script_character = data.entity
}
)
end
end
end
local function mine_entity(data, target)
data.entity.selected = target
data.entity.mining_state = {mining = true, position = target.position}
end
local function shoot_stop(entity)
entity.shooting_state = {
state = defines.shooting.not_shooting,
position = {0, 0}
}
end
local function insert_weapons(entity)
if not entity or not entity.valid then
return
end
local weapon = entity.get_inventory(defines.inventory.character_guns)[entity.selected_gun_index]
if weapon and weapon.valid_for_read then
return
end
if Utils.rand_range(1, 15) == 1 then
entity.insert({name = 'shotgun', count = 1})
entity.insert({name = 'shotgun-shell', count = 5})
elseif Utils.rand_range(1, 10) == 1 then
entity.insert({name = 'submachine-gun', count = 1})
entity.insert({name = 'firearm-magazine', count = 5})
else
entity.insert({name = 'pistol', count = 1})
entity.insert({name = 'firearm-magazine', count = 5})
end
end
local function seek_and_mine(data)
if data.radius >= data.max_radius_mine then
if data.overriden_command then
data.command = data.overriden_command
data.overriden_command = nil
return
else
data.radius = 1
end
end
local entity = data.entity
if not entity or not entity.valid then
remove_character(data.unit_number)
return
end
local surface = entity.surface
local player_index = data.player_index
local player = game.get_player(player_index)
if not player or not player.valid or not player.connected then
remove_character(data.unit_number)
return
end
local position
if data.search_local then
position = entity.position
else
position = player.position
end
local search_info = {
position = position,
radius = data.radius,
type = {
'simple-entity-with-owner',
'simple-entity',
'tree'
},
force = {
'neutral'
}
}
local closest = surface.find_entities_filtered(search_info)
if #closest ~= 0 then
local target = Utils.get_closest_neighbour_non_player(entity.position, closest)
if not target then
data.radius = data.radius + 1
return
end
data.radius = 1
if not move_to(entity, target, 1) then
if not is_mining_target_taken(target) then
if data.raised_event then
data.raised_event = nil
end
if entity.can_reach_entity(target) then
mine_entity(data, target)
else
move_to(entity, target, 1)
end
end
if data.overriden_command then
data.command = data.overriden_command
data.overriden_command = nil
end
end
else
data.radius = data.radius + 1
end
end
local function seek_enemy_and_destroy(data)
if data.radius >= data.max_radius_destroy then
remove_character(data.unit_number)
return
end
local entity = data.entity
if not entity or not entity.valid then
remove_character(data.unit_number)
return
end
local surface = entity.surface
local player_index = data.player_index
local player = game.get_player(player_index)
if not player or not player.valid or not player.connected then
remove_character(data.unit_number)
return
end
local search_info = {
type = {'unit', 'unit-spawner', 'turret'},
position = entity.position,
radius = data.radius,
force = 'enemy'
}
local closest = surface.find_entities_filtered(search_info)
if #closest ~= 0 then
local target = Utils.get_closest_neighbour_non_player(entity.position, closest)
if not target then
data.radius = data.radius + 5
return
end
data.radius = default_radius
insert_weapons(entity)
refill_ammo(entity)
local inside = ((entity.position.x - data.walking_position.position.x) ^ 2 + (entity.position.y - data.walking_position.position.y) ^ 2) < 1 ^ 2
data.walking_position.position = get_near_position(entity)
if inside then
data.walking_position.count = data.walking_position.count + 1
end
if data.walking_position.count == 3 then
data.radius = 1
data.walking_position.count = 1
data.overriden_command = data.command
data.command = Public.command.seek_and_mine_cmd
seek_and_mine(data)
else
if not move_to(entity, target, Utils.rand_range(5, 10)) then
shoot_at(entity, target)
else
shoot_stop(entity)
end
end
else
data.radius = data.radius + 5
end
end
--- Creates a new character that seeks and does stuff.
---@param data table
----- @usage local Ai = require 'modules.ai' Ai.create_char({player_index = game.player.index, command = 1})
function Public.create_char(data)
if not data or not type(data) == 'table' then
return error('No data was provided or the provided data was not a table.', 2)
end
if not data.player_index or not data.command then
return error('No correct data was not provided.', 2)
end
if data.command ~= Public.command.seek_and_destroy_cmd and data.command ~= Public.command.attack_objects_cmd and data.command ~= Public.command.seek_and_mine_cmd then
return error('No correct command was not provided.', 2)
end
local player = game.get_player(data.player_index)
if not player or not player.valid or not player.connected then
return error('Provided player was not valid or not connected.', 2)
end
local surface = player.surface
local valid_position = surface.find_non_colliding_position('character', {x = player.position.x, y = player.position.y + 2}, 3, 0.5)
if not valid_position then
return
end
local entity = surface.create_entity {name = 'character', position = valid_position, force = player.force}
if not entity or not entity.valid then
return
end
entity.associated_player = player
if player.character_health_bonus >= 200 then
entity.character_health_bonus = player.character_health_bonus / 2
end
entity.color = player.color
local index = #this.characters + 1
local render_id =
rendering.draw_text {
text = player.name .. "'s drone #" .. index,
surface = player.surface,
target = entity,
target_offset = {0, -2.25},
color = Color.orange,
scale = 1.00,
font = 'default-large-semibold',
alignment = 'center',
scale_with_zoom = false
}
add_character(player.index, entity, render_id, data)
end
Event.on_nth_tick(
2,
function()
char_callback(
function(data)
check_progress_and_raise_event(data)
end
)
end
)
Event.on_nth_tick(
10,
function()
local tick = game.tick
char_callback(
function(data)
if data.ttl <= tick then
remove_character(data.unit_number)
return
end
local command = data.command
if command == Public.command.seek_and_destroy_cmd then
seek_enemy_and_destroy(data)
elseif command == Public.command.seek_and_mine_cmd then
seek_and_mine(data)
end
end
)
end
)
Event.add(
defines.events.on_entity_died,
function(event)
local entity = event.entity
if not entity or not entity.valid then
return
end
if entity.type ~= 'character' then
return
end
local unit_number = entity.unit_number
if not exists_character(unit_number) then
return
end
Task.set_timeout_in_ticks(1, clear_corpse_token, {position = entity.position, surface_index = entity.surface.index})
remove_character(unit_number)
end
)
return Public

View File

@@ -910,12 +910,12 @@ local function on_player_used_capsule(event)
right_bottom = {x = position.x + radius, y = position.y + radius}
}
if rpg_t.level < spell.level then
if not spell.enabled then
return Public.cast_spell(player, true)
end
if not spell.enabled then
return
if rpg_t.level < spell.level then
return Public.cast_spell(player, true)
end
if not Math2D.bounding_box.contains_point(area, player.position) then

View File

@@ -28,7 +28,7 @@ local function get_comparator(sort_by)
return comparators[sort_by]
end
local function create_input_element(frame, type, value, items, index)
local function create_input_element(frame, type, value, items, index, tooltip)
if type == 'slider' then
return frame.add({type = 'slider', value = value, minimum_value = 0, maximum_value = 1})
end
@@ -38,6 +38,7 @@ local function create_input_element(frame, type, value, items, index)
if type == 'label' then
local label = frame.add({type = 'label', caption = value})
label.style.font = 'default-listbox'
label.tooltip = tooltip or ''
return label
end
if type == 'dropdown' then
@@ -46,7 +47,7 @@ local function create_input_element(frame, type, value, items, index)
return frame.add({type = 'text-box', text = value})
end
local function create_custom_label_element(frame, sprite, localised_string, value)
local function create_custom_label_element(frame, sprite, localised_string, value, tooltip)
local t = frame.add({type = 'flow'})
t.add({type = 'label', caption = '[' .. sprite .. ']'})
local heading = t.add({type = 'label', caption = localised_string})
@@ -54,6 +55,8 @@ local function create_custom_label_element(frame, sprite, localised_string, valu
local subheading = t.add({type = 'label', caption = value})
subheading.style.font = 'default-listbox'
t.tooltip = tooltip or ''
return subheading
end
@@ -710,13 +713,15 @@ function Public.settings_tooltip(player)
table.sort(spells, comparator)
for _, entity in pairs(spells) do
local cooldown = (entity.cooldown / 60) .. 's'
if entity.type == 'item' then
local text = '[item=' .. entity.entityName .. '] ▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_input_element(normal_spell_grid, 'label', text)
elseif entity.type == 'entity' then
local text = '[entity=' .. entity.entityName .. '] ▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_input_element(normal_spell_grid, 'label', text)
if entity.enabled then
local cooldown = (entity.cooldown / 60) .. 's'
if entity.type == 'item' then
local text = '[item=' .. entity.entityName .. '] ▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_input_element(normal_spell_grid, 'label', text, nil, nil, entity.tooltip)
elseif entity.type == 'entity' then
local text = '[entity=' .. entity.entityName .. '] ▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_input_element(normal_spell_grid, 'label', text, nil, nil, entity.tooltip)
end
end
end
@@ -741,10 +746,12 @@ function Public.settings_tooltip(player)
local special_spell_grid = special_spell_pane.add({type = 'table', column_count = 1})
for _, entity in pairs(spells) do
local cooldown = (entity.cooldown / 60) .. 's'
if entity.type == 'special' then
local text = '▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_custom_label_element(special_spell_grid, entity.special_sprite, entity.name, text)
if entity.enabled then
local cooldown = (entity.cooldown / 60) .. 's'
if entity.type == 'special' then
local text = '▪️ Level: [font=default-bold]' .. entity.level .. '[/font] Mana: [font=default-bold]' .. entity.mana_cost .. '[/font]. Cooldown: [font=default-bold]' .. cooldown .. '[/font]'
create_custom_label_element(special_spell_grid, entity.special_sprite, entity.name, text, entity.tooltip)
end
end
end
end

View File

@@ -1,6 +1,7 @@
local Public = require 'modules.rpg.table'
local Token = require 'utils.token'
local Task = require 'utils.task'
local Ai = require 'modules.ai'
local spells = {}
local random = math.random
@@ -1030,6 +1031,58 @@ spells[#spells + 1] = {
end
}
spells[#spells + 1] = {
name = {'spells.drone_enemy'},
entityName = 'drone_enemy',
target = false,
force = 'player',
level = 200,
type = 'special',
mana_cost = 1000,
cooldown = 18000,
enabled = false,
enforce_cooldown = true,
log_spell = true,
sprite = 'virtual-signal/signal-info',
special_sprite = 'virtual-signal=signal-info',
tooltip = 'Creates a drone that searches for enemies and destroys them.',
callback = function(data)
local self = data.self
local player = data.player
Ai.create_char({player_index = player.index, command = 1, search_local = true})
Public.cast_spell(player)
Public.remove_mana(player, self.mana_cost)
return true
end
}
spells[#spells + 1] = {
name = {'spells.drone_mine'},
entityName = 'drone_mine',
target = false,
force = 'player',
level = 200,
type = 'special',
mana_cost = 1000,
cooldown = 18000,
enabled = false,
enforce_cooldown = true,
log_spell = true,
sprite = 'virtual-signal/signal-info',
special_sprite = 'virtual-signal=signal-info',
tooltip = 'Creates a drone mines entities around you.',
callback = function(data)
local self = data.self
local player = data.player
Ai.create_char({player_index = player.index, command = 2, search_local = false})
Public.cast_spell(player)
Public.remove_mana(player, self.mana_cost)
return true
end
}
Public.projectile_types = {
['explosives'] = {name = 'grenade', count = 0.5, max_range = 32, tick_speed = 1},
['distractor-capsule'] = {name = 'distractor-capsule', count = 1, max_range = 32, tick_speed = 1},

View File

@@ -162,7 +162,7 @@ function Public.get(key)
end
--- Gets value from player rpg_t table
---@param key string
---@param key string|integer
---@param value string|nil
function Public.get_value_from_player(key, value)
if key and value then

View File

@@ -554,4 +554,32 @@ Public.get_closest_neighbour = function(position, objects)
return closest
end
--[[
get_closest_neighbour - Return object whose is closest to given position.
@param position - Position, origin point
@param object - Table of objects that have any sort of position datafield.
--]]
Public.get_closest_neighbour_non_player = function(position, objects)
local closest = objects[1]
local min_dist = Public.get_distance(position, closest)
local object, dist
for i = #objects, 1, -1 do
object = objects[i]
if object and object.valid and object.destructible then
dist = Public.get_distance(position, object)
if dist < min_dist then
closest = object
min_dist = dist
end
end
end
if closest and closest.valid and not closest.destructible then
return false
end
return closest
end
return Public

View File

@@ -117,4 +117,11 @@ Module.format_time = function(ticks)
return table.concat(result, ' ')
end
function Module.inside(pos, area)
local lt = area.left_top
local rb = area.right_bottom
return pos.x >= lt.x and pos.y >= lt.y and pos.x <= rb.x and pos.y <= rb.y
end
return Module