doodle/pkg/scripting/scripting.go
Noah Petherbridge 1a8a5eb94b Polish and bugfixes
- Fix a memory sharing bug in the Giant Screenshot feature.
- Main Menu to eagerload chunks in the background to make scrolling less
  jittery. No time for a loadscreen!
- Extra script debugging: names/IDs of doodads are shown when they send
  messages to one another.
- Level Properties: you can edit the Bounded max width/height values for
  the level.

Doodad changes:

- Buttons: fix a timing bug and keep better track of who is stepping on it,
  only popping up when all colliders have left. The effect: they pop up
  immediately (not after 200ms) and are more reliable.
- Keys: zero-qty keys will no longer put themselves into the inventory of
  characters who already have one except for the player character. So
  the Thief will not steal them if she already has the key.

Added to the JavaScript API:

* time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond
2021-10-09 20:45:38 -07:00

112 lines
2.8 KiB
Go

// Package scripting manages the JavaScript VMs for Doodad scripts.
package scripting
import (
"errors"
"fmt"
"time"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/go/render"
)
// Supervisor manages the JavaScript VMs for each doodad by its
// unique ID.
type Supervisor struct {
scripts map[string]*VM
// Global event handlers.
onLevelExit func()
onLevelFail func(message string)
onSetCheckpoint func(where render.Point)
}
// NewSupervisor creates a new JavaScript Supervior.
func NewSupervisor() *Supervisor {
return &Supervisor{
scripts: map[string]*VM{},
}
}
// Loop the supervisor to invoke timer events in any running scripts.
func (s *Supervisor) Loop() error {
now := time.Now()
for _, vm := range s.scripts {
vm.TickTimer(now)
}
return nil
}
// InstallScripts loads scripts for all actors in the level.
func (s *Supervisor) InstallScripts(level *level.Level) error {
for _, actor := range level.Actors {
if err := s.AddLevelScript(actor.ID(), actor.Filename); err != nil {
return err
}
}
// Loop again to bridge channels together for linked VMs.
for _, actor := range level.Actors {
// Add linked actor IDs.
if len(actor.Links) > 0 {
// Bridge the links up.
var thisVM = s.scripts[actor.ID()]
for _, id := range actor.Links {
// Assign this target actor's Inbound channel to the source
// actor's array of Outbound channels.
if _, ok := s.scripts[id]; !ok {
log.Error("scripting.InstallScripts: actor %s is linked to %s but %s was not found",
actor.ID(),
id,
id,
)
continue
}
thisVM.Outbound = append(thisVM.Outbound, s.scripts[id].Inbound)
}
}
}
return nil
}
// AddLevelScript adds a script to the supervisor with level hooks.
// The `id` will key the VM and should be the Actor ID in the level.
// The `name` is used to name the VM for debug logging.
func (s *Supervisor) AddLevelScript(id string, name string) error {
if _, ok := s.scripts[id]; ok {
return fmt.Errorf("duplicate actor ID %s in level", id)
}
s.scripts[id] = NewVM(fmt.Sprintf("%s#%s", name, id))
RegisterPublishHooks(s, s.scripts[id])
RegisterEventHooks(s, s.scripts[id])
if err := s.scripts[id].RegisterLevelHooks(); err != nil {
return err
}
return nil
}
// To returns the VM for a named script.
func (s *Supervisor) To(name string) *VM {
if vm, ok := s.scripts[name]; ok {
return vm
}
// TODO: put this log back in, but add PLAYER script so it doesn't spam
// the console for missing PLAYER.
log.Error("scripting.Supervisor.To(%s): no such VM but returning blank VM",
name,
)
return NewVM(name)
}
// GetVM returns a script VM from the supervisor.
func (s *Supervisor) GetVM(name string) (*VM, error) {
if vm, ok := s.scripts[name]; ok {
return vm, nil
}
return nil, errors.New("not found")
}