[A23] Improving the Lua: a modder's insight (devs, please read)

As I was modding various things, autoharvest for renewables being one of them, I found areas where small additions to the code could benefit the possibilities a lot. I decided to list them here.

task_tracker_component.lua: in many cases, especially with more than one player, it would be beneficial to be able to inform components about task cancellations so they can double-check how their situation has changed; this solution has practically no impact on performance as it is just a single event:

function TaskTrackerComponent:cancel_current_task(should_reconsider_ai)
   log:debug('Entity %s: cancelling current task \"%s\"', self._entity, tostring(self._sv.task_activity_name))

   local can_reconsider = false
   if self._sv.task_player_id ~= nil then
      can_reconsider = true
   end

   if self._task_effect then
      log:debug('Entity %s: removing effect "%s"', self._entity, self._sv._task_effect_name)
      self._task_effect:stop()
      self._task_effect = nil
   end
   if self._position_trace then
      self._position_trace:destroy()
      self._position_trace = nil
   end

   -- ADDITION: it is necessary to store event data prior to cancellation
   local cancel_info = {entity = self._entity, player_id = self._sv.task_player_id, category = self._sv.task_category, activity_name = self._sv.task_activity_name}

   self._sv.task_player_id = nil
   self._sv.task_category = nil
   self._sv.task_activity_name = nil
   self._sv._task_effect_name = nil
   self._sv._cancelled_if_entity_moves = false

   if can_reconsider and should_reconsider_ai then
      stonehearth.ai:reconsider_entity(self._entity, 'task tracker cancelling task')
   end

   self.__saved_variables:mark_changed()

   -- ADDITION: trigger an event so components can notice a task being removed and react properly
   radiant.events.trigger(self._entity, 'stonehearth:task_tracker:task_cancelled', cancel_info)

   return self
end

place_item_call_handler.lua: this is not an optimal solution, but there is no way to restock objects which have no player ID assigned (e.g. berry bushes) so they cannot be undeployed; assigning player ID during undeploying is a temporary solution as it won’t work properly in mulitplayer:

function PlaceItemCallHandler:undeploy_item(session, response, item)
   local efc = item:get_component('stonehearth:entity_forms')
   if efc then
      -- ADDITION: setting the player ID here allows to undeploy items with no player ID
      radiant.entities.set_player_id(item, session.player_id)

      local current_value = efc:get_should_restock()
      efc:set_should_restock(not current_value)
   end

   radiant.events.trigger_async(item, 'stonehearth:item_undeployed')
   return true
end

renewable_resource_node_component.lua: here we have two places which can result in buggy behaviour:

  1. if the node is not harvestable on load starting model variant and description are not updated:
function RenewableResourceNodeComponent:post_activate()

   if not self._sv.renew_timer and not self._sv.harvestable then
      self:_update_renew_timer()
   end
   --If we're harvestable on load, fire the harvestable event again,
   --in case we need to reinitialize tasks and other nonsavables on the event
   if self._sv.harvestable then
      radiant.events.trigger(self._entity, 'stonehearth:on_renewable_resource_renewed', {target = self._entity, available_resource = self._resource})
   -- ADDITION: in case we are not harvestable on load we should use depleted model and description
   else
      local render_info = self._entity:add_component('render_info')
      render_info:set_model_variant('depleted')

      if self._json.unripe_description then
         radiant.entities.set_description(self._entity, self._json.unripe_description)
      end
   end
end
  1. unlike resource node, renewable resource node does not cancel tasks when harvest is requested:
function RenewableResourceNodeComponent:request_harvest(player_id)
   if not self:is_harvestable() then
      return false
   end

   local task_tracker_component = self._entity:add_component('stonehearth:task_tracker')
   if task_tracker_component:is_activity_requested(HARVEST_ACTION) then
      return false -- If someone has requested to harvest already
   end

   -- ADDITION: cancel tasks (pasted from resource_node_component.lua)
   task_tracker_component:cancel_current_task(false) -- cancel current task first and force the resource node harvest

   local json = self._json
   local category = json.category or 'harvest'
   local success = task_tracker_component:request_task(player_id, category, HARVEST_ACTION, json.harvest_overlay_effect)
   return success
end

farming_service.lua: it would be nice if it was possible to specify starting crop availability per biome as well as per kingdom:

function FarmingService:_get_crop_list(session)
   local player_id = session.player_id
   local crop_list = self._data.player_crops[player_id]
   if not crop_list then
      -- xxx: look this up from the player info when that is avaiable
      local kingdom = stonehearth.population:get_population(player_id):get_kingdom()
      -- start out with the default crops for this player's kingdom.
      crop_list = {}
      local all_crops = self._all_crops
      local kingdom_crops = self._initial_crops[kingdom]
      -- ADDITION: biome-enabled crops are added to the list of available crops
      ----[[
      local enabled_crops = kingdom_crops
      local biome = stonehearth.world_generation:get_biome_alias()
      local biome_crops = self._initial_crops[biome]
      if biome_crops then
         for key, crop in pairs (biome_crops) do
            enabled_crops[key] = crop
         end
      end
      --]]
      -- gather data when all available crops are listed in enabled_crops
      if enabled_crops and all_crops then
         for key, crop in pairs(all_crops) do
            crop_list[key] = {
               crop_key = key,
               crop_type = crop.crop_type,
               crop_info = self:get_crop_details(crop.crop_type),
               crop_level_requirement = crop.level_requirement,
               ordinal = crop.ordinal,
               initial_crop = enabled_crops[key]
            }
         end
      end
      self._data.player_crops[player_id] = crop_list
   end
   return crop_list
end

I decided to write this because after looking at code changes affecting my mods I noticed the new alpha uses a resource call handler fix for renewable and one-time resource nodes used in BrunoSupremo’s mods so I thought someone may find my observations useful.

4 Likes