Merge pull request 'Event api, Raids, Zombie sieges' (#2833) from events into master
Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/2833 Reviewed-by: 𝕵𝖔𝖍𝖆𝖓𝖓𝖊𝖘 𝕱𝖗𝖎𝖙𝖟 <mrrar@noreply.git.minetest.land>
This commit is contained in:
commit
9cf910c47f
27
mods/CORE/mcl_events/API.md
Normal file
27
mods/CORE/mcl_events/API.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
## mcl_events
|
||||||
|
### Registering Events
|
||||||
|
`mlc_events.register_event("name",def)`
|
||||||
|
|
||||||
|
#### Event Definition
|
||||||
|
{
|
||||||
|
stage = 0,
|
||||||
|
max_stage = 1,
|
||||||
|
percent = 100,
|
||||||
|
bars = {},
|
||||||
|
completed = false,
|
||||||
|
cond_start = function() end,
|
||||||
|
--return table of paramtables e.g. { { player = playername, pos = position, ... } }, custom parameters will be passed to the event object/table
|
||||||
|
on_step = function(event) end,
|
||||||
|
--this function is run every game step when the event is active
|
||||||
|
on_start = function(event) end,
|
||||||
|
-- this function is run when the event starts
|
||||||
|
on_stage_begin = function(event) end,
|
||||||
|
-- this function runs when a new stage of the event starts
|
||||||
|
cond_progress = function(event) end, --return false or next stage id
|
||||||
|
--this function checks if the event should progress to the next (or any other) stage
|
||||||
|
cond_complete = function(event) end,
|
||||||
|
--return true if event finished successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
* /event_start <event> -- starts the given event at the current player coordinates
|
151
mods/CORE/mcl_events/init.lua
Normal file
151
mods/CORE/mcl_events/init.lua
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
mcl_events = {}
|
||||||
|
mcl_events.registered_events = {}
|
||||||
|
local disabled_events = minetest.settings:get("mcl_disabled_events")
|
||||||
|
if disabled_events then disabled_events = disabled_events:split(",")
|
||||||
|
else disabled_events = {} end
|
||||||
|
local DBG = minetest.settings:get_bool("mcl_logging_event_api",false)
|
||||||
|
local active_events = {}
|
||||||
|
|
||||||
|
local event_tpl = {
|
||||||
|
stage = 0,
|
||||||
|
max_stage = 1,
|
||||||
|
percent = 100,
|
||||||
|
bars = {},
|
||||||
|
completed = false,
|
||||||
|
cond_start = function(event) end, --return table of positions
|
||||||
|
on_step = function(event) end,
|
||||||
|
on_start = function(event) end,
|
||||||
|
on_stage_begin = function(event) end,
|
||||||
|
cond_progress = function(event) end, --return next stage
|
||||||
|
cond_complete = function(event) end, --return success
|
||||||
|
}
|
||||||
|
|
||||||
|
local function mcl_log(m,l)
|
||||||
|
if DBG then
|
||||||
|
if not l then l = "action" end
|
||||||
|
minetest.log(l,"[mcl_events] "..m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_events.register_event(name,def)
|
||||||
|
if table.indexof(disabled_events,name) ~= -1 then return end
|
||||||
|
mcl_events.registered_events[name] = def
|
||||||
|
mcl_events.registered_events[name].name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addbars(self)
|
||||||
|
if not self.enable_bossbar then return end
|
||||||
|
for _,player in pairs(minetest.get_connected_players()) do
|
||||||
|
if vector.distance(self.pos,player:get_pos()) < 64 then
|
||||||
|
local bar = mcl_bossbars.add_bar(player, {color = "red", text = self.readable_name .. ": Wave "..self.stage.." / "..self.max_stage, percentage = self.percent }, true,1)
|
||||||
|
table.insert(self.bars,bar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function start_event(p,e)
|
||||||
|
mcl_log("[mcl_events] Event started: "..e.readable_name.." at "..minetest.pos_to_string(vector.round(p.pos)))
|
||||||
|
local idx = #active_events + 1
|
||||||
|
active_events[idx] = table.copy(e)
|
||||||
|
setmetatable(active_events[idx],{__index = event_tpl})
|
||||||
|
for k,v in pairs(p) do active_events[idx][k] = v end
|
||||||
|
active_events[idx].stage = 0
|
||||||
|
active_events[idx].percent = 100
|
||||||
|
active_events[idx].bars = {}
|
||||||
|
active_events[idx].time_start = os.time()
|
||||||
|
if active_events[idx].on_start then
|
||||||
|
active_events[idx]:on_start(p.pos)
|
||||||
|
end
|
||||||
|
addbars(active_events[idx])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function finish_event(self,idx)
|
||||||
|
mcl_log("[mcl_events] Finished: "..self.readable_name.." at "..minetest.pos_to_string(vector.round(self.pos)))
|
||||||
|
if self.on_complete then self:on_complete() end
|
||||||
|
for _,b in pairs(self.bars) do
|
||||||
|
mcl_bossbars.remove_bar(b)
|
||||||
|
end
|
||||||
|
table.remove(active_events,idx)
|
||||||
|
end
|
||||||
|
|
||||||
|
local etime = 0
|
||||||
|
function check_events(dtime)
|
||||||
|
--process active events
|
||||||
|
for idx,ae in pairs(active_events) do
|
||||||
|
if ae.cond_complete and ae:cond_complete() then
|
||||||
|
ae.finished = true
|
||||||
|
finish_event(ae,idx)
|
||||||
|
elseif not ae.cond_complete and ae.max_stage and ae.max_stage <= ae.stage then
|
||||||
|
ae.finished = true
|
||||||
|
finish_event(ae,idx)
|
||||||
|
elseif not ae.finished and ae.cond_progress then
|
||||||
|
local p = ae:cond_progress()
|
||||||
|
if p == true then
|
||||||
|
ae.stage = ae.stage + 1
|
||||||
|
if ae:on_stage_begin() == true then
|
||||||
|
mcl_log("[mcl_events] Event "..ae.readable_name.." at "..minetest.pos_to_string(vector.round(ae.pos)).." failed at stage_begin of stage "..ae.stage )
|
||||||
|
active_events[idx] = nil
|
||||||
|
end
|
||||||
|
elseif tonumber(p) then
|
||||||
|
ae.stage = tonumber(p) or ae.stage + 1
|
||||||
|
ae:on_stage_begin()
|
||||||
|
end
|
||||||
|
elseif not ae.finished and ae.on_step then
|
||||||
|
ae:on_step(dtime)
|
||||||
|
end
|
||||||
|
addbars(ae)
|
||||||
|
end
|
||||||
|
-- check if a new event should be started
|
||||||
|
etime = etime - dtime
|
||||||
|
if etime > 0 then return end
|
||||||
|
etime = 10
|
||||||
|
for _,e in pairs(mcl_events.registered_events) do
|
||||||
|
local pp = e.cond_start()
|
||||||
|
if pp then
|
||||||
|
for _,p in pairs(pp) do
|
||||||
|
local start = true
|
||||||
|
if e.exclusive_to_area then
|
||||||
|
for _,ae in pairs(active_events) do
|
||||||
|
if e.name == ae.name and vector.distance(p.pos,ae.pos) < e.exclusive_to_area then start = false end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if start then
|
||||||
|
start_event(p,e)
|
||||||
|
elseif DBG then
|
||||||
|
mcl_log("[mcl_events] Event "..e.readable_name.." already active at "..minetest.pos_to_string(vector.round(p.pos)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for idx,ae in pairs(active_events) do
|
||||||
|
local player_near = false
|
||||||
|
for _,pl in pairs(minetest.get_connected_players()) do
|
||||||
|
if ae.pos and vector.distance(pl:get_pos(),ae.pos) < 64 then player_near = true end
|
||||||
|
end
|
||||||
|
if ae.pos and not player_near then
|
||||||
|
mcl_log("[mcl_events] Event "..ae.readable_name.." at "..minetest.pos_to_string(vector.round(ae.pos)).." aborted - no players near." )
|
||||||
|
active_events[idx] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_globalstep(check_events)
|
||||||
|
|
||||||
|
mcl_info.register_debug_field("Active Events",{
|
||||||
|
level = 4,
|
||||||
|
func = function(pl,pos)
|
||||||
|
return tostring(#active_events)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("event_start",{
|
||||||
|
privs = {debug = true},
|
||||||
|
description = "Debug command to start events",
|
||||||
|
func = function(pname,param)
|
||||||
|
local p = minetest.get_player_by_name(pname)
|
||||||
|
local evdef = mcl_events.registered_events[param]
|
||||||
|
if not evdef then return false,"Event "..param.." doesn't exist.'" end
|
||||||
|
start_event({pos=p:get_pos(),player=pname,factor=1},evdef)
|
||||||
|
return true,"Started event "..param
|
||||||
|
end,
|
||||||
|
})
|
3
mods/CORE/mcl_events/mod.conf
Normal file
3
mods/CORE/mcl_events/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name = mcl_events
|
||||||
|
author = cora
|
||||||
|
depends = mcl_mobs,mcl_bossbars, mcl_info
|
|
@ -11,6 +11,8 @@ local S = minetest.get_translator("mobs_mc")
|
||||||
|
|
||||||
local pr = PseudoRandom(os.time()*666)
|
local pr = PseudoRandom(os.time()*666)
|
||||||
|
|
||||||
|
local spawned_vexes = {} --this is stored locally so the mobs engine doesn't try to store it in staticdata
|
||||||
|
|
||||||
mcl_mobs:register_mob("mobs_mc:evoker", {
|
mcl_mobs:register_mob("mobs_mc:evoker", {
|
||||||
description = S("Evoker"),
|
description = S("Evoker"),
|
||||||
type = "monster",
|
type = "monster",
|
||||||
|
@ -42,16 +44,24 @@ mcl_mobs:register_mob("mobs_mc:evoker", {
|
||||||
attack_type = "dogfight",
|
attack_type = "dogfight",
|
||||||
-- Summon vexes
|
-- Summon vexes
|
||||||
custom_attack = function(self, to_attack)
|
custom_attack = function(self, to_attack)
|
||||||
local r = pr:next(2,4)
|
if not spawned_vexes[self] then spawned_vexes[self] = {} end
|
||||||
|
if #spawned_vexes[self] >= 7 then return end
|
||||||
|
for k,v in pairs(spawned_vexes[self]) do
|
||||||
|
if not v or v.health <= 0 then table.remove(spawned_vexes[self],k) end
|
||||||
|
end
|
||||||
|
local r = pr:next(1,4)
|
||||||
local basepos = self.object:get_pos()
|
local basepos = self.object:get_pos()
|
||||||
basepos.y = basepos.y + 1
|
basepos.y = basepos.y + 1
|
||||||
for i=1, r do
|
for i=1, r do
|
||||||
local spawnpos = vector.add(basepos, minetest.yaw_to_dir(pr:next(0,360)))
|
local spawnpos = vector.add(basepos, minetest.yaw_to_dir(pr:next(0,360)))
|
||||||
local vex = minetest.add_entity(spawnpos, "mobs_mc:vex")
|
local vex = minetest.add_entity(spawnpos, "mobs_mc:vex")
|
||||||
local ent = vex:get_luaentity()
|
local ent = vex:get_luaentity()
|
||||||
|
|
||||||
-- Mark vexes as summoned and start their life clock (they take damage it reaches 0)
|
-- Mark vexes as summoned and start their life clock (they take damage it reaches 0)
|
||||||
ent._summoned = true
|
ent._summoned = true
|
||||||
ent._lifetimer = pr:next(33, 108)
|
ent._lifetimer = pr:next(33, 108)
|
||||||
|
|
||||||
|
table.insert(spawned_vexes[self],ent)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
shoot_interval = 15,
|
shoot_interval = 15,
|
||||||
|
|
393
mods/ENVIRONMENT/mcl_raids/init.lua
Normal file
393
mods/ENVIRONMENT/mcl_raids/init.lua
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
-- mcl_raids
|
||||||
|
mcl_raids = {}
|
||||||
|
local S = minetest.get_translator(minetest.get_current_modname())
|
||||||
|
|
||||||
|
-- Define the amount of illagers to spawn each wave.
|
||||||
|
local waves = {
|
||||||
|
{
|
||||||
|
["mobs_mc:pillager"] = 5,
|
||||||
|
["mobs_mc:vindicator"] = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
["mobs_mc:pillager"] = 4,
|
||||||
|
["mobs_mc:vindicator"] = 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
["mobs_mc:pillager"] = 4,
|
||||||
|
["mobs_mc:vindicator"] = 1,
|
||||||
|
["mobs_mc:witch"] = 1,
|
||||||
|
--["mobs_mc:ravager"] = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
["mobs_mc:pillager"] = 5,
|
||||||
|
["mobs_mc:vindicator"] = 2,
|
||||||
|
["mobs_mc:witch"] = 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
["mobs_mc:pillager"] = 5,
|
||||||
|
["mobs_mc:vindicator"] = 5,
|
||||||
|
["mobs_mc:witch"] = 1,
|
||||||
|
["mobs_mc:evoker"] = 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local extra_wave = {
|
||||||
|
["mobs_mc:pillager"] = 5,
|
||||||
|
["mobs_mc:vindicator"] = 5,
|
||||||
|
["mobs_mc:witch"] = 1,
|
||||||
|
["mobs_mc:evoker"] = 1,
|
||||||
|
--["mobs_mc:ravager"] = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
local oban_layers = {
|
||||||
|
{
|
||||||
|
pattern = "rhombus",
|
||||||
|
color = "unicolor_cyan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = "unicolor_grey",
|
||||||
|
pattern = "stripe_bottom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern = "stripe_center",
|
||||||
|
color = "unicolor_darkgrey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = "unicolor_black",
|
||||||
|
pattern = "stripe_middle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern = "half_horizontal",
|
||||||
|
color = "unicolor_grey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color = "unicolor_grey",
|
||||||
|
pattern = "circle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern = "border",
|
||||||
|
color = "unicolor_black"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local oban_def = table.copy(minetest.registered_entities["mcl_banners:standing_banner"])
|
||||||
|
oban_def.visual_size = { x=1, y=1 }
|
||||||
|
local old_step = oban_def.on_step
|
||||||
|
oban_def.on_step = function(self,dtime)
|
||||||
|
if not self.object:get_attach() then return self.object:remove() end
|
||||||
|
if old_step then return old_step(self.dtime) end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_entity(":mcl_raids:ominous_banner",oban_def)
|
||||||
|
|
||||||
|
function mcl_raids.drop_obanner(pos)
|
||||||
|
local it = ItemStack("mcl_banners:banner_item_white")
|
||||||
|
it:get_meta():set_string("layers",minetest.serialize(oban_layers))
|
||||||
|
it:get_meta():set_string("name",S("Ominous Banner"))
|
||||||
|
minetest.add_item(pos,it)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.promote_to_raidcaptain(c) -- object
|
||||||
|
if not c or not c:get_pos() then return end
|
||||||
|
local pos = c:get_pos()
|
||||||
|
local l = c:get_luaentity()
|
||||||
|
l._banner = minetest.add_entity(pos,"mcl_raids:ominous_banner")
|
||||||
|
l._banner:set_properties({textures = {mcl_banners.make_banner_texture("unicolor_white", oban_layers)}})
|
||||||
|
l._banner:set_attach(c,"",vector.new(-1,5.5,0),vector.new(0,0,0),true)
|
||||||
|
l._raidcaptain = true
|
||||||
|
local old_ondie = l.on_die
|
||||||
|
l.on_die = function(self, pos, cmi_cause)
|
||||||
|
if l._banner then
|
||||||
|
l._banner:remove()
|
||||||
|
l._banner = nil
|
||||||
|
mcl_raids.drop_obanner(pos)
|
||||||
|
if cmi_cause and cmi_cause.type == "punch" and cmi_cause.puncher:is_player() then
|
||||||
|
awards.unlock(cmi_cause.puncher:get_player_name(), "mcl:voluntary_exile")
|
||||||
|
local lv = mcl_potions.player_get_effect(cmi_cause.puncher, "bad_omen")
|
||||||
|
if not lv then lv = 0
|
||||||
|
else lv = lv.factor end
|
||||||
|
lv = math.max(5,lv + 1)
|
||||||
|
mcl_potions.bad_omen_func(cmi_cause.puncher,lv,6000)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if old_ondie then return old_ondie(self,pos,cmi_cause) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.is_raidcaptain_near(pos)
|
||||||
|
for k,v in pairs(minetest.get_objects_inside_radius(pos,32)) do
|
||||||
|
local l = v:get_luaentity()
|
||||||
|
if l and l._raidcaptain then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.register_possible_raidcaptain(mob)
|
||||||
|
local old_on_spawn = minetest.registered_entities[mob].on_spawn
|
||||||
|
local old_on_pick_up = minetest.registered_entities[mob].on_pick_up
|
||||||
|
if not minetest.registered_entities[mob].pick_up then minetest.registered_entities[mob].pick_up = {} end
|
||||||
|
table.insert(minetest.registered_entities[mob].pick_up,"mcl_banners:banner_item_white")
|
||||||
|
minetest.registered_entities[mob].on_pick_up = function(self,e)
|
||||||
|
local stack = ItemStack(e.itemstring)
|
||||||
|
if not self._raidcaptain and stack:get_meta():get_string("name"):find("Ominous Banner") then
|
||||||
|
stack:take_item(1)
|
||||||
|
mcl_raids.promote_to_raidcaptain(self.object)
|
||||||
|
return stack
|
||||||
|
end
|
||||||
|
if old_on_pick_up then return old_on_pick_up(self,e) end
|
||||||
|
end
|
||||||
|
minetest.registered_entities[mob].on_spawn = function(self)
|
||||||
|
if not mcl_raids.is_raidcaptain_near(self.object:get_pos()) then
|
||||||
|
mcl_raids.promote_to_raidcaptain(self.object)
|
||||||
|
end
|
||||||
|
if old_on_spawn then return old_on_spawn(self) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mcl_raids.register_possible_raidcaptain("mobs_mc:pillager")
|
||||||
|
mcl_raids.register_possible_raidcaptain("mobs_mc:vindicator")
|
||||||
|
mcl_raids.register_possible_raidcaptain("mobs_mc:evoker")
|
||||||
|
|
||||||
|
function mcl_raids.spawn_raid(event)
|
||||||
|
local pos = event.pos
|
||||||
|
local wave = event.stage
|
||||||
|
local illager_count = 0
|
||||||
|
local spawnable = false
|
||||||
|
local r = 32
|
||||||
|
local n = 12
|
||||||
|
local i = math.random(1, n)
|
||||||
|
local raid_pos = vector.offset(pos,r * math.cos(((i-1)/n) * (2*math.pi)),0, r * math.sin(((i-1)/n) * (2*math.pi)))
|
||||||
|
local sn = minetest.find_nodes_in_area_under_air(vector.offset(raid_pos,-5,-50,-5), vector.offset(raid_pos,5,50,5), {"group:grass_block", "group:grass_block_snow", "group:snow_cover", "group:sand", "mcl_core:ice"})
|
||||||
|
mcl_bells.ring_once(pos)
|
||||||
|
if sn and #sn > 0 then
|
||||||
|
local spawn_pos = sn[math.random(#sn)]
|
||||||
|
if spawn_pos then
|
||||||
|
minetest.log("action", "[mcl_raids] Raid Spawn Position chosen at " .. minetest.pos_to_string(spawn_pos) .. ".")
|
||||||
|
event.health_max = 0
|
||||||
|
local w
|
||||||
|
if event.stage <= #waves then
|
||||||
|
w= waves[event.stage]
|
||||||
|
else
|
||||||
|
w = extra_wave
|
||||||
|
end
|
||||||
|
for m,c in pairs(w) do
|
||||||
|
for i=1,c do
|
||||||
|
local p = vector.offset(spawn_pos,0,1,0)
|
||||||
|
local mob = mcl_mobs.spawn(p,m)
|
||||||
|
local l = mob:get_luaentity()
|
||||||
|
if l then
|
||||||
|
l.raidmob = true
|
||||||
|
event.health_max = event.health_max + l.health
|
||||||
|
table.insert(event.mobs,mob)
|
||||||
|
mcl_mobs:gopath(l,pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if event.stage == 1 then
|
||||||
|
table.shuffle(event.mobs)
|
||||||
|
mcl_raids.promote_to_raidcaptain(event.mobs[1])
|
||||||
|
end
|
||||||
|
minetest.log("action", "[mcl_raids] Raid Spawned. Illager Count: " .. #event.mobs .. ".")
|
||||||
|
return #event.mobs == 0
|
||||||
|
else
|
||||||
|
minetest.log("action", "[mcl_raids] Raid Spawn Postion not chosen.")
|
||||||
|
end
|
||||||
|
elseif not sn then
|
||||||
|
minetest.log("action", "[mcl_raids] Raid Spawn Position error, no appropriate site found.")
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.find_villager(pos)
|
||||||
|
local obj = minetest.get_objects_inside_radius(pos, 8)
|
||||||
|
for _, objects in ipairs(obj) do
|
||||||
|
local object = objects:get_luaentity()
|
||||||
|
if object then
|
||||||
|
if object.name ~= "mobs_mc:villager" then
|
||||||
|
return
|
||||||
|
elseif object.name == "mobs_mc:villager" then
|
||||||
|
--minetest.log("action", "[mcl_raids] Villager Found.")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
--minetest.log("action", "[mcl_raids] No Villager Found.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.find_bed(pos)
|
||||||
|
return minetest.find_node_near(pos,128,{"mcl_beds:bed_red_bottom"})
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_raids.find_village(pos)
|
||||||
|
local bed = mcl_raids.find_bed(pos)
|
||||||
|
if bed and mcl_raids.find_villager(bed) then
|
||||||
|
return bed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_point_on_circle(pos,r,n)
|
||||||
|
local rt = {}
|
||||||
|
for i=1, n do
|
||||||
|
table.insert(rt,vector.offset(pos,r * math.cos(((i-1)/n) * (2*math.pi)),0, r* math.sin(((i-1)/n) * (2*math.pi)) ))
|
||||||
|
end
|
||||||
|
table.shuffle(rt)
|
||||||
|
return rt[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function start_firework_rocket(pos)
|
||||||
|
local p = get_point_on_circle(pos,math.random(32,64),32)
|
||||||
|
local n = minetest.get_node(p)
|
||||||
|
local l = minetest.get_natural_light(pos,0.5)
|
||||||
|
if n.name ~= "air" or l <= minetest.LIGHT_MAX then return end
|
||||||
|
local o = minetest.add_entity(p,"mcl_bows:rocket_entity")
|
||||||
|
o:get_luaentity()._harmless = true
|
||||||
|
o:set_acceleration(vector.new(math.random(0,2),math.random(30,50),math.random(0,2)))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_firework(pos,stime)
|
||||||
|
if os.time() - stime > 60 then return end
|
||||||
|
for i=1,math.random(25) do
|
||||||
|
minetest.after(math.random(i),start_firework_rocket,pos)
|
||||||
|
end
|
||||||
|
minetest.after(10,make_firework,pos,stime)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_player_near(self)
|
||||||
|
for _,pl in pairs(minetest.get_connected_players()) do
|
||||||
|
if self.pos and vector.distance(pl:get_pos(),self.pos) < 64 then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function check_mobs(self)
|
||||||
|
local m = {}
|
||||||
|
local h = 0
|
||||||
|
for k,o in pairs(self.mobs) do
|
||||||
|
if o and o:get_pos() then
|
||||||
|
local l = o:get_luaentity()
|
||||||
|
h = h + l.health
|
||||||
|
table.insert(m,o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #m == 0 then --if no valid mobs in table search if there are any (reloaded ones) in the area
|
||||||
|
for k,o in pairs(minetest.get_objects_inside_radius(self.pos,64)) do
|
||||||
|
local l = o:get_luaentity()
|
||||||
|
if l and l.raidmob then
|
||||||
|
local l = o:get_luaentity()
|
||||||
|
h = h + l.health
|
||||||
|
table.insert(m,o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.mobs = m
|
||||||
|
return h
|
||||||
|
end
|
||||||
|
|
||||||
|
mcl_events.register_event("raid",{
|
||||||
|
readable_name = "Raid",
|
||||||
|
max_stage = 5,
|
||||||
|
health = 1,
|
||||||
|
health_max = 1,
|
||||||
|
exclusive_to_area = 128,
|
||||||
|
enable_bossbar = true,
|
||||||
|
cond_start = function(self)
|
||||||
|
local r = {}
|
||||||
|
for _,p in pairs(minetest.get_connected_players()) do
|
||||||
|
if mcl_potions.player_has_effect(p,"bad_omen") then
|
||||||
|
local raid_pos = mcl_raids.find_village(p:get_pos())
|
||||||
|
if raid_pos then
|
||||||
|
table.insert(r,{ player = p:get_player_name(), pos = raid_pos })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #r > 0 then return r end
|
||||||
|
end,
|
||||||
|
on_start = function(self)
|
||||||
|
self.mobs = {}
|
||||||
|
self.health_max = 1
|
||||||
|
self.health = 0
|
||||||
|
local lv = mcl_potions.player_get_effect(minetest.get_player_by_name(self.player), "bad_omen")
|
||||||
|
if lv and lv.factor and lv.factor > 1 then self.max_stage = 6 end
|
||||||
|
end,
|
||||||
|
cond_progress = function(self)
|
||||||
|
if not is_player_near(self) then return false end
|
||||||
|
self.health = check_mobs(self)
|
||||||
|
self.percent = math.max(0,(self.health / self.health_max ) * 100)
|
||||||
|
if #self.mobs < 1 then
|
||||||
|
return true end
|
||||||
|
end,
|
||||||
|
on_stage_begin = mcl_raids.spawn_raid,
|
||||||
|
cond_complete = function(self)
|
||||||
|
if not is_player_near(self) then return false end
|
||||||
|
--let the event api handle cancel the event when no players are near
|
||||||
|
--without this check it would sort out the unloaded mob entities and
|
||||||
|
--think the raid is defeated.
|
||||||
|
check_mobs(self)
|
||||||
|
return self.stage >= self.max_stage and #self.mobs < 1
|
||||||
|
end,
|
||||||
|
on_complete = function(self)
|
||||||
|
awards.unlock(self.player,"mcl:hero_of_the_village")
|
||||||
|
mcl_potions.player_clear_effect(minetest.get_player_by_name(self.player),"bad_omen")
|
||||||
|
make_firework(self.pos,os.time())
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("raidcap",{
|
||||||
|
privs = {debug = true},
|
||||||
|
func = function(pname,param)
|
||||||
|
local c = minetest.add_entity(minetest.get_player_by_name(pname):get_pos(),"mobs_mc:pillager")
|
||||||
|
mcl_raids.promote_to_raidcaptain(c)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("dump_banner_layers",{
|
||||||
|
privs = {debug = true},
|
||||||
|
func = function(pname,param)
|
||||||
|
local p = minetest.get_player_by_name(pname)
|
||||||
|
mcl_raids.drop_obanner(vector.offset(p:get_pos(),1,1,1))
|
||||||
|
for k,v in pairs(minetest.get_objects_inside_radius(p:get_pos(),5)) do
|
||||||
|
local l = v:get_luaentity()
|
||||||
|
if l and l.name == "mcl_banners:standing_banner" then
|
||||||
|
minetest.log(dump(l._base_color))
|
||||||
|
minetest.log(dump(l._layers))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
local function is_new_years()
|
||||||
|
local d = os.date("*t")
|
||||||
|
return d.month == 1 and d.day == 1 and d.hour < 1
|
||||||
|
end
|
||||||
|
|
||||||
|
mcl_events.register_event("new_years",{
|
||||||
|
stage = 0,
|
||||||
|
max_stage = 1,
|
||||||
|
readable_name = "New Years",
|
||||||
|
pos = vector.new(0,0,0),
|
||||||
|
exclusive_to_area = 256,
|
||||||
|
cond_start = function(event)
|
||||||
|
if not is_new_years() then return false end
|
||||||
|
local r = {}
|
||||||
|
for _,p in pairs(minetest.get_connected_players()) do
|
||||||
|
table.insert(r,{ player = p:get_player_name(), pos = p:get_pos()})
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end,
|
||||||
|
on_start = function(self)
|
||||||
|
minetest.chat_send_player(self.player,"<cora> Happy new year <3")
|
||||||
|
end,
|
||||||
|
on_step = function(self,dtime)
|
||||||
|
if not self.timer or self.timer < 0 then
|
||||||
|
self.timer = math.random(1,5)
|
||||||
|
for i=1,math.random(8) do
|
||||||
|
minetest.after(math.random(i),start_firework_rocket,minetest.get_player_by_name(self.player):get_pos())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.timer = self.timer - dtime
|
||||||
|
end,
|
||||||
|
cond_complete = function(event)
|
||||||
|
return not is_new_years()
|
||||||
|
end, --return success
|
||||||
|
})
|
3
mods/ENVIRONMENT/mcl_raids/mod.conf
Normal file
3
mods/ENVIRONMENT/mcl_raids/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name = mcl_raids
|
||||||
|
author = PrairieWind
|
||||||
|
depends = mcl_events, mobs_mc, mcl_potions, mcl_bells, mcl_achievements
|
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
77
mods/ENVIRONMENT/mcl_zombie_sieges/init.lua
Normal file
77
mods/ENVIRONMENT/mcl_zombie_sieges/init.lua
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
local function check_spawn_pos(pos)
|
||||||
|
return minetest.get_natural_light(pos) < 7
|
||||||
|
end
|
||||||
|
|
||||||
|
local function spawn_zombies(self)
|
||||||
|
local nn = minetest.find_nodes_in_area_under_air(vector.offset(self.pos,-16,-16,-16),vector.offset(self.pos,16,16,16),{"group:solid"})
|
||||||
|
table.shuffle(nn)
|
||||||
|
for i=1,20 do
|
||||||
|
local p = vector.offset(nn[i%#nn],0,1,0)
|
||||||
|
if check_spawn_pos(p) then
|
||||||
|
local m = mcl_mobs.spawn(p,"mobs_mc:zombie")
|
||||||
|
local l = m:get_luaentity()
|
||||||
|
mcl_mobs:gopath(m:get_luaentity(),self.pos)
|
||||||
|
table.insert(self.mobs,m)
|
||||||
|
self.health_max = self.health_max + l.health
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mcl_events.register_event("zombie_siege",{
|
||||||
|
readable_name = "Zombie Siege",
|
||||||
|
max_stage = 1,
|
||||||
|
health = 1,
|
||||||
|
health_max = 1,
|
||||||
|
exclusive_to_area = 128,
|
||||||
|
enable_bossbar = false,
|
||||||
|
cond_start = function(self)
|
||||||
|
local pr = PseudoRandom(minetest.get_day_count())
|
||||||
|
local rnd = pr:next(1,10)
|
||||||
|
local t = minetest.get_timeofday()
|
||||||
|
local r = {}
|
||||||
|
for _,p in pairs(minetest.get_connected_players()) do
|
||||||
|
local village = mcl_raids.find_village(p:get_pos())
|
||||||
|
if t < 0.04 and village and rnd == 1 then
|
||||||
|
table.insert(r,{ player = p:get_player_name(), pos = village})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #r > 0 then return r end
|
||||||
|
end,
|
||||||
|
on_start = function(self)
|
||||||
|
self.mobs = {}
|
||||||
|
self.health_max = 1
|
||||||
|
self.health = 0
|
||||||
|
end,
|
||||||
|
cond_progress = function(self)
|
||||||
|
local m = {}
|
||||||
|
local h = 0
|
||||||
|
for k,o in pairs(self.mobs) do
|
||||||
|
if o and o:get_pos() then
|
||||||
|
local l = o:get_luaentity()
|
||||||
|
h = h + l.health
|
||||||
|
table.insert(m,o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.mobs = m
|
||||||
|
self.health = h
|
||||||
|
self.percent = math.max(0,(self.health / self.health_max ) * 100)
|
||||||
|
if #m < 1 then
|
||||||
|
return true end
|
||||||
|
end,
|
||||||
|
on_stage_begin = spawn_zombies,
|
||||||
|
cond_complete = function(self)
|
||||||
|
local m = {}
|
||||||
|
for k,o in pairs(self.mobs) do
|
||||||
|
if o and o:get_pos() then
|
||||||
|
local l = o:get_luaentity()
|
||||||
|
table.insert(m,o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self.stage >= self.max_stage and #m < 1
|
||||||
|
end,
|
||||||
|
on_complete = function(self)
|
||||||
|
--minetest.log("SIEGE complete")
|
||||||
|
--awards.unlock(self.player,"mcl:hero_of_the_village")
|
||||||
|
end,
|
||||||
|
})
|
3
mods/ENVIRONMENT/mcl_zombie_sieges/mod.conf
Normal file
3
mods/ENVIRONMENT/mcl_zombie_sieges/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name = mcl_zombie_sieges
|
||||||
|
author = cora
|
||||||
|
depends = mcl_events, mcl_raids
|
|
@ -529,3 +529,21 @@ awards.register_achievement("mcl:obsidian", {
|
||||||
type = "Advancement",
|
type = "Advancement",
|
||||||
group = "Overworld",
|
group = "Overworld",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
awards.register_achievement("mcl:hero_of_the_village", {
|
||||||
|
title = S("Hero of the Village"),
|
||||||
|
description = S("Successfully defend a village from a raid"),
|
||||||
|
icon = "mcl_raids_hero_of_the_village_icon.png",
|
||||||
|
type = "Advancement",
|
||||||
|
group = "Adventure",
|
||||||
|
secret = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
awards.register_achievement("mcl:voluntary_exile", {
|
||||||
|
title = S("Voluntary Exile"),
|
||||||
|
description = S("Kill a raid captain. Maybe consider staying away from the local villages for the time being..."),
|
||||||
|
icon = "mcl_potions_effect_bad_omen.png",
|
||||||
|
type = "Advancement",
|
||||||
|
group = "Adventure",
|
||||||
|
secret = true,
|
||||||
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ local function dir_to_pitch(dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function damage_explosion(self, damagemulitplier)
|
local function damage_explosion(self, damagemulitplier)
|
||||||
|
if self._harmless then return end
|
||||||
local p = self.object:get_pos()
|
local p = self.object:get_pos()
|
||||||
if not p then return end
|
if not p then return end
|
||||||
mcl_explosions.explode(p, 3, {})
|
mcl_explosions.explode(p, 3, {})
|
||||||
|
|
|
@ -19,6 +19,7 @@ get_chat_function["water_breathing"] = mcl_potions.water_breathing_func
|
||||||
get_chat_function["leaping"] = mcl_potions.leaping_func
|
get_chat_function["leaping"] = mcl_potions.leaping_func
|
||||||
get_chat_function["swiftness"] = mcl_potions.swiftness_func
|
get_chat_function["swiftness"] = mcl_potions.swiftness_func
|
||||||
get_chat_function["heal"] = mcl_potions.healing_func
|
get_chat_function["heal"] = mcl_potions.healing_func
|
||||||
|
get_chat_function["bad_omen"] = mcl_potions.bad_omen_func
|
||||||
|
|
||||||
minetest.register_chatcommand("effect",{
|
minetest.register_chatcommand("effect",{
|
||||||
params = S("<effect> <duration> [<factor>]"),
|
params = S("<effect> <duration> [<factor>]"),
|
||||||
|
|
|
@ -9,6 +9,7 @@ EF.leaping = {}
|
||||||
EF.swift = {} -- for swiftness AND slowness
|
EF.swift = {} -- for swiftness AND slowness
|
||||||
EF.night_vision = {}
|
EF.night_vision = {}
|
||||||
EF.fire_proof = {}
|
EF.fire_proof = {}
|
||||||
|
EF.bad_omen = {}
|
||||||
|
|
||||||
local EFFECT_TYPES = 0
|
local EFFECT_TYPES = 0
|
||||||
for _,_ in pairs(EF) do
|
for _,_ in pairs(EF) do
|
||||||
|
@ -350,6 +351,26 @@ minetest.register_globalstep(function(dtime)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check for Bad Omen
|
||||||
|
for player, vals in pairs(EF.bad_omen) do
|
||||||
|
|
||||||
|
is_player = player:is_player()
|
||||||
|
|
||||||
|
EF.bad_omen[player].timer = EF.bad_omen[player].timer + dtime
|
||||||
|
|
||||||
|
if player:get_pos() then mcl_potions._add_spawner(player, "#0b6138") end
|
||||||
|
|
||||||
|
if EF.bad_omen[player] and EF.bad_omen[player].timer >= EF.bad_omen[player].dur then
|
||||||
|
EF.bad_omen[player] = nil
|
||||||
|
if is_player then
|
||||||
|
meta = player:get_meta()
|
||||||
|
meta:set_string("_has_bad_omen", minetest.serialize(EF.bad_omen[player]))
|
||||||
|
potions_set_hud(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Prevent damage to player with Fire Resistance enabled
|
-- Prevent damage to player with Fire Resistance enabled
|
||||||
|
@ -386,6 +407,7 @@ function mcl_potions._clear_cached_player_data(player)
|
||||||
EF.swift[player] = nil
|
EF.swift[player] = nil
|
||||||
EF.night_vision[player] = nil
|
EF.night_vision[player] = nil
|
||||||
EF.fire_proof[player] = nil
|
EF.fire_proof[player] = nil
|
||||||
|
EF.bad_omen[player] = nil
|
||||||
|
|
||||||
meta = player:get_meta()
|
meta = player:get_meta()
|
||||||
meta:set_int("night_vision", 0)
|
meta:set_int("night_vision", 0)
|
||||||
|
@ -429,6 +451,7 @@ function mcl_potions._save_player_effects(player)
|
||||||
meta:set_string("_is_swift", minetest.serialize(EF.swift[player]))
|
meta:set_string("_is_swift", minetest.serialize(EF.swift[player]))
|
||||||
meta:set_string("_is_cat", minetest.serialize(EF.night_vision[player]))
|
meta:set_string("_is_cat", minetest.serialize(EF.night_vision[player]))
|
||||||
meta:set_string("_is_fire_proof", minetest.serialize(EF.fire_proof[player]))
|
meta:set_string("_is_fire_proof", minetest.serialize(EF.fire_proof[player]))
|
||||||
|
meta:set_string("_has_bad_omen", minetest.serialize(EF.bad_omen[player]))
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -480,6 +503,10 @@ function mcl_potions._load_player_effects(player)
|
||||||
EF.fire_proof[player] = minetest.deserialize(meta:get_string("_is_fire_proof"))
|
EF.fire_proof[player] = minetest.deserialize(meta:get_string("_is_fire_proof"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if minetest.deserialize(meta:get_string("_has_bad_omen")) then
|
||||||
|
EF.bad_omen[player] = minetest.deserialize(meta:get_string("_has_bad_omen"))
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Returns true if player has given effect
|
-- Returns true if player has given effect
|
||||||
|
@ -490,6 +517,18 @@ function mcl_potions.player_has_effect(player, effect_name)
|
||||||
return EF[effect_name][player] ~= nil
|
return EF[effect_name][player] ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function mcl_potions.player_get_effect(player, effect_name)
|
||||||
|
if not EF[effect_name] or not EF[effect_name][player] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return EF[effect_name][player]
|
||||||
|
end
|
||||||
|
|
||||||
|
function mcl_potions.player_clear_effect(player,effect)
|
||||||
|
EF[effect][player] = nil
|
||||||
|
potions_set_icons(player)
|
||||||
|
end
|
||||||
|
|
||||||
minetest.register_on_leaveplayer( function(player)
|
minetest.register_on_leaveplayer( function(player)
|
||||||
mcl_potions._save_player_effects(player)
|
mcl_potions._save_player_effects(player)
|
||||||
mcl_potions._clear_cached_player_data(player) -- clearout the buffer to prevent looking for a player not there
|
mcl_potions._clear_cached_player_data(player) -- clearout the buffer to prevent looking for a player not there
|
||||||
|
@ -966,3 +1005,18 @@ function mcl_potions._extinguish_nearby_fire(pos, radius)
|
||||||
end
|
end
|
||||||
return exting
|
return exting
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function mcl_potions.bad_omen_func(player, factor, duration)
|
||||||
|
if not EF.bad_omen[player] then
|
||||||
|
EF.bad_omen[player] = {dur = duration, timer = 0, factor = factor}
|
||||||
|
else
|
||||||
|
local victim = EF.bad_omen[player]
|
||||||
|
victim.dur = math.max(duration, victim.dur - victim.timer)
|
||||||
|
victim.timer = 0
|
||||||
|
victim.factor = factor
|
||||||
|
end
|
||||||
|
|
||||||
|
if player:is_player() then
|
||||||
|
potions_set_icons(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
BIN
mods/ITEMS/mcl_potions/textures/mcl_potions_effect_bad_omen.png
Normal file
BIN
mods/ITEMS/mcl_potions/textures/mcl_potions_effect_bad_omen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
|
@ -42,6 +42,9 @@ mcl_tnt_griefing (TNT destroys blocks) bool true
|
||||||
# Comma separated list of disabled structure names
|
# Comma separated list of disabled structure names
|
||||||
mcl_disabled_structures (Disabled structures) string
|
mcl_disabled_structures (Disabled structures) string
|
||||||
|
|
||||||
|
# Comma separated list of disabled event names
|
||||||
|
mcl_disabled_events (Disabled events) string
|
||||||
|
|
||||||
[Players]
|
[Players]
|
||||||
# If enabled, players respawn at the bed they last lay on instead of normal
|
# If enabled, players respawn at the bed they last lay on instead of normal
|
||||||
# spawn.
|
# spawn.
|
||||||
|
@ -233,4 +236,7 @@ mcl_logging_mapgen (Chunk generation logging) bool false
|
||||||
mcl_logging_structures (Structure generation logging) bool true
|
mcl_logging_structures (Structure generation logging) bool true
|
||||||
|
|
||||||
#Complete debug logging for mcl_signs events. Use this if you have issues with signs.
|
#Complete debug logging for mcl_signs events. Use this if you have issues with signs.
|
||||||
mcl_logging_mcl_signs (Complete debug logging for mcl_signs) bool true
|
mcl_logging_mcl_signs (Complete debug logging for mcl_signs) bool false
|
||||||
|
|
||||||
|
#Debug logging for mcl_events.
|
||||||
|
mcl_logging_event_api (Debug logging for mcl_events) bool false
|
||||||
|
|
Loading…
Reference in New Issue
Block a user