local S = core.get_translator(core.get_current_modname()) local tt_help = S("Flight Duration:") local description = S("Firework Rocket") local TAU = 2*math.pi local function explode(self, pos, stars) mcl_mobs.mob_class.safe_boom(self, pos, 1) if not stars then return end for _, effect in pairs(stars) do if type(effect) == "string" then effect = core.deserialize(effect) end if effect.fn == "generic" then vl_fireworks.generic_particle_explosion(pos, effect.size or 1) end -- TODO implement other handlers end end local firework_entity = { physical = true, pointable = false, visual = "mesh", visual_size = {x=3, y=3}, mesh = "vl_fireworks_rocket.obj", textures = {"vl_fireworks_entity.png"}, backface_culling = false, collisionbox = {-0.1, 0, -0.1, 0.1, 0.5, 0.1}, collide_with_objects = false, liquid_drag = true, _fire_damage_resistant = true, _save_fields = { "last_pos", "vl_projectile", "dir", "rot_axis", "force", "stars" }, _vector_save_fields = { last_pos = true, dir = true, rot_axis = true, force = true }, _damage=1, -- Damage on impact _blocked = false, _viscosity=0, -- Viscosity of node the arrow is currently in _vl_projectile = { ignore_gravity = true, survive_collision = false, damages_players = true, maximum_time = 3, pitch_offset = -math.pi / 2, damage_groups = function(self) return { fleshy = vector.length(self.object:get_velocity()) } end, tracer_texture = "mobs_mc_arrow_particle.png", behaviors = { vl_projectile.burns, vl_projectile.has_tracer, function(self, dtime) if self._vl_projectile.extra then local e = self._vl_projectile.extra self._force = e.force/10 + 5 self._vl_projectile.maximum_time = e.dur self._rot_axis = e.rot_axis self._dir = self.object:get_velocity():normalize() self._stars = e.stars self._vl_projectile.extra = nil end if not self._dir then return end if self._last_pos and (self._last_pos - self.object:get_pos()):length() < (10*dtime) then self._rot_axis = -self._rot_axis end self._dir = self._dir:rotate_around_axis(self._rot_axis, dtime/3) local obj = self.object obj:set_velocity((obj:get_velocity():length() + self._force*dtime) * self._dir) end, vl_projectile.collides_with_solids, vl_projectile.raycast_collides_with_entities, }, allow_punching = function(self, entity_def, projectile_def, object) local lua = object:get_luaentity() if lua and lua.name == "mobs_mc:rover" then return false end -- TODO at some point make it so impact depends on collision speed? (see next line) --if (self.object:get_velocity() + object:get_velocity()).length() < 5 then return end return true end, }, get_staticdata = function(self) local out = {} local save_fields = self._save_fields for i = 1,#save_fields do local field = save_fields[i] out[field] = self["_"..field] end out.timer = self.timer -- Preserve entity properties out.properties = self.object:get_properties() return core.serialize(out) end, on_activate = function(self, staticdata, dtime_s) self.object:set_armor_groups({ immortal = 1 }) self._time_in_air = 1.0 local data = core.deserialize(staticdata) if not data then return end -- Restore entity properties if data.properties then self.object:set_properties(data.properties) data.properties = nil end self.timer = data.timer -- Restore rocket state local save_fields = self._save_fields local vecs = self._vector_save_fields for i = 1,#save_fields do local field = save_fields[i] local d = data[field] if type(d) == "table" and vecs[field] then d = vector.new(d.x, d.y, d.z) end self["_"..field] = d end if not self._vl_projectile then self._vl_projetile = {} end end, _on_remove = function(self) explode(self, self.object:get_pos(), self._stars) end, } vl_projectile.register("vl_fireworks:rocket", firework_entity) function vl_fireworks.shoot_firework(itemstack, pos, dir) local meta = itemstack:get_meta() local rot_axis = vector.new(1,0,0) rot_axis = rot_axis:rotate_around_axis(vector.new(0,1,0), math.random()*TAU) local stars = meta:get("vl_fireworks:stars") or core.serialize({}) vl_projectile.create("vl_fireworks:rocket", { pos = pos, dir = dir or vector.new(0,1,0), velocity = 1 + meta:get_int("vl_fireworks:force")/10, extra = { dur = meta:get_float("vl_fireworks:duration"), force = meta:get_float("vl_fireworks:force"), rot_axis = rot_axis, stars = core.deserialize(stars) } }) end local firework_def = { description = description, inventory_image = "vl_fireworks_rocket.png", stack_max = 64, on_use = function(itemstack, user, pointed_thing) local elytra = mcl_playerplus.elytra[user] if elytra.active and elytra.rocketing <= 0 then local meta = itemstack:get_meta() elytra.rocketing = meta:get_float("vl_fireworks:duration") if not core.is_creative_enabled(user:get_player_name()) then itemstack:take_item() end core.sound_play("vl_fireworks_rocket", {pos = user:get_pos()}) end return itemstack end, on_place = function(itemstack, user, pointed_thing) local new_stack = mcl_util.call_on_rightclick(itemstack, user, pointed_thing) if new_stack then return new_stack end local pos = pointed_thing.above vl_fireworks.shoot_firework(itemstack, pos) if mcl_gamemode.get_gamemode(user) ~= "creative" then itemstack:take_item() return itemstack end end, _on_dispense = function(dropitem, pos, droppos, dropnode, dropdir) vl_fireworks.shoot_firework(dropitem, pos, dropdir) end, _vl_fireworks_std_durs_forces = { {2.2, 10}, {4.5, 20}, {6, 30} }, _vl_fireworks_tt = function(duration, stars) local retval = tt_help .. " " .. duration for _, effect in pairs(stars) do if type(effect) == "string" then effect = core.deserialize(effect) end retval = retval .. "\n\n" .. vl_fireworks.star_tt(effect) end return retval end, } vl_fireworks.firework_def = table.copy(firework_def) core.register_craftitem("vl_fireworks:rocket", firework_def) -- legacy for i=1, 3 do vl_legacy.register_item_conversion("mcl_fireworks:rocket_"..i, nil, function(itemstack) itemstack:set_name("vl_fireworks:rocket") local meta = itemstack:get_meta() local tbl = vl_fireworks.firework_def._vl_fireworks_std_durs_forces[i] meta:set_float("vl_fireworks:duration", tbl[1]) meta:set_int("vl_fireworks:force", tbl[2]) tt.reload_itemstack_description(itemstack) end) end