Difference between revisions of "Modding:Tutorial/The Infinite Arena"

From DoomRL Wiki

Jump to: navigation, search
(added code parts)
m (added source template)
Line 637: Line 637:
  
 
==Review==
 
==Review==
{{Modding:Tutorial/The_Infinite_Arena/Source}}
+
{{:Modding:Tutorial/The_Infinite_Arena/Source}}

Revision as of 22:54, 1 October 2011

(NOTE: if you are unfamiliar with the general structure of the Hell's Arena module, it is suggested that you read the tutorial before proceeding, as many of the topics found there also are here, and will only be touched upon in this tutorial.)

Hell's Arena is a fun little level, as it can be fairly difficult to complete without some grasp on DoomRL's mechanics, but pillar and monster placement aside, it is still a fairly static map. The following tutorial is a generalized version of Hell's Arena, coined the Infinite Arena, which has been reconstructed based on yaflhdztioxo's design from v0.9.9.1. A fair number of seemingly-superfluous tools have also been created as a means to provide easy customization possibilities for those who want to quickly change the settings of the Arena. If nothing else, you will discover a variety of helper tools in this tutorial that may prove to be useful in your own modules.

Contents

Special Concept: Custom Tables and Functions

The basics of tables have already been explained in the Game Objects tutorial: it was also explained there that building your own tables is generally unnecessary. There are, however, plenty of reasons to make tables for your own purposes:

  • You want the game to randomly choose from a group of variables
  • You want to perform calculations or subroutines on many objects simultaneously
  • You are creating an object map that other functions may require (e.g., translation table for .run() )
  • You want to store a number of variables in an organized manner in order to access them easily

There are two kinds of tables. The first kind are what are more commonly referred to as tables, as they contain keys and values in each of their fields:

local some_table = {
    key1 = "value1",
    key2 = "value2",
    ....
}

In fact, these tables can contain more tables, like so:

local big_table = {
    small_table1 = {
        key1 = "value1",
        key2 = "value2",
    },
    small_table2 = {
        key1 = "value3",
        key3 = "value4",
    },
}

Note that these sub-tables do not have to carry the same information, although you may want them to (depending on the table's purpose). In doing so, you can create very large and intricate tables to suit your needs.

The second kind of table is commonly known in the modding community as an array, and is essentially a table that does not explicitly write keys in each field. The following examples show an array, followed by a table that imitates the functionality of an array:

local some_array = {"value1","value2","value3"}
 
local some_imit_table = {
    [1] = "value1",
    [2] = "value2",
    [3] = "value3",
}
 
local big_array = {
    {"value1","value2","value3"},
    {"value4","value5","value6"},
}
 
local big_imit_table = {
    [1] = {
        [1] = "value1",
        [2] = "value2",
        [3] = "value3",
    },
    [2] = {
        [1] = "value4",
        [2] = "value5",
        [3] = "value6",
    }, 
}

As you can see, the array format is a lot more compact, but it also forces very specific keys on each field (namely numerical ones). Naturally, tables and arrays can be combined seamlessly, such that you can have a table of arrays or an array or tables (as well as more complicated setups).

When you want to call various parts within a table or array, you can use the following syntax (we will base these on big_array and big_table):

big_table.small_table1        --returns the contents of small_table1
big_table[small_table1]       --identical to above
 
big_table.small_table1.key1   --returns "value1"
big_table.small_table1[key1]  --identical to above
big_table[small_table1].key1  --identical to above
big_table[small_table1[key1]] --identical to above
 
big_array.1     --returns the contents of the first array in big_array
big_array[1]    --identical to above
 
big_array.2.2   --returns "value5"
big_array.2[2]  --identical to above
big_array[2].2  --identical to above
big_array[2[2]] --identical to above

As a matter of convention, we will write table-like extensions using the dot syntax and array-like extensions using the bracket syntax. (This is typically how other languages write their arrays and tables, and it is often a good idea to organize your code in such a manner so that it is easier to read when looking back at it.)

Functions, like tables, are mostly known based on using parts of pre-defined object classes: in the case of functions, we should already be fairly familiar with their capabilities when calling engine hooks and API methods. The creation of a function is very simple, nearly identical to engine hooks.

local function add_diff(arg1,arg2)
    val1 = arg1+arg2
    val2 = arg1-arg2
    return(val1,val2)
end
 
local add_diff = function(arg1,arg2) --this line is identical to the first line of the above function
    ....
end
  • First you must give the function a name: the above example is called "add_diff".
  • Next you must supply input arguments, if any. These are variables that can be used within the function, which is otherwise self-contained. That is to say, a function can't use any variables from anywhere else in your lua script unless they are given through the input arguments. The above example takes in two arguments, generically named "arg1" and "arg2".
  • From here we add the script that is to be run when the function is called (this is usually what you concern yourself with when working with the engine hooks). In the above example, the sum of "arg1" and "arg2" is assigned to "val1", and their difference is assigned to "val2".
  • Finally, we consider what the function should output. This can be accomplished in two ways when modding:
    • You can simply provide outputs to the function using the return() core method. This immediately stops the function and sends outputs to the line that called the function. In the above example, "val1" and "val2" are returned. (Note that, when called, the script should contain some variables so that the return values are properly assigned.)
    • You can manipulate objects. Since changing objects (such as their prototype) modify things on the engine side of the game, this can be done without having any particular outputs to the function. Even though the function is self-contained within your script, it can influence things not belonging to the script.

If you want to call a function, use its name with the appropriate input and outputs. For the above example, you could call the function in the following way:

local num1 = 5
local num2 = 4
 
local sum,diff = add_diff(num1,num2)

The "sum" variable will have a value of 9, and the "diff" variable will have a value of 1.

In the Infinite Arena module, a large portion of the code is dedicated to constructing easy-to-read and well-organized tables, as well as functions that make use of them. The tables and functions will eventually be called in the engine hooks, which leaves us with a relatively small amount of run-time code to sift through. Ideally, modules should have a format in which much of the script is written not as a part of the core module, but as something that the core module will refer to.

Initialization Code

Variables

mobGenStats

--enemy scaling for each wave: note that game difficulty will modify scaling independently
local mob_scale_factor = 0.5
--customize minLev/maxLev/weight of each enemy (defaults given)
--for names of beings, check Object IDs in the modding documentation
--be aware that JC auto-ends the game unless modified directly
local mobGenStats = {
    { being = "former",         minLev = 0,   maxLev = 12,  weight = 10 },
    { being = "sergeant",       minLev = 2,   maxLev = 15,  weight = 10 },
    { being = "captain",        minLev = 8,   maxLev = 15,  weight = 10 },
    { being = "imp",            minLev = 0,   maxLev = 17,  weight = 8  },
    { being = "demon",          minLev = 4,   maxLev = 20,  weight = 6  },
    { being = "lostsoul",       minLev = 6,   maxLev = 16,  weight = 10 },
    { being = "knight",         minLev = 9,   maxLev = 15,  weight = 6  },
    { being = "cacodemon",      minLev = 10,  maxLev = 50,  weight = 6  },
    { being = "commando",       minLev = 12,  maxLev = 17,  weight = 6  },
    { being = "pain",           minLev = 12,  maxLev = 17,  weight = 2  },
    { being = "baron",          minLev = 12,  maxLev = 200, weight = 6  },
    { being = "arachno",        minLev = 13,  maxLev = 50,  weight = 4  },
    { being = "revenant",       minLev = 13,  maxLev = 200, weight = 5  },
    { being = "mancubus",       minLev = 15,  maxLev = 200, weight = 7  },
    { being = "arch",           minLev = 16,  maxLev = 200, weight = 4  },
    { being = "nimp",           minLev = 30,  maxLev = 60,  weight = 8  },
    { being = "ndemon",         minLev = 40,  maxLev = 200, weight = 6  },
    { being = "ncacodemon",     minLev = 51,  maxLev = 200, weight = 6  },
    { being = "narachno",       minLev = 50,  maxLev = 200, weight = 5  },
    { being = "narch",          minLev = 90,  maxLev = 200, weight = 3  },
    { being = "bruiser",        minLev = 50,  maxLev = 200, weight = 6  },
    { being = "lava_elemental", minLev = 70,  maxLev = 200, weight = 1  },
    { being = "shambler",       minLev = 80,  maxLev = 200, weight = 3  },
    { being = "agony",          minLev = 80,  maxLev = 200, weight = 1  },
    { being = "cyberdemon",     minLev = 80,  maxLev = 200, weight = 1  },
    { being = "arenamaster",    minLev = 0,   maxLev = 0,   weight = 0  },
    { being = "angel",          minLev = 0,   maxLev = 0,   weight = 0  },
    { being = "jc",             minLev = 0,   maxLev = 0,   weight = 0  },
}
--changes the generation stats of enemies based on mobGenStats
for _,v in ipairs(mobGenStats) do
    beings[v.being].minLev = v.minLev
    beings[v.being].maxLev = v.maxLev
    beings[v.being].weight = v.weight
end

itemGenStats

--item scaling for each wave
local item_scale_factor = 0.5
--customize level/weight of items (defaults given)
--for names of items, check Object IDs in the modding documentation
local itemGenStats = {
    --Commons
 
    --weapons
    { item = "knife",        level = 1,  weight = 640 },
    { item = "pistol",       level = 1,  weight = 70  },
    { item = "shotgun",      level = 2,  weight = 180 },
    { item = "ashotgun",     level = 2,  weight = 160 },
    { item = "dshotgun",     level = 4,  weight = 100 },
    { item = "chaingun",     level = 5,  weight = 200 },
    { item = "bazooka",      level = 7,  weight = 200 },
    { item = "plasma",       level = 12, weight = 70  },
    --ammo/ammo packs
    { item = "ammo",         level = 1,  weight = 500 },
    { item = "pammo",        level = 3,  weight = 700 },
    { item = "shell",        level = 2,  weight = 400 },
    { item = "pshell",       level = 4,  weight = 200 },
    { item = "rocket",       level = 5,  weight = 60  },
    { item = "procket",      level = 7,  weight = 60  },
    { item = "cell",         level = 8,  weight = 36  },
    { item = "pcell",        level = 10, weight = 18  },
    --armor/boots
    { item = "garmor",       level = 1,  weight = 400 },
    { item = "barmor",       level = 4,  weight = 240 },
    { item = "rarmor",       level = 7,  weight = 150 },
    { item = "sboots",       level = 4,  weight = 240 },
    { item = "pboots",       level = 7,  weight = 150 },
    { item = "psboots",      level = 11, weight = 80  },
    --consumables
    { item = "smed",         level = 1,  weight = 600 },
    { item = "lmed",         level = 5,  weight = 400 },
    { item = "phase",        level = 5,  weight = 200 },
    { item = "hphase",       level = 7,  weight = 100 },
    { item = "epack",        level = 5,  weight = 100 },
    { item = "nuke",         level = 10, weight = 40  },
    { item = "lava_element", level = 23, weight = 0   },
    { item = "mod_power",    level = 7,  weight = 120 },
    { item = "mod_tech",     level = 6,  weight = 120 },
    { item = "mod_bulk",     level = 6,  weight = 120 },
    { item = "mod_agility",  level = 5,  weight = 120 },
    --powerups
    { item = "shglobe",      level = 1,  weight = 900 },
    { item = "lhglobe",      level = 6,  weight = 330 },
    { item = "scglobe",      level = 4,  weight = 150 },
    { item = "bpack",        level = 1,  weight = 200 },
    { item = "iglobe",       level = 7,  weight = 200 },
    { item = "msglobe",      level = 16, weight = 60  },
    { item = "map",          level = 1,  weight = 200 },
    { item = "pmap",         level = 1,  weight = 80  },
    { item = "ashard",       level = 5,  weight = 700 },
    { item = "backpack",     level = 7,  weight = 0   },
 
    --Exotics
 
    --weapons
    { item = "chainsaw",        level = 12, weight = 3  },
    { item = "ublaster",        level = 4,  weight = 2  },
    { item = "uashotgun",       level = 6,  weight = 6  },
    { item = "upshotgun",       level = 12, weight = 4  },
    { item = "udshotgun",       level = 10, weight = 5  },
    { item = "uminigun",        level = 10, weight = 6  },
    { item = "umbazooka",       level = 10, weight = 6  },
    { item = "unapalm",         level = 10, weight = 6  },
    { item = "ulaser",          level = 12, weight = 5  },
    { item = "unplasma",        level = 15, weight = 4  },
    { item = "utristar",        level = 12, weight = 4  },
    { item = "bfg9000",         level = 20, weight = 2  },
    { item = "unbfg9000",       level = 22, weight = 2  },
    { item = "utrans",          level = 14, weight = 3  },
    --armor/boots
    { item = "uoarmor",         level = 7,  weight = 4  },
    { item = "uparmor",         level = 10, weight = 6  },
    { item = "upboots",         level = 8,  weight = 6  },
    { item = "ugarmor",         level = 15, weight = 6  },
    { item = "ugboots",         level = 10, weight = 6  },
    { item = "umedarmor",       level = 5,  weight = 6  },
    { item = "uduelarmor",      level = 5,  weight = 6  },
    { item = "ubulletarmor",    level = 2,  weight = 4  },
    { item = "uballisticarmor", level = 2,  weight = 5  },
    { item = "ueshieldarmor",   level = 5,  weight = 3  },
    { item = "uplasmashield",   level = 10, weight = 3  },
    { item = "uenergyshield",   level = 8,  weight = 3  },
    { item = "ubalshield",      level = 6,  weight = 3  },
    { item = "uacidboots",      level = 8,  weight = 5  },
    --consumables
    { item = "uswpack",         level = 5,  weight = 10 },
    { item = "ubskull",         level = 5,  weight = 8  },
    { item = "ufskull",         level = 7,  weight = 8  },
    { item = "uhskull",         level = 9,  weight = 8  },
    { item = "umod_firestorm",  level = 10, weight = 4  },
    { item = "umod_sniper",     level = 10, weight = 2  },
 
    --Uniques
 
    --weapons	
    { item = "ubutcher",     level = 1,  weight = 2 },
    { item = "spear",        level = 16, weight = 0 },
    { item = "uscythe",      level = 16, weight = 0 },
    { item = "utrigun",      level = 8,  weight = 2 },
    { item = "ujackal",      level = 10, weight = 2 },
    { item = "uberetta",     level = 6,  weight = 3 },
    { item = "usjack",       level = 12, weight = 2 },
    { item = "urbazooka",    level = 12, weight = 2 },
    { item = "uacid",        level = 12, weight = 3 },
    { item = "urailgun",     level = 15, weight = 2 },
    { item = "ubfg10k",      level = 20, weight = 1 },
    --armor/boots
    { item = "umarmor",      level = 15, weight = 3 },
    { item = "ucarmor",      level = 10, weight = 2 },
    { item = "unarmor",      level = 10, weight = 3 },
    { item = "umedparmor",   level = 10, weight = 2 },
    { item = "ulavaarmor",   level = 12, weight = 2 },
    { item = "uenviroboots", level = 10, weight = 2 },
    { item = "ushieldarmor", level = 10, weight = 2 },
    { item = "aarmor",       level = 22, weight = 0 },
    --consumables
    { item = "uhwpack",      level = 10, weight = 4 },
    { item = "umodstaff",    level = 15, weight = 4 },
    { item = "uarenastaff",  level = 4,  weight = 0 },
    { item = "umod_nano",    level = 10, weight = 1 },
    { item = "umod_onyx",    level = 10, weight = 1 },	
}
--changes the generation stats of items based on itemGenStats
for _,v in ipairs(itemGenStats) do
    items[v.item].level  = v.level
    items[v.item].weight = v.weight
end

anncrMsg and crowdMsg

--add your own announcer lines here
local anncrMsg = {
    --displays whenever player completes wave
    success = {
        {
        "The voice booms, \"Congratulations mortal!",
        "The voice booms, \"Impressive mortal!",
        "The voice booms, \"Most impressive.",
        "The voice booms, \"You are a formidable warrior!",
        },
        {
        "Each of your triumphs is a work of art!",
        "Your ability to survive is incredible!",
        "You've given us a great show!",
        "You would make a terrific hell warrior!",
        },
        {
        "But can you keep going?\"",
        "How much longer can you go?\"",
        "Will you fight with us a little more?\"",
        "I can let you go now if you like, or...\"",
        }
    },
    --displays whenever player decides to continue to another wave
    choice = {
        {
        "The voice booms, \"I like it! Let the show go on!\"",
        "The voice booms, \"Excellent! May the fight begin!!!\"",
        },
        {
        "You hear screams everywhere! \"More Blood! More BLOOD!\"",
        "You hear screams everywhere! \"Kill, Kill, KILL!\"",
        }
    }
}
--add your own crowd lines here
--displays whenever enemy is killed
local crowdMsg = {
    "The crowd goes wild! \"BLOOD! BLOOD!\"",
    "The crowd cheers! \"Blood! Blood!\"",
    "The crowd cheers! \"Kill! Kill!\"",
    "The crowd hisses. \"We came for a REAL fight!\"",
    "The crowd boos. \"No skill, no kill!\"",
}

Functions

build_gear()

--changes player equipment
--add "nil" for any slot that should be empty
--always refreshes all slots: do not use to change only one equipment slot
local function build_gear(weap,prep,body,foot)
    player.eq:clear()
    if weap then player.eq.weapon   = item.new(weap) end --main weapon
    if prep then player.eq.prepared = item.new(prep) end --prepared weapon	
    if body then player.eq.armor    = item.new(body) end --body armor
    if foot then player.eq.boots    = item.new(foot) end --boots
end

build_pack()

--changes player inventory
--always refreshes all slots: do not use to change only one equipment slot
local function build_pack(itemPack)
    player.inv:clear()
    for _,part in ipairs(itemPack) do
        if items[part.name].type == ITEMTYPE_AMMO then
            --ammo is handled separately, loops for however many stacks are needed
            for n=1,math.ceil(part.amt/items[part.name].ammomax) do
                local pack = item.new(part.name)
                if part.amt < pack.ammomax then
                    --remainder (or single) stack
                    pack.ammo = part.amt
                else
                    --filled (and/or multiple) stacks
                    pack.ammo = pack.ammomax
                end
                player.inv:add(pack)
            end
        else
            --loops for however many items are needed
            for n=1,part.amt do
                player.inv:add(part.name)
            end
        end
    end
end

build_announcement()

local function build_announcerMsg(MSG)
    for _,msgNum in ipairs(MSG) do
        ui.msg(table.random_pick(msgNum))
    end
end

get_corpses() and clear_corpses()

--initializes table of cells that a considered corpses
local corpseCells = {}
 
local function get_corpses
    for i = 1, #cells do
        if cells[i] and cells[i].flags then
            if cells[i].flag_set[CF_CORPSE] == true then
                table.insert(corpseCells, i) --i == sID's numeric value
            end
        end
    end
end
--removes corpses and blood-like cells
local function clear_corpses()
    --fade away all blood
    Generator.transmute("blood", "floor")
    Generator.transmute("bloodpool", "blood")
    --changes corpses to blood
    for i = 1, #corpseCells do
        Generator.transmute(CorpseCells[i], "bloodpool")
    end
end

Engine Hooks

.run()

--Creation of Infinite Arena
function inf_arena.run()
    Level.name = "Infinite Arena"
    Level.name_number = 0
    Level.fill("prwall")
 
    local translation = {
    ["."] = "floor",
    ["#"] = "prwall",
    ["X"] = "pwall",
    [","] = "blood",
    [">"] = "stairs",
    }
 
    local map = [[
#######################.............................########################
###########.....................................................############
#####..................................................................#####
##........................................................................##
#..........................................................................#
....,.......................................................................
.................................,...,......................................
.,.....................................,....................................
..,>.,..........................,...........................................
.,..,.................................,.,...................................
................................,..,...,....................................
............................................................................
............................................................................
#..........................................................................#
##........................................................................##
#####..................................................................#####
###########.....................................................############
#######################.............................########################
]]
    --change up the column types
    local column = {
    [[
,..,.,
,XXXX.
.X##X,
.XXXX.
,..,.,
    ]],
    [[
,..,.,
,X##X.
.####,
.X##X.
,..,.,
    ]],
    [[
,..,.,
,####.
.####,
.####.
,..,.,
    ]]
    }
 
    Level.place_tile(translation,map,2,2)
    for i=1,11 + math.random(4) do
        Level.scatter_put( area.new(5,3,68,15), translation, table.random_pick(column), "floor", 1)
    end
    Level.scatter(area.FULL_SHRINKED, "floor", "blood", 100)
    Level.player(38, 10)
end

.OnEnter()

function inf_arena.OnEnter()
    --inventory table used for build_pack()
    local itemPack = {
        {name = "smed",  amt = 2},
        {name = "shell", amt = 50},
    }
    --set up starting eq/inv
    build_gear("shotgun","pistol",nil,nil)
    build_pack(itemPack)
 
    --Print announcer messages
    ui.msg("A devilish voice announces: \"Welcome to Hell's Arena, mortal! " ..
           "\"You are either very brave or very foolish. Either way I like it! " ..
           "\"And so do the crowds!\" Suddenly you hear screams everywhere! " ..
	   "\"Blood! Blood! BLOOD!\" \"Kill all enemies and I shall reward thee!\"")
 
    --spawn first wave
    Level.flood_monsters(Generator.being_weight()*mob_scale_factor)
    Level.result(1)
end

.OnKill()

function inf_arena.OnKill(being)
    --random message from crowdMsg array
    ui.msg(table.random_pick(crowdMsg))
end

.OnKillAll()

function inf_arena.OnKillAll()
    --print more announcer stuff
    if Level.result() == 1 then
        ui.msg("The voice booms, \"Not bad mortal! For a weakling that you are, " ..
               "you show some determination.\" You hear screams everywhere! " ..
               "\"More Blood! More BLOOD!\" The voice continues, \"I can now " ..
               "let you go free, or you may try to complete the challenge!\"")
    elseif Level.result() == 2 then
        ui.msg("The voice booms, \"Impressive mortal! Your determination to " ..
               "survive makes me excited!\" You hear screams everywhere! " ..
               "\"More Blood! More BLOOD!\" \"I can let you go now, and give you " ..
               "a small reward, or you can choose to fight an additional challenge!\"")
    else
        --random message from anncrMsg.success array
        build_announcerMsg(anncrMsg.success)
    end
 
    local choice = ui.msg_confirm("Round " .. Level.result() + 1 .. " awaits. " ..
                                  "Do you want to continue the fight?")
    if choice == true then --continuing
        --random message from anncrMsg.choice array
        build_announcerMsg(anncrMsg.choice)
        --set up the danger level for the next wave
        Level.result(Level.result() + 1);
        Level.danger_level = Level.result();
        --spawn items for next wave
        Level.flood_items(Generator.item_amount()*item_scale_factor)
        --spawn enemies for next wave
        Level.flood_monsters(Generator.being_weight()*mob_scale_factor)
 
    else --quitting
        if Level.result() == 1 then
            ui.msg("The voice booms, \"Coward!\" You hear screams everywhere! " ..
                   "\"Coward! Coward! COWARD!\"")
        elseif Level.result() == 2 then
            ui.msg("The voice booms, \"Too bad, you won't make it far then...!\" " ..
                   "You hear screams everywhere! \"Boooo...\"")
        elseif Level.result() < 10 then
            ui.msg("The voice booms, \"An impressive run, Mortal!  We appreciate " ..
                   "it!\" The crowd starts to chant! \"Encore! Encore!\"")
        else
            ui.msg("\"Ladies and gentlemen, your champion, "
                   .. Player.get_name() .. ". He survived " .. Level.result() ..
                   " rounds in our arena! That has to be some sort of record. " ..
                   "Give him a hand folks!\" The crowd starts to chant your name " ..
                   "and they begin throwing items into the ring!")
            Level.flood_items(Generator.item_weight())
        end
    end
end

.OnExit()

function inf_arena.OnExit()
    if Level.result() < 10 then
        ui.msg("The voice laughs, \"Flee mortal, flee! There's no hiding in hell!\"")
    else
        ui.msg("The voice laughs, \"Remember to come back once you return to Hell " ..
               "for the extended stay\"")
    end
    --used in .OnMortem()
    arena.result = "had enough of the gauntlet at wave "..Level.result()
end

.OnMortem()

function arena.OnMortem()
    local kill = player.killedby --calls kill descriptions from beings
    if arena.result then kill = arena.result end
        player:mortem_print( " "..player.name..", level "..player.explevel.." "
                             .." "..klasses[player.klass].name..", "..kill )
        --e.g., "Cool Guy, level 1 Marine, had enough of the gauntlet at wave 8"
        player:mortem_print(" in the Infinite Arena...")
end

Review

core.declare("inf_arena", {} )
 
--enemy scaling for each wave: note that game difficulty will modify scaling independently
local mob_scale_factor = 0.5
--customize min_lev/max_lev/weight of each enemy (defaults given)
--for names of beings, check Object IDs in the modding documentation
--be aware that JC auto-ends the game unless modified directly
local mobGenStats = {
    { being = "former",         min_lev = 0,   max_lev = 12,  weight = 10 },
    { being = "sergeant",       min_lev = 2,   max_lev = 15,  weight = 10 },
    { being = "captain",        min_lev = 8,   max_lev = 15,  weight = 10 },
    { being = "imp",            min_lev = 0,   max_lev = 17,  weight = 8  },
    { being = "demon",          min_lev = 4,   max_lev = 20,  weight = 6  },
    { being = "lostsoul",       min_lev = 6,   max_lev = 16,  weight = 10 },
    { being = "knight",         min_lev = 9,   max_lev = 15,  weight = 6  },
    { being = "cacodemon",      min_lev = 10,  max_lev = 50,  weight = 6  },
    { being = "commando",       min_lev = 12,  max_lev = 17,  weight = 6  },
    { being = "pain",           min_lev = 12,  max_lev = 17,  weight = 2  },
    { being = "baron",          min_lev = 12,  max_lev = 200, weight = 6  },
    { being = "arachno",        min_lev = 13,  max_lev = 50,  weight = 4  },
    { being = "revenant",       min_lev = 13,  max_lev = 200, weight = 5  },
    { being = "mancubus",       min_lev = 15,  max_lev = 200, weight = 7  },
    { being = "arch",           min_lev = 16,  max_lev = 200, weight = 4  },
    { being = "nimp",           min_lev = 30,  max_lev = 60,  weight = 8  },
    { being = "ndemon",         min_lev = 40,  max_lev = 200, weight = 6  },
    { being = "ncacodemon",     min_lev = 51,  max_lev = 200, weight = 6  },
    { being = "narachno",       min_lev = 50,  max_lev = 200, weight = 5  },
    { being = "narch",          min_lev = 90,  max_lev = 200, weight = 3  },
    { being = "bruiser",        min_lev = 50,  max_lev = 200, weight = 6  },
    { being = "lava_elemental", min_lev = 70,  max_lev = 200, weight = 1  },
    { being = "shambler",       min_lev = 80,  max_lev = 200, weight = 3  },
    { being = "agony",          min_lev = 80,  max_lev = 200, weight = 1  },
    { being = "cyberdemon",     min_lev = 80,  max_lev = 200, weight = 1  },
    { being = "arenamaster",    min_lev = 0,   max_lev = 0,   weight = 0  },
    { being = "angel",          min_lev = 0,   max_lev = 0,   weight = 0  },
    { being = "jc",             min_lev = 0,   max_lev = 0,   weight = 0  },
}
 
--item scaling for each wave
local item_scale_factor = 0.5
--customize level/weight of items (defaults given)
--for names of items, check Object IDs in the modding documentation
local itemGenStats = {
    --Commons
 
    --weapons
    { item = "knife",        level = 1,  weight = 640 },
    { item = "pistol",       level = 1,  weight = 70  },
    { item = "shotgun",      level = 2,  weight = 180 },
    { item = "ashotgun",     level = 2,  weight = 160 },
    { item = "dshotgun",     level = 4,  weight = 100 },
    { item = "chaingun",     level = 5,  weight = 200 },
    { item = "bazooka",      level = 7,  weight = 200 },
    { item = "plasma",       level = 12, weight = 70  },
    --ammo/ammo packs
    { item = "ammo",         level = 1,  weight = 500 },
    { item = "pammo",        level = 3,  weight = 700 },
    { item = "shell",        level = 2,  weight = 400 },
    { item = "pshell",       level = 4,  weight = 200 },
    { item = "rocket",       level = 5,  weight = 60  },
    { item = "procket",      level = 7,  weight = 60  },
    { item = "cell",         level = 8,  weight = 36  },
    { item = "pcell",        level = 10, weight = 18  },
    --armor/boots
    { item = "garmor",       level = 1,  weight = 400 },
    { item = "barmor",       level = 4,  weight = 240 },
    { item = "rarmor",       level = 7,  weight = 150 },
    { item = "sboots",       level = 4,  weight = 240 },
    { item = "pboots",       level = 7,  weight = 150 },
    { item = "psboots",      level = 11, weight = 80  },
    --consumables
    { item = "smed",         level = 1,  weight = 600 },
    { item = "lmed",         level = 5,  weight = 400 },
    { item = "phase",        level = 5,  weight = 200 },
    { item = "hphase",       level = 7,  weight = 100 },
    { item = "epack",        level = 5,  weight = 100 },
    { item = "nuke",         level = 10, weight = 40  },
    { item = "lava_element", level = 23, weight = 0   },
    { item = "mod_power",    level = 7,  weight = 120 },
    { item = "mod_tech",     level = 6,  weight = 120 },
    { item = "mod_bulk",     level = 6,  weight = 120 },
    { item = "mod_agility",  level = 5,  weight = 120 },
    --powerups
    { item = "shglobe",      level = 1,  weight = 900 },
    { item = "lhglobe",      level = 6,  weight = 330 },
    { item = "scglobe",      level = 4,  weight = 150 },
    { item = "bpack",        level = 1,  weight = 200 },
    { item = "iglobe",       level = 7,  weight = 200 },
    { item = "msglobe",      level = 16, weight = 60  },
    { item = "map",          level = 1,  weight = 200 },
    { item = "pmap",         level = 1,  weight = 80  },
    { item = "ashard",       level = 5,  weight = 700 },
    { item = "backpack",     level = 7,  weight = 0   },
 
    --Exotics
 
    --weapons
    { item = "chainsaw",        level = 12, weight = 3  },
    { item = "ublaster",        level = 8,  weight = 2  },
    { item = "ucpistol",        level = 4,  weight = 6  },
    { item = "uashotgun",       level = 6,  weight = 6  },
    { item = "upshotgun",       level = 12, weight = 4  },
    { item = "udshotgun",       level = 10, weight = 5  },
    { item = "uminigun",        level = 10, weight = 6  },
    { item = "umbazooka",       level = 10, weight = 6  },
    { item = "unapalm",         level = 10, weight = 6  },
    { item = "ulaser",          level = 12, weight = 5  },
    { item = "unplasma",        level = 15, weight = 4  },
    { item = "utristar",        level = 12, weight = 4  },
    { item = "bfg9000",         level = 20, weight = 2  },
    { item = "unbfg9000",       level = 22, weight = 2  },
    { item = "utrans",          level = 14, weight = 3  },
    --armor/boots
    { item = "uoarmor",         level = 7,  weight = 4  },
    { item = "uparmor",         level = 10, weight = 6  },
    { item = "upboots",         level = 8,  weight = 6  },
    { item = "ugarmor",         level = 15, weight = 6  },
    { item = "ugboots",         level = 10, weight = 6  },
    { item = "umedarmor",       level = 5,  weight = 6  },
    { item = "uduelarmor",      level = 5,  weight = 6  },
    { item = "ubulletarmor",    level = 2,  weight = 4  },
    { item = "uballisticarmor", level = 2,  weight = 5  },
    { item = "ueshieldarmor",   level = 5,  weight = 3  },
    { item = "uplasmashield",   level = 10, weight = 3  },
    { item = "uenergyshield",   level = 8,  weight = 3  },
    { item = "ubalshield",      level = 6,  weight = 3  },
    { item = "uacidboots",      level = 8,  weight = 5  },
    --consumables
    { item = "uswpack",         level = 5,  weight = 10 },
    { item = "ubskull",         level = 5,  weight = 8  },
    { item = "ufskull",         level = 7,  weight = 8  },
    { item = "uhskull",         level = 9,  weight = 8  },
    { item = "umod_firestorm",  level = 10, weight = 4  },
    { item = "umod_sniper",     level = 10, weight = 4  },
    { item = "umod_nano",       level = 10, weight = 4  },
    { item = "umod_onyx",       level = 10, weight = 4  },
 
    --Uniques
 
    --weapons	
    { item = "ubutcher",     level = 1,  weight = 2 },
    { item = "spear",        level = 16, weight = 0 },
    { item = "uscythe",      level = 16, weight = 0 },
    { item = "udragon",      level = 16, weight = 1 },
    { item = "utrigun",      level = 8,  weight = 2 },
    { item = "ujackal",      level = 10, weight = 2 },
    { item = "uberetta",     level = 6,  weight = 3 },
    { item = "usjack",       level = 12, weight = 2 },
    { item = "urbazooka",    level = 12, weight = 2 },
    { item = "uacid",        level = 12, weight = 3 },
    { item = "urailgun",     level = 15, weight = 2 },
    { item = "ubfg10k",      level = 20, weight = 1 },
    --armor/boots
    { item = "umarmor",      level = 15, weight = 3 },
    { item = "ucarmor",      level = 10, weight = 2 },
    { item = "unarmor",      level = 10, weight = 3 },
    { item = "umedparmor",   level = 10, weight = 2 },
    { item = "ulavaarmor",   level = 12, weight = 2 },
    { item = "uenviroboots", level = 10, weight = 2 },
    { item = "ushieldarmor", level = 10, weight = 2 },
    { item = "uberarmor",    level = 10, weight = 1 },
    { item = "aarmor",       level = 22, weight = 0 },
    --consumables
    { item = "uhwpack",      level = 10, weight = 4 },
    { item = "umodstaff",    level = 15, weight = 4 },
    { item = "uarenastaff",  level = 4,  weight = 0 },
}
 
--changes the generation stats of enemies based on mobGenStats
for _,v in ipairs(mobGenStats) do
    beings[v.being].min_lev = v.min_lev
    beings[v.being].max_lev = v.max_lev
    beings[v.being].weight = v.weight
end
 
--changes the generation stats of items based on itemGenStats
for _,v in ipairs(itemGenStats) do
    items[v.item].level  = v.level
    items[v.item].weight = v.weight
end
 
--add your own announcer lines here
local anncrMsg = {
    --displays whenever player completes wave
    success = {
        {
        "The voice booms, \"Congratulations mortal!",
        "The voice booms, \"Impressive mortal!",
        "The voice booms, \"Most impressive.",
        "The voice booms, \"You are a formidable warrior!",
        },
        {
        "Each of your triumphs is a work of art!",
        "Your ability to survive is incredible!",
        "You've given us a great show!",
        "You would make a terrific hell warrior!",
        },
        {
        "But can you keep going?\"",
        "How much longer can you go?\"",
        "Will you fight with us a little more?\"",
        "I can let you go now if you like, or...\"",
        }
    },
    --displays whenever player decides to continue to another wave
    choice = {
        {
        "The voice booms, \"I like it! Let the show go on!\"",
        "The voice booms, \"Excellent! May the fight begin!!!\"",
        },
        {
        "You hear screams everywhere! \"More Blood! More BLOOD!\"",
        "You hear screams everywhere! \"Kill, Kill, KILL!\"",
        }
    }
}
 
--add your own crowd lines here
--displays whenever enemy is killed
local crowdMsg = {
    "The crowd goes wild! \"BLOOD! BLOOD!\"",
    "The crowd cheers! \"Blood! Blood!\"",
    "The crowd cheers! \"Kill! Kill!\"",
    "The crowd hisses. \"We came for a REAL fight!\"",
    "The crowd boos. \"No skill, no kill!\"",
}
 
local function build_announcerMsg(MSG)
    for _,msgNum in ipairs(MSG) do
        ui.msg(table.random_pick(msgNum))
    end
end
 
--from Skulltag Arena, credit goes to yaflhdztioxo
--initializes table of cells that contains only corpses
local corpseCells = {}
 
local function get_corpses()
    for i = 1, #cells do
        if cells[i] and cells[i].flags then
            if cells[i].flag_set[CF_CORPSE] == true then
                table.insert(corpseCells, i) --i == sID's numeric value
            end
        end
    end
end
 
--removes corpses and blood-like cells
local function clear_corpses()
    --fade away all blood
    Generator.transmute("blood", "floor")
    Generator.transmute("bloodpool", "blood")
    --changes corpses to blood
    for i = 1, #corpseCells do
        Generator.transmute(corpseCells[i], "bloodpool")
    end
end
 
--changes player equipment
--add "nil" for any slot that should be empty
--always refreshes all slots: do not use to change only one equipment slot
local function build_gear(weap,prep,body,foot)
    player.eq:clear()
    if weap then player.eq.weapon   = item.new(weap) end --main weapon
    if prep then player.eq.prepared = item.new(prep) end --prepared weapon	
    if body then player.eq.armor    = item.new(body) end --body armor
    if foot then player.eq.boots    = item.new(foot) end --boots
end
 
--changes player inventory
--always refreshes all slots: do not use to change only one equipment slot
--[[
input is an array of tables with two fields:
-name is the identifier of the item that goes in the inventory
-amt is how many items should be added (or how many units of ammo)
example table (based on default inventory):
 
local starter_pack = {
    {name = "ammo", amt = 24},
    {name = "smed", amt = 2},
}
--]]
local function build_pack(itemPack)
    player.inv:clear()
    for _,part in ipairs(itemPack) do
        if items[part.name].type == ITEMTYPE_AMMO then
            --ammo is handled separately, loops for however many stacks are needed
            for n=1,math.ceil(part.amt/items[part.name].ammomax) do
                local pack = item.new(part.name)
                if part.amt < pack.ammomax then
                    --remainder (or single) stack
                    pack.ammo = part.amt
                else
                    --filled (and/or multiple) stacks
                    pack.ammo = pack.ammomax
                    part.amt = part.amt - pack.ammomax
                end
                player.inv:add(pack)
            end
        else
            --loops for however many items are needed
            for n=1,part.amt do
                player.inv:add(part.name)
            end
        end
    end
end
 
 
 
function inf_arena.OnEnter()
    --inventory table used for build_pack()
    local itemPack = {
        {name = "smed",  amt = 2},
        {name = "shell", amt = 50},
    }
    --set up starting eq/inv
    build_gear("shotgun","pistol",nil,nil)
    build_pack(itemPack)
 
    --Print announcer messages
    ui.msg("A devilish voice announces: \"Welcome to Hell's Arena, mortal! " ..
           "\"You are either very brave or very foolish. Either way I like it! " ..
           "\"And so do the crowds!\" Suddenly you hear screams everywhere! " ..
           "\"Blood! Blood! BLOOD!\" \"Kill all enemies and I shall reward thee!\"")
 
    --spawn first wave
    Level.flood_monsters(Generator.being_weight()*mob_scale_factor)
    Level.result(1)
end
 
function inf_arena.OnKill(being)
    --random message from crowdMsg array
    ui.msg(table.random_pick(crowdMsg))
end
 
function inf_arena.OnKillAll()
    --print more announcer stuff
    if Level.result() == 1 then
        ui.msg("The voice booms, \"Not bad mortal! For a weakling that you are, " ..
               "you show some determination.\" You hear screams everywhere! " ..
               "\"More Blood! More BLOOD!\" The voice continues, \"I can now " ..
               "let you go free, or you may try to complete the challenge!\"")
    elseif Level.result() == 2 then
        ui.msg("The voice booms, \"Impressive mortal! Your determination to " ..
               "survive makes me excited!\" You hear screams everywhere! " ..
               "\"More Blood! More BLOOD!\" \"I can let you go now, and give you " ..
               "a small reward, or you can choose to fight an additional challenge!\"")
    else
        --random message from anncrMsg.success array
        build_announcerMsg(anncrMsg.success)
    end
 
    local choice = ui.msg_confirm("Round " .. Level.result() + 1 .. " awaits. " ..
                                  "Do you want to continue the fight?")
    if choice == true then --continuing
        --random message from anncrMsg.choice array
        build_announcerMsg(anncrMsg.choice)
        --set up the danger level for the next wave
        Level.result(Level.result() + 1);
        Level.danger_level = Level.result();
        --spawn items for next wave
        Level.flood_items(Generator.item_amount()*item_scale_factor)
        --spawn enemies for next wave
        Level.flood_monsters(Generator.being_weight()*mob_scale_factor)
 
    else --quitting
        if Level.result() == 1 then
            ui.msg("The voice booms, \"Coward!\" You hear screams everywhere! " ..
                   "\"Coward! Coward! COWARD!\"")
        elseif Level.result() == 2 then
            ui.msg("The voice booms, \"Too bad, you won't make it far then...!\" " ..
                   "You hear screams everywhere! \"Boooo...\"")
        elseif Level.result() < 10 then
            ui.msg("The voice booms, \"An impressive run, Mortal!  We appreciate " ..
                   "it!\" The crowd starts to chant! \"Encore! Encore!\"")
        else
            ui.msg("\"Ladies and gentlemen, your champion, "
                   .. Player.get_name() .. ". He survived " .. Level.result() ..
                   " rounds in our arena! That has to be some sort of record. " ..
                   "Give him a hand folks!\" The crowd starts to chant your name " ..
                   "and they begin throwing items into the ring!")
            Level.flood_items(Generator.item_weight())
        end
    end
end
 
function inf_arena.OnExit()
    if Level.result() < 10 then
        ui.msg("The voice laughs, \"Flee mortal, flee! There's no hiding in hell!\"")
    else
        ui.msg("The voice laughs, \"Remember to come back once you return to Hell " ..
               "for the extended stay\"")
    end
    --used in .OnMortem()
    inf_arena.result = "had enough of the gauntlet at wave "..Level.result()
end
 
function inf_arena.OnMortem()
    local kill = player.killedby --calls kill descriptions from beings
    if inf_arena.result then kill = inf_arena.result end
        player:mortem_print( " "..player.name..", level "..player.explevel.." "
                             .." "..klasses[player.klass].name..", "..kill )
        --e.g., "Cool Guy, level 1 Marine, had enough of the gauntlet at wave 8"
        player:mortem_print(" in the Infinite Arena...")
end
 
--Creation of Infinite Arena
function inf_arena.run()
    Level.name = "Infinite Arena"
    Level.name_number = 0
    Level.fill("rwall")
 
    local translation = {
    ["."] = "floor",
    ["#"] = {"rwall", LFPERMANENT = true},
    ["X"] = "rwall",
    [","] = "blood",
    [">"] = "stairs",
    }
 
    local map = [[
#######################.............................########################
###########.....................................................############
#####..................................................................#####
##........................................................................##
#..........................................................................#
....,.......................................................................
.................................,...,......................................
.,.....................................,....................................
..,>.,..........................,...........................................
.,..,.................................,.,...................................
................................,..,...,....................................
............................................................................
............................................................................
#..........................................................................#
##........................................................................##
#####..................................................................#####
###########.....................................................############
#######################.............................########################
]]
    --change up the column types
    local column = {
    [[
,..,.,
,XXXX.
.X##X,
.XXXX.
,..,.,
    ]],
    [[
,..,.,
,X##X.
.####,
.X##X.
,..,.,
    ]],
    [[
,..,.,
,####.
.####,
.####.
,..,.,
    ]]
    }
 
    Level.place_tile(translation,map,2,2)
    for i=1,11 + math.random(4) do
        Level.scatter_put( area.new(5,3,68,15), translation, table.random_pick(column), "floor", 1)
    end
    Level.scatter(area.FULL_SHRINKED, "floor", "blood", 100)
    Level.player(38, 10)
end
Personal tools