Skip to content
Snippets Groups Projects
Commit f332a9d1 authored by dg's avatar dg
Browse files

New engine.NameGenerator class

New random quest: sometimes you will meet a lost adventurer (of a random class), you can help him by escorting him to a portal, but beware he dies easily. He might reward you for you kindness.


git-svn-id: http://svn.net-core.org/repos/t-engine4@1064 51575b47-30f0-44d4-a5cc-537603b46e54
parent 84e15b05
No related branches found
No related tags found
No related merge requests found
......@@ -169,6 +169,13 @@ function _M:canMove(x, y, terrain_only)
end
end
--- Remove the actor from the level, marking it as dead but not using the death functions
function _M:disappear(src)
if game.level:hasEntity(self) then game.level:removeEntity(self) end
self.dead = true
self.changed = true
end
--- Get the "path string" for this actor
-- See Map:addPathString() for more info
function _M:getPathString()
......
-- TE4 - T-Engine 4
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local lpeg = require "lpeg"
module(..., package.seeall, class.make)
local number = lpeg.R"09" ^ 0
local rule_pattern = "$" * lpeg.C(number) * lpeg.C(lpeg.S"smePpvc?")
--- Creates a random name generator with a set of rules
-- The lang_def parameter is a table that must look like this:<br/>
-- {<br/>
-- phonemesVocals = "a, e, i, o, u, y",<br/>
-- phonemesConsonants = "b, c, ch, ck, cz, d, dh, f, g, gh, h, j, k, kh, l, m, n, p, ph, q, r, rh, s, sh, t, th, ts, tz, v, w, x, z, zh",<br/>
-- syllablesStart = "Aer, Al, Am, An, Ar, Arm, Arth, B, Bal, Bar, Be, Bel, Ber, Bok, Bor, Bran, Breg, Bren, Brod, Cam, Chal, Cham, Ch, Cuth, Dag, Daim, Dair, Del, Dr, Dur, Duv, Ear, Elen, Er, Erel, Erem, Fal, Ful, Gal, G, Get, Gil, Gor, Grin, Gun, H, Hal, Han, Har, Hath, Hett, Hur, Iss, Khel, K, Kor, Lel, Lor, M, Mal, Man, Mard, N, Ol, Radh, Rag, Relg, Rh, Run, Sam, Tarr, T, Tor, Tul, Tur, Ul, Ulf, Unr, Ur, Urth, Yar, Z, Zan, Zer",<br/>
-- syllablesMiddle = "de, do, dra, du, duna, ga, go, hara, kaltho, la, latha, le, ma, nari, ra, re, rego, ro, rodda, romi, rui, sa, to, ya, zila",<br/>
-- syllablesEnd = "bar, bers, blek, chak, chik, dan, dar, das, dig, dil, din, dir, dor, dur, fang, fast, gar, gas, gen, gorn, grim, gund, had, hek, hell, hir, hor, kan, kath, khad, kor, lach, lar, ldil, ldir, leg, len, lin, mas, mnir, ndil, ndur, neg, nik, ntir, rab, rach, rain, rak, ran, rand, rath, rek, rig, rim, rin, rion, sin, sta, stir, sus, tar, thad, thel, tir, von, vor, yon, zor",<br/>
-- rules = "$s$v$35m$10m$e",<br/>
-- }<br/>
-- The rules field defines how names are generated. Any special character found inside (starting with a $, with an optional number and a character) will be
-- replaced by a random word from one of the lists.<br/>
-- $P = syllablesPre<br/>
-- $s = syllablesStart<br/>
-- $m = syllablesMiddle<br/>
-- $e = syllablesEnd<br/>
-- $p = syllablesPost<br/>
-- $v = phonemesVocals<br/>
-- $c = phonemesConsonants<br/>
-- If a number is give it is a percent chance for this part to exist
function _M:init(lang_def)
local def = {}
if lang_def.phonemesVocals then def.v = lang_def.phonemesVocals:split(", ") end
if lang_def.phonemesConsonants then def.c = lang_def.phonemesConsonants:split(", ") end
if lang_def.syllablesPre then def.P = lang_def.syllablesPre:split(", ") end
if lang_def.syllablesStart then def.s = lang_def.syllablesStart:split(", ") end
if lang_def.syllablesMiddle then def.m = lang_def.syllablesMiddle:split(", ") end
if lang_def.syllablesEnd then def.e = lang_def.syllablesEnd:split(", ") end
if lang_def.syllablesPost then def.p = lang_def.syllablesPost:split(", ") end
if type(lang_def.rules) == "string" then def.rules = {{100, lang_def.rules}} else def = lang_def.rules end
self.lang_def = def
end
--- Generates a name
-- @param if not nil this is a generation rule to use instead of a random one
function _M:generate(rule)
while not rule do
rule = rng.table(self.lang_def.rules)
if rng.percent(rule[1]) then rule = rule[2] end
end
-- Generate the name, using lpeg pattern matching. Lpeg is nice. Love lpeg.
local name = rule:lpegSub(rule_pattern, function(chance, type)
if not chance or chance == "" or rng.percent(chance) then
return rng.table(self.lang_def[type])
else
return ""
end
end)
-- Check for double spaces
name = name:lpegSub(" ", " ")
-- Prunes repetitions
-- Those do tail calls in case of bad names
for i = 1, #name do
if name:sub(i, i+1) == name:sub(i+2, i+3) then return self:generate() end
if name:sub(i, i+2) == name:sub(i+3, i+4) then return self:generate() end
end
return name
end
-- ToME - Tales of Middle-Earth
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
local Astar = require "engine.Astar"
-- AI for the escort quest
-- the NPC will run toward the portal, if hostiles are in sight he attacks them
newAI("escort_quest", function(self)
if self:runAI("target_simple") then
-- One in "talent_in" chance of using a talent
if rng.chance(self.ai_state.talent_in or 6) and self:reactionToward(self.ai_target.actor) < 0 then
self:runAI("dumb_talented")
end
if not self.energy.used then
if self:reactionToward(self.ai_target.actor) < 0 then
self:runAI("move_dmap")
else
self:runAI("move_escort")
end
end
return true
end
end)
newAI("move_escort", function(self)
if self.escort_target then
-- Randomly stop to give time to the player
if rng.percent(35) then self:useEnergy() return true end
local tx, ty = self.escort_target.x, self.escort_target.y
local a = Astar.new(game.level.map, self)
local path = a:calc(self.x, self.y, tx, ty)
if not path then
return self:runAI("move_simple")
else
return self:move(path[1].x, path[1].y)
end
end
end)
......@@ -487,6 +487,17 @@ function _M:setupCommands()
-- self.player:grantQuest("escort-duty")
end
end,
[{"_z","ctrl"}] = function()
if config.settings.tome.cheat then
self.player:forceLevelup(50)
self.player.no_breath = 1
self.player.invulnerable = 1
self.player.esp.all = 1
self.player.esp.range = 50
self.player.inc_damage.all = 100000
self.player:grantQuest("escort-duty")
end
end,
[{"_f","ctrl"}] = function()
if config.settings.tome.cheat then
self.player:incStat("str", 100) self.player:incStat("dex", 100) self.player:incStat("mag", 100) self.player:incStat("wil", 100) self.player:incStat("cun", 100) self.player:incStat("con", 100)
......
......@@ -33,7 +33,7 @@ newBirthDescriptor{
subrace =
{
__ALL__ = "disallow",
Orc = "allow",
["Uruk-hai"] = "allow",
},
sex =
{
......@@ -67,7 +67,7 @@ newBirthDescriptor{
newBirthDescriptor
{
type = "subrace",
name = "Orc",
name = "Uruk-hai",
desc = {
"",
},
......
......@@ -31,7 +31,7 @@ newBirthDescriptor{
subrace =
{
__ALL__ = "disallow",
Troll = "allow",
["Olog-hai"] = "allow",
},
sex =
{
......@@ -61,7 +61,7 @@ newBirthDescriptor{
newBirthDescriptor
{
type = "subrace",
name = "Troll",
name = "Olog-hai",
desc = {
"",
},
......
-- ToME - Tales of Middle-Earth
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
newChat{ id="welcome",
text = [[Todo!]],
answers = {
{"[leave]"},
}
}
return "welcome"
......@@ -17,6 +17,67 @@
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
local Talents = require("engine.interface.ActorTalents")
local Stats = require("engine.interface.ActorStats")
local NameGenerator = require("engine.NameGenerator")
local Astar = require("engine.Astar")
--------------------------------------------------------------------------------
-- Quest data
--------------------------------------------------------------------------------
local name_rules = {
phonemesVocals = "a, e, i, o, u, y",
phonemesConsonants = "b, c, ch, ck, cz, d, dh, f, g, gh, h, j, k, kh, l, m, n, p, ph, q, r, rh, s, sh, t, th, ts, tz, v, w, x, z, zh",
syllablesStart = "Aer, Al, Am, An, Ar, Arm, Arth, B, Bal, Bar, Be, Bel, Ber, Bok, Bor, Bran, Breg, Bren, Brod, Cam, Chal, Cham, Ch, Cuth, Dag, Daim, Dair, Del, Dr, Dur, Duv, Ear, Elen, Er, Erel, Erem, Fal, Ful, Gal, G, Get, Gil, Gor, Grin, Gun, H, Hal, Han, Har, Hath, Hett, Hur, Iss, Khel, K, Kor, Lel, Lor, M, Mal, Man, Mard, N, Ol, Radh, Rag, Relg, Rh, Run, Sam, Tarr, T, Tor, Tul, Tur, Ul, Ulf, Unr, Ur, Urth, Yar, Z, Zan, Zer",
syllablesMiddle = "de, do, dra, du, duna, ga, go, hara, kaltho, la, latha, le, ma, nari, ra, re, rego, ro, rodda, romi, rui, sa, to, ya, zila",
syllablesEnd = "bar, bers, blek, chak, chik, dan, dar, das, dig, dil, din, dir, dor, dur, fang, fast, gar, gas, gen, gorn, grim, gund, had, hek, hell, hir, hor, kan, kath, khad, kor, lach, lar, ldil, ldir, leg, len, lin, mas, mnir, ndil, ndur, neg, nik, ntir, rab, rach, rain, rak, ran, rand, rath, rek, rig, rim, rin, rion, sin, sta, stir, sus, tar, thad, thel, tir, von, vor, yon, zor",
rules = "$s$v$35m$10m$e",
}
local possible_types = {
{ name="lost warrior",
types = {
["technique/combat-training"] = 0.7,
["technique/combat-techniques-active"] = 0.7,
["technique/combat-techniques-passive"] = 0.7,
},
talents = {
[Talents.T_RUSH] = 1,
},
stats = {
[Stats.STAT_STR] = 2,
[Stats.STAT_DEX] = 1,
[Stats.STAT_CON] = 2,
},
actor = {
type = "humanoid", subtype = "human",
display = "@", color=colors.UMBER,
name = "%s, the lost warrior",
desc = [[He looks tired and wounded.]],
autolevel = "warrior",
ai = "summoned", ai_real = "escort_quest", ai_state = { talent_in=4, },
stats = { str=18, dex=13, mag=5, con=15 },
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
resolvers.equip{ {type="weapon", subtype="greatsword", autoreq=true} },
resolvers.talents{ [Talents.T_STUNNING_BLOW]=1, },
lite = 4,
rank = 2,
exp_worth = 0,
max_life = 50, life_regen = 0,
life_rating = 5,
combat_armor = 3, combat_def = 3,
inc_damage = {all=-50},
},
},
}
--------------------------------------------------------------------------------
-- Quest code
--------------------------------------------------------------------------------
-- Random escort
id = "escort-duty-"..game.zone.short_name.."-"..game.level.level
......@@ -29,66 +90,79 @@ desc = function(self, who)
return table.concat(desc, "\n")
end
local function getPortalSpot(npc, dist, min_dist)
local astar = Astar.new(game.level.map, npc)
local poss = {}
dist = math.floor(dist)
min_dist = math.floor(min_dist or 0)
for i = npc.x - dist, npc.x + dist do
for j = npc.y - dist, npc.y + dist do
if game.level.map:isBound(i, j) and
core.fov.distance(npc.x, npc.y, i, j) <= dist and
core.fov.distance(npc.x, npc.y, i, j) >= min_dist and
npc:canMove(i, j) then
poss[#poss+1] = {i,j}
end
end
end
while #poss > 0 do
local pos = rng.tableRemove(poss)
if astar:calc(npc.x, npc.y, pos[1], pos[2]) then
print("Placing portal at ", pos[1], pos[2])
return pos[1], pos[2]
end
end
end
on_grant = function(self, who)
self.on_grant = nil
local types = {
{ name="lost warrior",
types = {
["technique/combat-training"] = 0.7,
["technique/combat-techniques-active"] = 0.7,
["technique/combat-techniques-passive"] = 0.7,
},
talents =
{
[who.T_RUSH] = 1,
},
stats =
{
[who.STAT_STR] = 2,
[who.STAT_DEX] = 1,
[who.STAT_CON] = 2,
},
actor = {
type = "humanoid", subtype = "human",
display = "@", color=colors.UMBER,
name = "lost warrior", faction = who.faction,
desc = [[He looks tired and wounded.]],
autolevel = "warrior",
ai = "summoned", ai_real = "dumb_talented_simple", ai_state = { talent_in=3, },
stats = { str=18, dex=13, mag=5, con=15 },
body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1, QUIVER=1 },
resolvers.equip{ {type="weapon", subtype="greatsword", autoreq=true} },
resolvers.talents{ [who.T_STUNNING_BLOW]=1, },
lite = 4,
rank = 2,
level_range = {game.level.level, game.level.level}, exp_worth = 0,
max_life = 50,
life_rating = 5,
combat_armor = 3, combat_def = 3,
summoner = who,
quest_id = self.id,
on_die = function(self, who)
game.logPlayer(game.player, "#LIGHT_RED#The %s is dead, quest failed!", self.name)
game.player:setQuestStatus(self.quest_id, engine.Quest.FAILED)
end,
},
},
}
self.kind = rng.table(types)
local ng = NameGenerator.new(name_rules)
self.kind = rng.table(possible_types)
self.kind.actor.level_range = {game.level.level, game.level.level}
self.kind.actor.name = self.kind.actor.name:format(ng:generate())
self.kind.actor.faction = who.faction
self.kind.actor.summoner = who
self.kind.actor.quest_id = self.id
self.kind.actor.escort_quest = true
self.kind.actor.on_die = function(self, who)
game.logPlayer(game.player, "#LIGHT_RED#%s is dead, quest failed!", self.name:capitalize())
game.player:setQuestStatus(self.quest_id, engine.Quest.FAILED)
end
-- Spawn actor
local x, y = util.findFreeGrid(who.x, who.y, 10, true, {[engine.Map.ACTOR]=true})
if not x then return end
local npc = mod.class.NPC.new(self.kind.actor)
npc:resolve()
npc:resolve(nil, true)
npc:resolve() npc:resolve(nil, true)
game.zone:addEntity(game.level, npc, "actor", x, y)
self.kind.actor = nil
-- Spawn the portal, far enough from the escort
local gx, gy = getPortalSpot(npc, 150, (game.level.map.w + game.level.map.h) / 2 / 2)
if not gx then return end
local g = mod.class.Grid.new{
show_tooltip=true,
name="Recall Portal: "..npc.name,
display='&', color=colors.VIOLET,
notice = true,
on_move = function(self, x, y, who)
if not who.escort_quest then return end
game.player:setQuestStatus(who.quest_id, engine.Quest.DONE)
npc:disappear()
local Chat = require "engine.Chat"
Chat.new("escort-quest", who, game.player):invoke()
end,
}
g:resolve() g:resolve(nil, true)
game.zone:addEntity(game.level, g, "terrain", gx, gy)
npc.escort_target = {x=gx, y=gy}
-- Setup quest
self.name = "Escort: "..self.kind.name
self.level_name = game.level.level.." of "..game.zone.name
self.name = "Escort: "..self.kind.name.." (level "..self.level_name..")"
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment