go: port dungeon generation, items base, pack, and monster creation
rooms.c, passages.c, new_level.c ported in full: room/maze layout, corridor spanning tree with extra connections, passage numbering flood fill, trap/stairs/object placement, treasure rooms. Careful RNG-call ordering throughout keeps generation seed-faithful to C. Supporting subsystems this depends on, also ported: - screen.go: curses replaced by in-memory Window cell buffers behind a Terminal interface (tcell arrives with the UI phase; tests run headless) - io.go: msg/addmsg/endmsg message-line protocol, status line, step_ok - pack.c in full (add_pack sorting/stacking walk, get_item, inventory) - things.c in full (inv_name, new_thing, discovery lists, pagination) - monsters.c in full; chase.c navigation half (roomin/cansee/see_monst/ find_dest/runto/set_oldch); rings.c, armor.c in full - weapons.c/sticks.c creation half (init_weapon, fix_stick, num) - misc.c look() display update, eat, level-up, direction input - daemons.c callbacks except stomach (needs death()) and the runners chase driver (combat phase) - init.c init_player with the starting kit Tests: level invariants across seeds, determinism, 30-depth sweep.
This commit is contained in:
200
game/daemons.go
200
game/daemons.go
@@ -1,17 +1,205 @@
|
||||
package game
|
||||
|
||||
// daemons.c — the daemon and fuse callbacks. Each callback is ported in the
|
||||
// phase that owns its subsystem; runDaemon is the dispatch switch that
|
||||
// replaces the C function pointers.
|
||||
// daemons.c — the daemon and fuse callbacks, dispatched by DaemonID.
|
||||
// stomach() arrives with the endgame phase (starvation calls death()).
|
||||
|
||||
// runDaemon invokes the callback named by id (the call through d_func in C).
|
||||
func (g *RogueGame) runDaemon(id DaemonID, arg int) {
|
||||
_ = arg
|
||||
switch id {
|
||||
case DRollwand:
|
||||
g.rollwand(arg)
|
||||
case DDoctor:
|
||||
g.doctor(arg)
|
||||
case DSwander:
|
||||
g.swander(arg)
|
||||
case DNohaste:
|
||||
g.nohaste(arg)
|
||||
case DUnconfuse:
|
||||
g.unconfuse(arg)
|
||||
case DUnsee:
|
||||
g.unsee(arg)
|
||||
case DSight:
|
||||
g.sight(arg)
|
||||
case DVisuals:
|
||||
g.visuals(arg)
|
||||
case DComeDown:
|
||||
g.comeDown(arg)
|
||||
case DLand:
|
||||
g.land(arg)
|
||||
default:
|
||||
// Callbacks are added to this switch as their subsystems are
|
||||
// ported; reaching one that isn't here is a porting bug, not a
|
||||
// game state.
|
||||
// ported; reaching one that isn't here is a porting bug.
|
||||
panic("daemon not yet ported")
|
||||
}
|
||||
}
|
||||
|
||||
// doctor is the healing daemon that restores hit points after rest
|
||||
// (daemons.c doctor).
|
||||
func (g *RogueGame) doctor(int) {
|
||||
p := &g.Player
|
||||
lv := p.Stats.Lvl
|
||||
ohp := p.Stats.HP
|
||||
g.Quiet++
|
||||
if lv < 8 {
|
||||
if g.Quiet+(lv<<1) > 20 {
|
||||
p.Stats.HP++
|
||||
}
|
||||
} else if g.Quiet >= 3 {
|
||||
p.Stats.HP += g.rnd(lv-7) + 1
|
||||
}
|
||||
if p.IsRing(Left, RRegen) {
|
||||
p.Stats.HP++
|
||||
}
|
||||
if p.IsRing(Right, RRegen) {
|
||||
p.Stats.HP++
|
||||
}
|
||||
if ohp != p.Stats.HP {
|
||||
if p.Stats.HP > p.Stats.MaxHP {
|
||||
p.Stats.HP = p.Stats.MaxHP
|
||||
}
|
||||
g.Quiet = 0
|
||||
}
|
||||
}
|
||||
|
||||
// swander is called when it is time to start rolling for wandering monsters
|
||||
// (daemons.c swander).
|
||||
func (g *RogueGame) swander(int) {
|
||||
g.StartDaemon(DRollwand, 0, Before)
|
||||
}
|
||||
|
||||
// rollwand rolls to see if a wandering monster starts up (daemons.c
|
||||
// rollwand).
|
||||
func (g *RogueGame) rollwand(int) {
|
||||
if g.Daemons.Between++; g.Daemons.Between >= 4 {
|
||||
if g.roll(1, 6) == 4 {
|
||||
g.wanderer()
|
||||
g.KillDaemon(DRollwand)
|
||||
g.Fuse(DSwander, 0, wanderTime(g), Before)
|
||||
}
|
||||
g.Daemons.Between = 0
|
||||
}
|
||||
}
|
||||
|
||||
// wanderTime is the WANDERTIME macro: spread(70).
|
||||
func wanderTime(g *RogueGame) int { return g.spread(70) }
|
||||
|
||||
// unconfuse releases the poor player from his confusion (daemons.c
|
||||
// unconfuse).
|
||||
func (g *RogueGame) unconfuse(int) {
|
||||
g.Player.Flags.Clear(IsHuh)
|
||||
g.msg("you feel less %s now", g.chooseStr("trippy", "confused"))
|
||||
}
|
||||
|
||||
// unsee turns off the ability to see invisible (daemons.c unsee).
|
||||
func (g *RogueGame) unsee(int) {
|
||||
for _, th := range g.Level.Monsters {
|
||||
if th.On(IsInvis) && g.seeMonst(th) {
|
||||
g.mvaddch(th.Pos.Y, th.Pos.X, th.OldCh)
|
||||
}
|
||||
}
|
||||
g.Player.Flags.Clear(CanSee)
|
||||
}
|
||||
|
||||
// sight gives the hero his sight back (daemons.c sight).
|
||||
func (g *RogueGame) sight(int) {
|
||||
p := &g.Player
|
||||
if p.On(IsBlind) {
|
||||
g.Extinguish(DSight)
|
||||
p.Flags.Clear(IsBlind)
|
||||
if !p.Room.Flags.Has(IsGone) {
|
||||
g.enterRoom(p.Pos)
|
||||
}
|
||||
g.msg("%s", g.chooseStr("far out! Everything is all cosmic again",
|
||||
"the veil of darkness lifts"))
|
||||
}
|
||||
}
|
||||
|
||||
// nohaste ends the hasting (daemons.c nohaste).
|
||||
func (g *RogueGame) nohaste(int) {
|
||||
g.Player.Flags.Clear(IsHaste)
|
||||
g.msg("you feel yourself slowing down")
|
||||
}
|
||||
|
||||
// comeDown takes the hero down off her acid trip (daemons.c come_down).
|
||||
func (g *RogueGame) comeDown(int) {
|
||||
p := &g.Player
|
||||
if !p.On(IsHalu) {
|
||||
return
|
||||
}
|
||||
|
||||
g.KillDaemon(DVisuals)
|
||||
p.Flags.Clear(IsHalu)
|
||||
|
||||
if p.On(IsBlind) {
|
||||
return
|
||||
}
|
||||
|
||||
// undo the things
|
||||
for _, tp := range g.Level.Objects {
|
||||
if g.cansee(tp.Pos.Y, tp.Pos.X) {
|
||||
g.mvaddch(tp.Pos.Y, tp.Pos.X, tp.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// undo the monsters
|
||||
seemonst := p.On(SeeMonst)
|
||||
for _, tp := range g.Level.Monsters {
|
||||
g.move(tp.Pos.Y, tp.Pos.X)
|
||||
if g.cansee(tp.Pos.Y, tp.Pos.X) {
|
||||
if !tp.On(IsInvis) || p.On(CanSee) {
|
||||
g.addch(tp.Disguise)
|
||||
} else {
|
||||
g.addch(g.Level.Char(tp.Pos.Y, tp.Pos.X))
|
||||
}
|
||||
} else if seemonst {
|
||||
g.standout()
|
||||
g.addch(tp.Type)
|
||||
g.standend()
|
||||
}
|
||||
}
|
||||
g.msg("Everything looks SO boring now.")
|
||||
}
|
||||
|
||||
// visuals changes the characters for the player while hallucinating
|
||||
// (daemons.c visuals).
|
||||
func (g *RogueGame) visuals(int) {
|
||||
p := &g.Player
|
||||
if !g.After || (g.Running && g.Options.Jump) {
|
||||
return
|
||||
}
|
||||
// change the things
|
||||
for _, tp := range g.Level.Objects {
|
||||
if g.cansee(tp.Pos.Y, tp.Pos.X) {
|
||||
g.mvaddch(tp.Pos.Y, tp.Pos.X, g.rndThing())
|
||||
}
|
||||
}
|
||||
|
||||
// change the stairs
|
||||
if !g.SeenStairs && g.cansee(g.Level.Stairs.Y, g.Level.Stairs.X) {
|
||||
g.mvaddch(g.Level.Stairs.Y, g.Level.Stairs.X, g.rndThing())
|
||||
}
|
||||
|
||||
// change the monsters
|
||||
seemonst := p.On(SeeMonst)
|
||||
for _, tp := range g.Level.Monsters {
|
||||
g.move(tp.Pos.Y, tp.Pos.X)
|
||||
if g.seeMonst(tp) {
|
||||
if tp.Type == 'X' && tp.Disguise != 'X' {
|
||||
g.addch(g.rndThing())
|
||||
} else {
|
||||
g.addch(byte(g.rnd(26) + 'A'))
|
||||
}
|
||||
} else if seemonst {
|
||||
g.standout()
|
||||
g.addch(byte(g.rnd(26) + 'A'))
|
||||
g.standend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// land lands the hero from a levitation potion (daemons.c land).
|
||||
func (g *RogueGame) land(int) {
|
||||
g.Player.Flags.Clear(IsLevit)
|
||||
g.msg("%s", g.chooseStr("bummer! You've hit the ground",
|
||||
"you float gently to the ground"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user