diff --git a/mods/ENTITIES/mcl_mobs/combat.lua b/mods/ENTITIES/mcl_mobs/combat.lua index 0ddc62d29..4acbdb14e 100644 --- a/mods/ENTITIES/mcl_mobs/combat.lua +++ b/mods/ENTITIES/mcl_mobs/combat.lua @@ -1216,6 +1216,9 @@ function mob_class:do_states_attack (dtime) end end end + + elseif self.attack_type == "custom" and self.attack_state then + self.attack_state(self, dtime) else end diff --git a/mods/ENTITIES/mcl_mobs/init.lua b/mods/ENTITIES/mcl_mobs/init.lua index 58e418018..25440129d 100644 --- a/mods/ENTITIES/mcl_mobs/init.lua +++ b/mods/ENTITIES/mcl_mobs/init.lua @@ -312,6 +312,7 @@ function mcl_mobs.register_mob(name, def) return self:mob_activate(staticdata, def, dtime) end, + attack_state = def.attack_state, harmed_by_heal = def.harmed_by_heal, is_boss = def.is_boss, dealt_effect = def.dealt_effect, @@ -348,6 +349,7 @@ function mcl_mobs.register_arrow(name, def) collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows timer = 0, switch = 0, + _lifetime = def._lifetime or 150, owner_id = def.owner_id, rotate = def.rotate, on_punch = function(self) @@ -367,7 +369,7 @@ function mcl_mobs.register_arrow(name, def) local pos = self.object:get_pos() if self.switch == 0 - or self.timer > 150 + or self.timer > self._lifetime or not within_limits(pos, 0) then mcl_burning.extinguish(self.object) self.object:remove(); diff --git a/mods/ENTITIES/mcl_wither_spawning/init.lua b/mods/ENTITIES/mcl_wither_spawning/init.lua index fa23c7063..cee95a905 100644 --- a/mods/ENTITIES/mcl_wither_spawning/init.lua +++ b/mods/ENTITIES/mcl_wither_spawning/init.lua @@ -59,11 +59,8 @@ local function wither_spawn(pos, player) if check_schem(p, schem) and check_limit(pos) then remove_schem(p, schem) local wither = minetest.add_entity(vector.add(p, {x = 0, y = 1, z = 0, [d] = 1}), "mobs_mc:wither") - local witherer = wither:get_luaentity() - witherer._spawner = player:get_player_name() - witherer._custom_timer = 0.0 - witherer._death_timer = 0.0 - witherer._health_old = witherer.hp_max + local wither_ent = wither:get_luaentity() + wither_ent._spawner = player:get_player_name() local dim = mcl_worlds.pos_to_dimension(pos) if dim == "overworld" then wboss_overworld = wboss_overworld + 1 diff --git a/mods/ENTITIES/mobs_mc/wither.lua b/mods/ENTITIES/mobs_mc/wither.lua index 997378a3a..671183340 100644 --- a/mods/ENTITIES/mobs_mc/wither.lua +++ b/mods/ENTITIES/mobs_mc/wither.lua @@ -4,6 +4,15 @@ --License for code WTFPL and otherwise stated in readmes local S = minetest.get_translator("mobs_mc") +local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false + +local function atan(x) + if not x or x ~= x then + return 0 + else + return math.atan(x) + end +end --################### --################### WITHER @@ -31,10 +40,11 @@ mcl_mobs.register_mob("mobs_mc:wither", { }, visual_size = {x=4, y=4}, makes_footstep_sound = true, - view_range = 16, + view_range = 50, fear_height = 4, walk_velocity = 2, run_velocity = 4, + strafes = true, sounds = { shoot_attack = "mobs_mc_ender_dragon_shoot", attack = "mobs_mc_ender_dragon_attack", @@ -57,7 +67,7 @@ mcl_mobs.register_mob("mobs_mc:wither", { }, lava_damage = 0, fire_damage = 0, - attack_type = "shoot", + attack_type = "custom", explosion_strength = 8, dogshoot_stop = true, arrow = "mobs_mc:wither_skull", @@ -73,33 +83,71 @@ mcl_mobs.register_mob("mobs_mc:wither", { harmed_by_heal = true, is_boss = true, do_custom = function(self, dtime) + if self._spawning then + if not self._spw_max then self._spw_max = self._spawning end + self._spawning = self._spawning - dtime + local bardef = { + color = "dark_purple", + text = "Wither spawning", + percentage = math.floor((self._spw_max - self._spawning) / self._spw_max * 100), + } + + local pos = self.object:get_pos() + for _, player in pairs(minetest.get_connected_players()) do + local d = vector.distance(pos, player:get_pos()) + if d <= 80 then + mcl_bossbars.add_bar(player, bardef, true, d) + end + end + self.object:set_yaw(self._spawning*10) + + local factor = math.floor((math.sin(self._spawning*10)+1.5) * 85) + local str = minetest.colorspec_to_colorstring({r=factor, g=factor, b=factor}) + self.object:set_texture_mod("^[brighten^[multiply:"..str) + + if self._spawning <= 0 then + if mobs_griefing and not minetest.is_protected(pos, "") then + mcl_explosions.explode(pos, 10, { drop_chance = 1.0 }, self.object) + else + mcl_mobs.mob_class.safe_boom(self, pos, 10) + end + self.object:set_texture_mod("") + self._spawning = nil + self._spw_max = nil + else + return false + end + end + self._custom_timer = self._custom_timer + dtime if self._custom_timer > 1 then self.health = math.min(self.health + 1, self.hp_max) self._custom_timer = self._custom_timer - 1 end - local spawner = minetest.get_player_by_name(self._spawner) - if spawner then - self._death_timer = 0 - local pos = self.object:get_pos() - local spw = spawner:get_pos() - local dist = vector.distance(pos, spw) - if dist > 60 then -- teleport to the player who spawned the wither - local R = 10 - pos.x = spw.x + math.random(-R, R) - pos.y = spw.y + math.random(-R, R) - pos.z = spw.z + math.random(-R, R) - self.object:set_pos(pos) + if self._spawner then + local spawner = minetest.get_player_by_name(self._spawner) + if spawner then + self._death_timer = 0 + local pos = self.object:get_pos() + local spw = spawner:get_pos() + local dist = vector.distance(pos, spw) + if dist > 60 then -- teleport to the player who spawned the wither TODO add a setting to disable this + local R = 10 + pos.x = spw.x + math.random(-R, R) + pos.y = spw.y + math.random(-R, R) + pos.z = spw.z + math.random(-R, R) + self.object:set_pos(pos) + end + else + self._death_timer = self._death_timer + self.health - self._health_old + if self.health == self._health_old then self._death_timer = self._death_timer + dtime end + if self._death_timer > 100 then + self.object:remove() + return false + end + self._health_old = self.health end - else - self._death_timer = self._death_timer + self.health - self._health_old - if self.health == self._health_old then self._death_timer = self._death_timer + dtime end - if self._death_timer > 100 then - self.object:remove() - return false - end - self._health_old = self.health end local dim = mcl_worlds.pos_to_dimension(self.object:get_pos()) @@ -127,22 +175,162 @@ mcl_mobs.register_mob("mobs_mc:wither", { self.arrow = "mobs_mc:wither_skull" end end, + + attack_state = function(self, dtime) + local s = self.object:get_pos() + local p = self.attack:get_pos() or s + + p.y = p.y - .5 + s.y = s.y + .5 + + local dist = vector.distance(p, s) + local vec = { + x = p.x - s.x, + y = p.y - s.y, + z = p.z - s.z + } + + local yaw = (atan(vec.z / vec.x) +math.pi/ 2) - self.rotate + if p.x > s.x then yaw = yaw +math.pi end + yaw = self:set_yaw( yaw, 0, dtime) + + local stay_away_from_player = vector.zero() + + --strafe back and fourth + + --stay away from player so as to shoot them + if dist < self.avoid_distance and self.shooter_avoid_enemy then + self:set_animation( "shoot") + stay_away_from_player=vector.multiply(vector.direction(p, s), 0.33) + end + + if self.fly then + local vel = self.object:get_velocity() + local diff = s.y - p.y + local FLY_FACTOR = self.walk_velocity + if diff < 10 then + self.object:set_velocity({x=vel.x, y= FLY_FACTOR, z=vel.z}) + elseif diff > 15 then + self.object:set_velocity({x=vel.x, y=-FLY_FACTOR, z=vel.z}) + end + for i=1, 15 do + if minetest.get_node(vector.offset(s, 0, -i, 0)).name ~= "air" then + self.object:set_velocity({x=vel.x, y= FLY_FACTOR, z=vel.z}) + break + elseif minetest.get_node(vector.offset(s, 0, i, 0)).name ~= "air" then + self.object:set_velocity({x=vel.x, y=-FLY_FACTOR/i, z=vel.z}) + break + end + end + end + + if self.strafes then + if not self.strafe_direction then + self.strafe_direction = 1.57 + end + if math.random(40) == 1 then + self.strafe_direction = self.strafe_direction*-1 + end + + local dir = vector.rotate_around_axis(vector.direction(s, p), vector.new(0,1,0), self.strafe_direction) + local dir2 = vector.multiply(dir, 0.3 * self.walk_velocity) + + if dir2 and stay_away_from_player then + self.acc = vector.add(dir2, stay_away_from_player) + end + else + self:set_velocity( 0) + end + + if dist > 30 then self.acc = vector.add(self.acc, vector.direction(s, p)*0.01) end + + local side_cor = vector.new(0.7*math.cos(yaw), 0, 0.7*math.sin(yaw)) + local m = self.object:get_pos() -- position of the middle head + local sr = self.object:get_pos() + side_cor -- position of side right head + local sl = self.object:get_pos() - side_cor -- position of side left head + -- height corrections + m.y = m.y + self.collisionbox[5] + sr.y = sr.y + self.collisionbox[5] - 0.3 + sl.y = sl.y + self.collisionbox[5] - 0.3 + local rand_pos = math.random(1,3) + if rand_pos == 1 then m = sr + elseif rand_pos == 2 then m = sl end + -- TODO multiple targets at once? + -- TODO targeting most mobs when no players can be seen/in addition to players + + if self.shoot_interval + and self.timer > self.shoot_interval + and not minetest.raycast(vector.add(m, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next() + and math.random(1, 100) <= 60 then + + self.timer = 0 + self:set_animation( "shoot") + + -- play shoot attack sound + self:mob_sound("shoot_attack") + + -- Shoot arrow + if minetest.registered_entities[self.arrow] then + + local arrow, ent + local v = 1 + if not self.shoot_arrow then + self.firing = true + minetest.after(1, function() + self.firing = false + end) + arrow = minetest.add_entity(m, self.arrow) + ent = arrow:get_luaentity() + if ent.velocity then + v = ent.velocity + end + ent.switch = 1 + ent.owner_id = tostring(self.object) -- add unique owner id to arrow + + -- important for mcl_shields + ent._shooter = self.object + ent._saved_shooter_pos = self.object:get_pos() + end + + local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 + -- offset makes shoot aim accurate + vec.y = vec.y + self.shoot_offset + vec.x = vec.x * (v / amount) + vec.y = vec.y * (v / amount) + vec.z = vec.z * (v / amount) + if self.shoot_arrow then + vec = vector.normalize(vec) + self:shoot_arrow(m, vec) + else + arrow:set_velocity(vec) + end + end + end + end, + do_punch = function(self, hitter, tflp, tool_capabilities, dir) + if self._spawning then return false end local ent = hitter:get_luaentity() if ent and self._arrow_resistant and (string.find(ent.name, "arrow") or string.find(ent.name, "rocket")) then return false end return true end, deal_damage = function(self, damage, mcl_reason) + if self._spawning then return end if self._arrow_resistant and mcl_reason.type == "magic" then return end self.health = self.health - damage end, + on_spawn = function(self) minetest.sound_play("mobs_mc_wither_spawn", {object=self.object, gain=1.0, max_hear_distance=64}) + self._custom_timer = 0.0 + self._death_timer = 0.0 + self._health_old = self.hp_max + self._spawning = 10 + return true end, }) -local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false local wither_rose_soil = { "group:grass_block", "mcl_core:dirt", "mcl_core:coarse_dirt", "mcl_nether:netherrack", "group:soul_block", "mcl_mud:mud", "mcl_moss:moss" } mcl_mobs.register_arrow("mobs_mc:wither_skull", { @@ -156,8 +344,9 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull", { "mobs_mc_wither_projectile.png^[verticalframe:6:4", -- back "mobs_mc_wither_projectile.png^[verticalframe:6:5", -- front }, - velocity = 6, + velocity = 7, rotate = 90, + _lifetime = 350, -- direct hit hit_player = function(self, player) @@ -167,8 +356,9 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull", { }, nil) mcl_mobs.effect_functions["withering"](player, 0.5, 10) mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1) - if player:get_hp() <= 0 then - self._shooter:get_luaentity().health = self._shooter:get_luaentity().health + 5 + local shooter = self._shooter:get_luaentity() + if player:get_hp() <= 0 and shooter then + shooter.health = shooter.health + 5 end end, @@ -181,7 +371,8 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull", { mcl_mobs.mob_class.boom(self,self.object:get_pos(), 1) local l = mob:get_luaentity() if l and l.health - 8 <= 0 then - self._shooter:get_luaentity().health = self._shooter:get_luaentity().health + 5 + local shooter = self._shooter:get_luaentity() + if shooter then shooter.health = shooter.health + 5 end local n = minetest.find_node_near(mob:get_pos(),2,wither_rose_soil) if n then local p = vector.offset(n,0,1,0) @@ -212,6 +403,7 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull_strong", { }, velocity = 4, rotate = 90, + _lifetime = 500, -- direct hit hit_player = function(self, player) @@ -226,8 +418,9 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull_strong", { else mcl_mobs.mob_class.safe_boom(self, pos, 1) --need to call it this way bc self is the "arrow" object here end - if player:get_hp() <= 0 then - self._shooter:get_luaentity().health = self._shooter:get_luaentity().health + 5 + local shooter = self._shooter:get_luaentity() + if player:get_hp() <= 0 and shooter then + shooter.health = shooter.health + 5 end end, @@ -245,7 +438,8 @@ mcl_mobs.register_arrow("mobs_mc:wither_skull_strong", { end local l = mob:get_luaentity() if l and l.health - 8 <= 0 then - self._shooter:get_luaentity().health = self._shooter:get_luaentity().health + 5 + local shooter = self._shooter:get_luaentity() + if shooter then shooter.health = shooter.health + 5 end local n = minetest.find_node_near(mob:get_pos(),2,wither_rose_soil) if n then local p = vector.offset(n,0,1,0)