Snake Sneak
Posts: 109
Joined: Wednesday, 2nd February 2011, 03:20
Code / Questions re: code for Testing Grounds Portal
As galehar said, it would be better to keep code and implementation in this subforum.
The original idea with some additional thoughts through the thread is here.
The code (as of this particular moment in time, I'll post updated stuff every so often) is here:
- Code:
###################################################################
#
# Testing Ground Portal
# Created by 7hm.
# Flavour: God challenge arena. Defeat successive waves of monsters
# to collect your reward.
#
#
# PLAN:
#
# Create monsters waves
# Create better randomized monster lists
# Create a random weapon generator
# Create a better random name generator
# Create "boss wave" monster wave
# Randomize # of monster waves
# Randomize # of monsters within waves
# Create more entry portals
# Create more destination portals
# Remove the v_mon_die_call hack if possible
# Create more flavour
# Create crowd noises effect - with possible god intervention effects?
# Remove v_guard as the defining variable and replace it with v_god
# Add error checking where applicable
# Create a template for addition of new vaults (both entry and destination)
# Create a loot table (with adjustment for # of waves, difficulty of waves
# .. god selection and depth found)
# Reward player with loot when all the monsters are defeated
# Add special casing (that works, unlike what I have now) for abyss
# .. and other special effects
# Create a random "status" effect generator so that monsters may be spawned
# .. berserking or otherwise, depending on what the situation allows
# Add tiles for the portal entry (both inactive - but not used, which
# .. will use "stone_arch" as now - and active)
#
{{
-------------------------------------------------------------------
-- Variables
-- : set up the global variables that are used by the various functions
-------------------------------------------------------------------
-- current wave #
v_wave_num = 0
-- number of monsters in the current wave
v_wave_qty = 0
-- how many times callback.dest_mons_die has been called (for that hack I
-- note in the function itself
v_mon_die_call = 0
-- the guardian type (which is used to select monster lists)
-- REMOVE THIS AND REPLACE IT WITH v_god
v_guard = nil
-- the destination - will pick easy / hard maps etc
v_dest = nil
-- the god choice - random with some weighting situationally
v_god = nil
-------------------------------------------------------------------
-- Random Generator Functions
-- : functions that are called to return randomized information
-------------------------------------------------------------------
-- random name generator (I know this could be done better, but
-- for now its fine) it returns a random name (25 possible combos)
function rdm_guard_name()
local a , b
-- first part of the name
if crawl.one_chance_in(5) then
a = "Joh"
elseif crawl.one_chance_in(4) then
a = "Din"
elseif crawl.one_chance_in(3) then
a = "Lai"
elseif crawl.one_chance_in(2) then
a = "Arn"
else
a = "Toms"
end
-- second part of the name
if crawl.one_chance_in(5) then
b = "tu"
elseif crawl.one_chance_in(4) then
b = "parn"
elseif crawl.one_chance_in(3) then
b = "ghie"
elseif crawl.one_chance_in(2) then
b = "yul"
else
b = "rins"
end
-- return the name
return a .. b
end
-- EVENTUALLY REMOVE THIS, REPLACE WITH THE GOD AS THE RANDOM SELECTOR
-- This picks the guardian monster --- at the moment it's just random, but
-- it should be based on depth etc on top of randomization
function rdm_guardian()
-- v_guard is used to create both the monster_list
-- and tie the destination portal into the guardian monster for flavour
if crawl.one_chance_in(2) then
v_guard = "goblin"
return "goblin name:" .. rdm_guard_name() .." name_replace"
else
v_guard = "kobold"
return "kobold name:" .. rdm_guard_name() .." name_replace"
end
end
-- This function returns a random god selection based on various possible
-- reasons to call the function.
-- (At the moment the reasons are: "all" - if you just want to randomly pick
-- a god, "arena" to choose the Training Grounds patron and "cheer" to be
-- used in crowd functionality when that gets implemented.)
function rdm_god(purpose)
local all_rand = crawl.random_range(1,18)
local all_gods = {
[1] = "Zin", [2] = "The Shining One", [3] = "Kikubaaqudgha",
[4] = "Yredelemnul", [5] = "Xom", [6] = "Vehumet",
[7] = "Okawaru", [8] = "Makhleb", [9] = "Sif Muna",
[10] = "Trog", [11] = "Nemelex Xobeh", [12] = "Elyvilon",
[13] = "Lugonu", [14] = "Beogh", [15] = "Fedhas",
[16] = "Cheibriados", [17] = "Ashenzari", [18] = "Jiyva"
}
local arena_rand = crawl.random_range(1,6)
local arena_gods = {
[1] = all_gods[7], [2] = all_gods[10], [3] = all_gods[4],
[4] = all_gods[16], [5] = all_gods[8], [6] = all_gods[2],
}
local cheer_rand = crawl.random_range(1,10)
local cheer_gods = {
[1] = all_gods[2], [2] = all_gods[3], [3] = all_gods[4],
[4] = all_gods[5], [5] = all_gods[6], [6] = all_gods[7],
[7] = all_gods[8], [8] = all_gods[10], [9] = all_gods[11],
[10] = all_gods[16]
}
if purpose == "all" then
return all_gods[all_rand]
elseif purpose == "arena" then
v_god = arena_gods[arena_rand]
return v_god
elseif purpose == "cheer" then
return cheer_gods[cheer_rand]
else
error("No purpose arg given to rdm_god(purpose); Trog is default")
return all_gods[10]
end
end
-- NOT ACTUALLY DOING ANYTHING RIGHT NOW
-- This picks the destination portal, based on the guardian from
-- create_guardian(); at the moment it just returns the default arena
function rdm_destination(a)
return "arena_portal_default"
end
-- This function uses v_guard to create a monster list
-- that is tied into the guardian monster (for flavour). It returns
-- a single monster selection.
function rdm_monster_list(a)
if a == "goblin" then
return "goblin / orc / hobgoblin"
elseif a == "kobold" then
return "kobold / big kobold"
else
error("failure to create monster list")
return "rat"
end
end
-------------------------------------------------------------------
-- Portal Entry Functions
-- : used for entry vaults
-------------------------------------------------------------------
-- This sets up the portal entry that will be triggered when the guardian
-- is killed
function setup_arena_portal(e)
local god = rdm_god("arena")
local guardian = rdm_guardian()
local destination = rdm_destination(v_guard)
local desc_long = "empty for now, please fill me"
e.lua_marker('O',
one_way_stair {
desc = "pulsating archway",
desc_long = desc_long,
entity = 'arena entry',
dst = destination,
floor = "stone_arch" })
e.kfeat("O = stone_arch")
e.kmons("M = " .. guardian)
crawl.mpr("Your god is: " .. god .. ".")
end
-- This function is called when the guardian is killed; it pushes flavour text and
-- calls create_arena.portal.
function callback.entry_mons_die(monster, killer_type, killer_index, silent, wizard)
if killer_type == "reset" then
crawl.mpr("You feel as though a passage to another world has been closed.")
else
crawl.mpr("As you kill the guardian, you sense a passage opening to another world.")
create_arena_portal(_G)
end
end
-- This function is called by callback.entry_mons_die, and turns the empty
-- arch into an actual portal.
function create_arena_portal(e)
local c = dgn.find_marker_positions_by_prop("portal", 1) [1]
local des_name = "arena_portal"
local to_feat = "enter_portal_vault"
dgn.terrain_changed(c.x , c.y , to_feat , false, false, false)
crawl.mpr("vguard is: " .. v_guard .. ".")
end
-------------------------------------------------------------------
-- Portal Destination Functions
-- : used for destination vaults
-------------------------------------------------------------------
-- This function sets up the destination portal tiles, features, etc.
-- You should use symbols from here when designing destination portal vaults.
-- If there is an intersting addition, you can also add to this function
-- so that future vaults may use it.
function arena_setup(e)
e.kfeat("< = exit_portal_vault")
end
-- THIS NEEDS WORK
-- This function is called when you enter through the portal into the destination vault.
-- It asks you whether or not you want to participate in the arena. If you do, it sets up
-- the arena. If you pick no, you end up with an empty vault.
function callback.arena_choice()
if v_god == nil then
v_god = rdm_god("arena")
end
crawl.god_speaks(v_god,"Welcome, mortal, to the Arena." .. v_god )
crawl.god_speaks(v_god,"You have here a choice. You may choose to battle many foes " ..
"and reap the rewards that are due to a champion.")
crawl.god_speaks(v_god,"Or you may choose to flee, as a coward, and forfeit a champion's rewards.")
if crawl.yesno("Will you accept this challenge and fight for your life in THE ARENA?") == true then
callback.place_monsters()
end
end
-- THIS NEEDS WORK: It should be passable multiple times rather than a single time.
-- This function actually creates the monsters based on a list of positions (from
-- rdm_monster_list
function callback.place_monsters()
v_wave_qty = 0
for slave in iter.slave_iterator("monster", 1) do
local monster = rdm_monster_list(v_guard)
dgn.create_monster(slave.x, slave.y, monster)
v_wave_qty = v_wave_qty + 1
end
-- this is just for error checking, should be removed for actual use
crawl.mpr("The number of monsters is: " .. v_wave_qty)
end
-- This function is called when the monsters die. It needs some work and I need
-- to ensure that the hack I used isn't achievable in some other way.
function callback.dest_mons_die(monster, killer_type, killer_index, silent, wizard)
v_mon_die_call = v_mon_die_call + 1
if killer_type == "reset" then
crawl.mpr("something wrong")
else
-- So... this is a hack because I can't figure out how to get the function to
-- only call once when a monster is killed, but still call for every monster.
-- Basically this divides the number of times the function is called into the number
-- of monsters and actually completes when all the monsters are killed. It first
-- error checks to ensure theres no division by zero (though that shouldn't be
-- possible).
-- crawl.mpr(killer_type)
if v_wave_qty == 0 then
error("division by zero in callback.main_mons_die")
return
end
local fake_num = v_mon_die_call / v_wave_qty
if fake_num == v_wave_qty then
crawl.mpr("You have killed all " .. v_wave_qty .. " monsters.")
else
return
end
end
end
-- This function is called by destination vaults and signals that the portal
-- has been completed.
function arena_milestone(e)
crawl.mark_milestone("br.enter", "entered an Arena.", true)
end
}}
#####################
# Entry portals
#####################
# Bare-bones portal vault entry
NAME: stuff_that
TAGS: allow_dup no_monster_gen
ORIENT: float
PLACE: D:1
KFEAT: O = stone_arch
: setup_arena_portal(_G)
{{
lua_marker('O', props_marker { portal = 1 })
}}
MARKER: M = lua: MonPropsMarker:new {monster_dies_lua_key = callback.entry_mons_die}
MAP
.....
.....
..O..
..M..
.....
ENDMAP
####################
# Destination portals
####################
NAME: arena_generic_default
TAGS: arena_portal_default allow_dup
ORIENT: encompass
: arena_setup(_G)
{{
local choice_prompt = TriggerableFunction:new{func="callback.arena_choice"}
kill_mons = TriggerableFunction:new{func="callback.dest_mons_die" ,
repeated = true }
choice_prompt:add_triggerer(DgnTriggerer:new {type="entered_level"})
kill_mons:add_triggerer(DgnTriggerer:new {type="monster_dies" , target = "any"})
lua_marker("<", choice_prompt)
lua_marker("g", props_marker{ monster = 1 })
lua_marker("g", kill_mons)
}}
SUBST: g : .
NSUBST: e = 2:< / *:.
epilogue{{
arena_milestone(_G)
}}
MAP
ccccccccccccccccccccccccccccccccccccccccccc
c...............................g.........c
c...e......g..........................e...c
c.........................................c
c....................g........cc..........c
c.......g......................cc..g......c
c....cc.........................cc........c
c..cccccc.......g..........g.....cc.......c
c....cc...........................ccc.....c
c..................................ccc....c
c..............cccccc.....................c
c...........g..cccccc.......g.............c
c...g..........cccccc............c........c
c..............cccccc...........cc........c
c..............cccccc...........cc...g....c
c.....cc........................cc........c
c....cccc.......................c.........c
c....cccc.......................c.........c
c.....cc.........<..A...........c.........c
c..............................cc.........c
c...........c...........c......cc.........c
c...........c..........ccc.....cc.........c
c.......ccccccccc.....ccccc....c..........c
c...........c..........ccc................c
c...g...ccccccccc.......c...........g.....c
c...........c.............................c
c...........c.............................c
c.........................................c
c.........g...............g.......g.......c
c.....ccc....ccc..........................c
c......ccc..ccc...........................c
c........cccc..........................g..c
c.........cc..g.......ccccccc.............c
c...................ccccccc.......g.......c
c.....g...........ccc.....................c
c...............cccccccc...g..............c
c.................ccc.....................c
c...................ccccccc...............c
c...e.......g.........ccccccc.........e...c
c.........................................c
ccccccccccccccccccccccccccccccccccccccccccc
ENDMAP
Right now I have a couple issues:
a) the monster_die trigger calls itself for every instance of the monster that exists. So if there are 5 monsters to kill, every time you kill one of them it will trigger the function 5 times (for each monster). My workaround is to create a variable that consists of the number of times the function is called / the number of monsters that exist in the wave (which we know from earlier in the setup). We can then check that against the total number of monsters to determine whether or not they've all been killed, and ignore all the extraneous calls. We're still calling the function far more times than we should though. I assume I'm setting this up wrong in some way, any suggestions?
b) how do we count an abyssing as a kill? I don't want you to be able to abyss the guardian and get the portal, but I do want you to be able to abyss the individual monsters from within the portal itself and still get the loot. Right now the exact opposite is happening, and I'm not really sure why. (I just copied the code without really understanding it, from the slime branch and jiyva stuff).