Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: primal menace mechanics #1415

Merged
merged 26 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e5eac88
feat: added kill events for primal menace and magma bubble
Aug 16, 2023
800ac4d
fixed event registration
Aug 16, 2023
03a2dea
fixed invalid storage path
Aug 16, 2023
0ca8e7b
fixed invalid storage lookup
Aug 16, 2023
da87943
moved code from hazard.lua to primal menace kill. Also modified the p…
Aug 17, 2023
79900a8
Merge branch 'main' into magma-bubble-death-event
luan Aug 17, 2023
8a11e22
Merge branch 'main' into magma-bubble-death-event
Aug 17, 2023
30cf808
fix non player checks for hazard points
Aug 17, 2023
2837912
Merge branch 'magma-bubble-death-event' of github.com:sebbesiren/cana…
Aug 17, 2023
e29dcf9
Merge branch 'main' into magma-bubble-death-event
luan Aug 18, 2023
f62cf69
feat: primal menace boss fight mechanics
Aug 18, 2023
76716c1
Merge branch 'magma-bubble-death-event' of github.com:sebbesiren/cana…
Aug 18, 2023
2c4872f
Merge branch 'main' into magma-bubble-death-event
Aug 18, 2023
c4ddbc0
fix killing monsters causing conversion to fungusaurus to fail
Aug 18, 2023
6517acf
added logs for live debugging
Aug 18, 2023
9e47962
fixed some logs
Aug 18, 2023
84f79be
fixed additional logging
Aug 18, 2023
ea79a1d
rebalanced and removed info
Aug 18, 2023
dcd8f9f
changes based on feedback
Aug 23, 2023
8054b05
Merge branch 'main' into magma-bubble-death-event
Aug 23, 2023
518fe25
updates due to main branch changes
Aug 23, 2023
10e78c8
Merge branch 'main' into magma-bubble-death-event
Aug 24, 2023
3f32f6d
small fixes
luan Aug 26, 2023
b4b206d
Merge branch 'main' into magma-bubble-death-event
dudantas Aug 26, 2023
42bc5b7
fix: typo
dudantas Aug 26, 2023
1e0224b
fix typo
dudantas Aug 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ monster.outfit = {
lookMount = 0
}

monster.events = {
"MagmaBubbleDeath"
}

monster.health = 300000
monster.maxHealth = 300000
monster.race = "undead"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,57 @@
local mType = Game.createMonsterType("The Primal Menace")
local monster = {}

local thePrimalMenaceConfig = {
Storage = {
Initialized = 1,
SpawnPos = 2,
NextPodSpawn = 3,
NextMonsterSpawn = 4,
PrimalBeasts = 5, -- List of monsters and when they were created in order to turn them into fungosaurus {monster, created}
},

-- Spawn area
SpawnRadius = 5,

-- Monster spawn time
MonsterConfig = {
IntervalBase = 30,
IntervalReductionPer10PercentHp = 0.98,
IntervalReductionPerHazard = 0.985,

CountBase = 4,
CountVarianceRate = 0.5,
CountGrowthPerHazard = 1.05,
CountMax = 6,

HpRateOnSpawn = 0.7,
MonsterPool = {
"Emerald Tortoise",
"Beast Gore Horn",
"Beast Gorerilla",
"Headpecker",
"Beast Hulking Prehemoth",
"Mantosaurus",
"Nighthunter",
"Noxious Ripptor",
"Beast Sabretooth",
"Stalking Stalk",
"Sulphider",
}
},

PodConfig = {
IntervalBase = 30,
IntervalReductionPer10PercentHp = 0.98,
IntervalReductionPerHazard = 0.985,

CountBase = 2,
CountVarianceRate = 0.5,
CountGrowthPerHazard = 1.1,
CountMax = 4,
},
}

monster.description = "The Primal Menace"
monster.experience = 80000
monster.outfit = {
Expand All @@ -13,8 +64,12 @@ monster.outfit = {
lookMount = 0
}

monster.health = 5000000
monster.maxHealth = 5000000
monster.events = {
"ThePrimalMenaceDeath"
}

monster.health = 400000
monster.maxHealth = 400000
monster.race = "blood"
monster.corpse = 39530
monster.speed = 180
Expand Down Expand Up @@ -49,56 +104,246 @@ monster.light = {
color = 0
}

monster.summon = {
maxSummons = 2,
summons = {
{name = "Headpecker", chance = 20, interval = 2000},
{name = "Sulphider", chance = 20, interval = 2000},
}
}

monster.voices = {
interval = 5000,
chance = 10,
}

monster.loot = {
{name = "primal bag", chance = 50},
{ name = "primal bag", chance = 50 },
}

monster.attacks = {
{name ="melee", interval = 2000, chance = 85, minDamage = -2500, maxDamage = -3500},
{name ="combat", interval = 4000, chance = 35, type = COMBAT_EARTHDAMAGE, minDamage = -3000, maxDamage = -4000, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false},
{name ="combat", interval = 2500, chance = 45, type = COMBAT_FIREDAMAGE, minDamage = -3000, maxDamage = -7000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false},
{name ="big death wave", interval = 3500, chance = 35, minDamage = -4000, maxDamage = -7000, target = false},
{name ="combat", interval = 5000, chance = 40, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -3500, maxDamage = -4000, range = 4, target = false},
{name ="combat", interval = 2700, chance = 45, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -1500, maxDamage = -4000, range = 4, target = false},
{ name = "melee", interval = 2000, chance = 85, minDamage = -0, maxDamage = -763 },
{ name = "combat", interval = 4000, chance = 35, type = COMBAT_EARTHDAMAGE, minDamage = -1500, maxDamage = -2200, length = 10, spread = 3, effect = CONST_ME_CARNIPHILA, target = false },
{ name = "combat", interval = 2500, chance = 45, type = COMBAT_FIREDAMAGE, minDamage = -700, maxDamage = -1000, length = 10, spread = 3, effect = CONST_ME_HITBYFIRE, target = false },
{ name = "big death wave", interval = 3500, chance = 35, minDamage = -250, maxDamage = -300, target = false },
{ name = "combat", interval = 5000, chance = 40, type = COMBAT_ENERGYDAMAGE, effect = CONST_ME_ENERGYHIT, minDamage = -1200, maxDamage = -1300, range = 4, target = false },
{ name = "combat", interval = 2700, chance = 45, type = COMBAT_EARTHDAMAGE, shootEffect = CONST_ANI_POISON, effect = CONST_ANI_EARTH, minDamage = -600, maxDamage = -1800, range = 4, target = false },
}

monster.defenses = {
defense = 80,
armor = 100
}
armor = 100,
mitigation = 3.72,

monster.elements = {
{type = COMBAT_PHYSICALDAMAGE, percent = 0},
{type = COMBAT_ENERGYDAMAGE, percent = -5},
{type = COMBAT_EARTHDAMAGE, percent = 100},
{type = COMBAT_FIREDAMAGE, percent = 5},
{type = COMBAT_LIFEDRAIN, percent = 0},
{type = COMBAT_MANADRAIN, percent = 0},
{type = COMBAT_DROWNDAMAGE, percent = 0},
{type = COMBAT_ICEDAMAGE, percent = 50},
{type = COMBAT_HOLYDAMAGE , percent = 40},
{type = COMBAT_DEATHDAMAGE , percent = 0}
{ type = COMBAT_PHYSICALDAMAGE, percent = 0 },
{ type = COMBAT_ENERGYDAMAGE, percent = -5 },
{ type = COMBAT_EARTHDAMAGE, percent = 100 },
{ type = COMBAT_FIREDAMAGE, percent = 5 },
{ type = COMBAT_LIFEDRAIN, percent = 0 },
{ type = COMBAT_MANADRAIN, percent = 0 },
{ type = COMBAT_DROWNDAMAGE, percent = 0 },
{ type = COMBAT_ICEDAMAGE, percent = 50 },
{ type = COMBAT_HOLYDAMAGE, percent = 40 },
{ type = COMBAT_DEATHDAMAGE, percent = 0 }
}

monster.immunities = {
{type = "paralyze", condition = true},
{type = "outfit", condition = false},
{type = "invisible", condition = true},
{type = "drunk", condition = true},
{type = "bleed", condition = false}
{ type = "paralyze", condition = true },
{ type = "outfit", condition = false },
{ type = "invisible", condition = true },
{ type = "drunk", condition = true },
{ type = "bleed", condition = false }
}

local function initialize(monster)
if monster:getStorageValue(thePrimalMenaceConfig.Storage.Initialized) == true then
return
end

monster:setStorageValue(thePrimalMenaceConfig.Storage.SpawnPos, monster:getPosition())
monster:setStorageValue(thePrimalMenaceConfig.Storage.NextPodSpawn, os.time() + 20)
monster:setStorageValue(thePrimalMenaceConfig.Storage.NextMonsterSpawn, os.time() + 10)
monster:setStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts, {})

monster:setStorageValue(thePrimalMenaceConfig.Storage.Initialized, true)
end

-- Functions for the fight
mType.onAppear = function(monster, creature)
if monster:getId() == creature:getId() then
initialize(monster)
end

if monster:getType():isRewardBoss() then
monster:setReward(true)
end
end

local function getHazardPoints(monster)
local hazard = Hazard.getByName("hazard.gnomprona-gardens")
if not hazard then return 0 end

local _, hazardPoints = hazard:getHazardPlayerAndPoints(monster:getDamageMap())
return hazardPoints
end

local function setNextTimeToSpawn(monster, spawnStorageValue, spawnConfig, hazardPoints)
local intervalBase = spawnConfig.IntervalBase
local intervalReductionPer10PercentHp = spawnConfig.IntervalReductionPer10PercentHp
local intervalReductionPerHazard = spawnConfig.IntervalReductionPerHazard

local maxHealth = monster:getMaxHealth()
local currentHealth = monster:getHealth()
local count10PercentHpMissing = math.floor((maxHealth - currentHealth) / maxHealth * 10)

local interval = intervalBase * (intervalReductionPer10PercentHp ^ count10PercentHpMissing) * (intervalReductionPerHazard ^ hazardPoints)

local nextTimeToSpawn = os.time() + interval
monster:setStorageValue(spawnStorageValue, nextTimeToSpawn)
end

local function spawnCount(spawnConfig, hazardPoints)
local countBase = spawnConfig.CountBase
local countVarianceRate = spawnConfig.CountVarianceRate
local countGrowthPerHazard = spawnConfig.CountGrowthPerHazard
local countMax = spawnConfig.CountMax

local directions = { -1, 1 }
local variance = math.random() * countVarianceRate * directions[math.random(#directions)]
local count = math.floor(countBase * (countGrowthPerHazard ^ hazardPoints) + variance)

return math.min(count, countMax)
end

local function getSpawnPosition(monster)
local attempts = 6
local attempt = 0
local spawnPosition = nil
local radius = thePrimalMenaceConfig.SpawnRadius
local centerPos = monster:getStorageValue(thePrimalMenaceConfig.Storage.SpawnPos)

while not spawnPosition and attempt < attempts do
local centerX = centerPos.x
local centerY = centerPos.y

local directions = { -1, 1 }
local xCoord = centerX + math.ceil(math.random() * radius) * directions[math.random(#directions)]
local yCoord = centerY + math.ceil(math.random() * radius) * directions[math.random(#directions)]

local positionAttempt = Position(xCoord, yCoord, centerPos.z)
local spawnTile = Tile(positionAttempt)
if spawnTile and spawnTile:getCreatureCount() == 0 and not spawnTile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) then
spawnPosition = positionAttempt
end
attempt = attempt + 1
end

-- Fallback
if not spawnPosition then
spawnPosition = centerPos
end

return spawnPosition
end

local function spawnTimer(monsterId, spawnPosition, spawnCallback)
local time_to_spawn = 3
for i = 1, time_to_spawn do
addEvent(function()
spawnPosition:sendMagicEffect(CONST_ME_TELEPORT)
end, i * 1000)
end
addEvent(function()
spawnCallback(monsterId, spawnPosition)
end, time_to_spawn * 1000)
end

local function spawnPod(monsterId, position)
createPrimalPod(position)
end

local function spawnPods(monster, hazardPoints)
local count = spawnCount(thePrimalMenaceConfig.PodConfig, hazardPoints)
for i = 1, count do
local spawnPosition = getSpawnPosition(monster)
spawnTimer(monster:getId(), spawnPosition, spawnPod)
end
end

local function handlePodSpawn(monster, hazardPoints)
local nextSpawn = monster:getStorageValue(thePrimalMenaceConfig.Storage.NextPodSpawn)
if nextSpawn - os.time() < 0 then
spawnPods(monster, hazardPoints)

setNextTimeToSpawn(monster, thePrimalMenaceConfig.Storage.NextPodSpawn, thePrimalMenaceConfig.PodConfig, hazardPoints)
end
end

local function spawnMonster(monsterId, spawnPosition)
local monster = Monster(monsterId)
if not monster then
return
end

local randomMonsterIndex = math.random(#thePrimalMenaceConfig.MonsterConfig.MonsterPool)
local primalBeastEntry = {
Monster = Game.createMonster(thePrimalMenaceConfig.MonsterConfig.MonsterPool[randomMonsterIndex], spawnPosition),
Created = os.time()
}
local monsterMaxHealth = primalBeastEntry.Monster:getMaxHealth()
primalBeastEntry.Monster:setHealth(monsterMaxHealth * thePrimalMenaceConfig.MonsterConfig.HpRateOnSpawn)

local primalBeasts = monster:getStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts)
table.insert(primalBeasts, primalBeastEntry)
monster:setStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts, primalBeasts)
end

local function spawnMonsters(monster, hazardPoints)
local count = spawnCount(thePrimalMenaceConfig.MonsterConfig, hazardPoints)
for i = 1, count do
local spawnPosition = getSpawnPosition(monster)
spawnTimer(monster:getId(), spawnPosition, spawnMonster)
end
end

local function handleMonsterSpawn(monster, hazardPoints)
local nextSpawn = monster:getStorageValue(thePrimalMenaceConfig.Storage.NextMonsterSpawn)
if nextSpawn - os.time() < 0 then
spawnMonsters(monster, hazardPoints)

setNextTimeToSpawn(monster, thePrimalMenaceConfig.Storage.NextMonsterSpawn, thePrimalMenaceConfig.MonsterConfig, hazardPoints)
end
end

local function handlePrimalBeasts(monster)
local primalBeasts = monster:getStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts)
local indexesToRemove = {}

for index, beastData in pairs(primalBeasts) do
local monster = beastData.Monster
local created = beastData.Created
if not monster:getHealth() or monster:getHealth() == 0 then
table.insert(indexesToRemove, index)
elseif os.time() - created > 20 and monster:getHealth() > 0 then
local position = monster:getPosition()
monster:remove()
table.insert(indexesToRemove, index)
if position then
Game.createMonster("Fungosaurus", position)
end
end
end

for i = #indexesToRemove, 1, -1 do
local indexToRemove = indexesToRemove[i]
table.remove(primalBeasts, indexToRemove)
end

monster:setStorageValue(thePrimalMenaceConfig.Storage.PrimalBeasts, primalBeasts)
end

mType.onThink = function(monster, interval)
if monster:getStorageValue(thePrimalMenaceConfig.Storage.Initialized) == -1 then
initialize(monster)
end

local hazardPoints = getHazardPoints(monster)
handleMonsterSpawn(monster, hazardPoints)
handlePodSpawn(monster, hazardPoints)
handlePrimalBeasts(monster)
end

mType:register(monster)
2 changes: 1 addition & 1 deletion data-otservbr-global/npc/gnomadness.lua
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ local function creatureSayCallback(npc, creature, type, message)
return false
end

local hazard = Hazard.getByName("hazard:gnomprona-gardens")
local hazard = Hazard.getByName("hazard.gnomprona-gardens")
local current = hazard:getPlayerCurrentLevel(player)
local maximum = hazard:getPlayerMaxLevel(player)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local magmaBubbleDeath = CreatureEvent("MagmaBubbleDeath")
function magmaBubbleDeath.onDeath(creature, corpse, killer, mostDamage, unjustified, mostDamage_unjustified)
if not creature then
return
end

local damageMap = creature:getMonster():getDamageMap()

for key, value in pairs(damageMap) do
local player = Player(key)
if player and player:getStorageValue(Storage.Quest.U12_90.PrimalOrdeal.Bosses.MagmaBubbleKilled) < 1 then
player:setStorageValue(Storage.Quest.U12_90.PrimalOrdeal.Bosses.MagmaBubbleKilled, 1) -- Access to The primal menace boss fight
end
end
end

magmaBubbleDeath:register()
Loading