-- custom rocket/grenade binder based on hawu impact grenade ini_ammo = ini_file("ammo\\importer.ltx") --[[ Credits: Thial ]] class "geometry_ray" --[[ (At least one range parameter should be specified) ray_range: Defines the total range of the ray. If you want to attach the ray to a fast moving object it is good to extend the ray so that you can reduce the polling rate by using the get function. contact_range: Defines the distance at which the result will report being in contact. You can skip it or set it to a value lower than the ray_range to still be able to get the intersection position from the result while marking the ray as not being in contact yet distance_offset: Defines how much the intersection position is offset. You can use both positive and negative values or you can leave it blank. flags (bit map = values can be added together for combined effect): 0 : None 1 : Objects 2 : Statics 4 : Shapes 8 : Obstacles ]]-- function geometry_ray:__init(ray_range, contact_range, distance_offset, flags) if ray_range == nil and contact_range == nil then return nil end self.ray_range = ray_range or contact_range self.contact_range = contact_range or ray_range self.distance_offset = distance_offset ~= nil and distance_offset or 0 self.ray = ray_pick() self.ray:set_ignore_object(db.actor) self.ray:set_flags(flags or 2) self.ray:set_range(self.ray_range) end --[[ position: position from which the ray will start direction: direction in which the ray will be fired ]]-- function geometry_ray:get(position, direction) if position == nil or direction == nil then return nil end self.ray:set_position(position) self.ray:set_direction(direction) local distance = self.ray:query() and self.ray:get_distance() or self.ray_range local result = {} result.in_contact = distance <= self.contact_range result.position = position:add(direction:mul(distance + self.distance_offset)) result.distance = distance + self.distance_offset return result end function bind(obj) if (not alife_object(obj:id())) then obj:bind_object(custom_grenade_binder(obj)) end end --[[ -------------------------------------------------------------------------------- Class "custom_grenade_binder" Usage is trickier than for bullets. In order to use it the following steps are required: 1. grenade ammo section needs to define a custom fake_grenade_name 2. said fake_grenade_name needs line `script_binding = bind_grenade.bind` 3. when projectile hits the ground, custom explosion will be triggered (default does nothing) 4. Default explosion spawns item defined by `bomb_type` and immediately detonates if possible -------------------------------------------------------------------------------- ]] local VEC_GROUND = vector():set(0, -1, 0) class "custom_grenade_binder" (object_binder) function custom_grenade_binder:__init(obj) super(obj) self.sec = self.object:section() self.ray = geometry_ray(0.11, 0.1, nil, 1 + 2) self.ray.ray:set_ignore_object(obj) -- self.ray = geometry_ray(40, 2, nil, 1 + 2) -- self.ray.ray:set_ignore_object(obj) -- self.frags_r = SYS_GetParam(2, self.sec, "frags_r", 6) -- self.velocity_threshold = SYS_GetParam(2, self.sec, "velocity_threshold", 0.36) self.tg_period = 33 self.object:set_fastcall(self.fastcall, self) end function custom_grenade_binder:fastcall() local obj = self.object if obj:parent() then return end -- shamelessly stolen from molotovs if (not self.position) then -- self.velocity = 0 self.position = obj:position() self.direction = VEC_GROUND self.tg = 0 self.tg_time = 0 return end self.tg = self.tg + device().time_delta if self.tg < self.tg_time then return end self.tg_time = self.tg + self.tg_period local pos = obj:position() local dir = vector():set(pos):sub(self.position) local result = self.ray:get(pos, dir) if result.in_contact or self.tg_time > 5000 then res = self:explode(pos, dir) self.object:destroy_object() end end local func local last_ammo function custom_grenade_binder:explode(pos, dir) if not last_ammo then local weapon = self.object:parent() or db.actor:active_item() local ammo = "ammo_vog-25" if weapon ~= nil then ammo_map = utils_item.get_ammo(nil, weapon:id()) if not is_empty(ammo_map) then ammo = ammo_map[weapon:get_ammo_type() + 1] end end last_ammo = ammo func = ballistics_mcm.get_handler(ammo) end if func and func[1] and func[2] and _G[func[1]] and _G[func[1]][func[2]] then _G[func[1]][func[2]](pos, dir, last_ammo) end func = nil end function custom_grenade_binder:net_spawn(se_abstract) if (not object_binder.net_spawn(self, se_abstract)) then return false end return true end -- lock the grenade handler for the next round function actor_on_weapon_before_fire(flags) local weapon = db.actor:active_item() if weapon and (weapon:weapon_in_grenade_mode() or weapon:clsid() == clsid.wpn_rg6_s or weapon:clsid() == clsid.wpn_rg6) then local ammo = "ammo_vog-25" if weapon ~= nil then ammo_map = utils_item.get_ammo(nil, weapon:id()) if not is_empty(ammo_map) then ammo = ammo_map[weapon:get_ammo_type() + 1] end end func = ballistics_mcm.get_handler(ammo) end end function explode(pos, dir, ammo) local lvid = db.actor:level_vertex_id() local gvid = db.actor:game_vertex_id() local bomb_type = ini_ammo:r_string_ex(ammo, "bomb_type") or "" if not bomb_type then return end local bomb = alife_create_item(bomb_type, {pos, lvid, gvid}) CreateTimeEvent(bomb.id, bomb.id, 0, function() local bomb_obj = level.object_by_id(bomb.id) if bomb_obj then bomb_obj:explode(0) return true else return false end end) end function on_game_start() RegisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire) func = nil end