| 1 | #!/usr/bin/env ruby |
|---|
| 2 | # |
|---|
| 3 | # FaerieMUD - The Hidden Path - Epic 4: Action |
|---|
| 4 | # |
|---|
| 5 | # == Authors |
|---|
| 6 | # |
|---|
| 7 | # * Michael Granger <ged@FaerieMUD.org> |
|---|
| 8 | # * Martin Chase <stillflame@FaerieMUD.org> |
|---|
| 9 | # * Aidan Rogers <aidan@FaerieMUD.org> |
|---|
| 10 | # |
|---|
| 11 | # == Legal |
|---|
| 12 | # |
|---|
| 13 | # Copyright (c) 2000-2005 The FaerieMUD Consortium. Some rights reserved. |
|---|
| 14 | # |
|---|
| 15 | # This is free software. You may use, modify, and/or redistribute this software |
|---|
| 16 | # under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike |
|---|
| 17 | # license, version 2.0 or greater. |
|---|
| 18 | # |
|---|
| 19 | # See http://creativecommons.org/licenses/by-nc-sa/2.0/ for more information. |
|---|
| 20 | # |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | # Put some local directories in the load path |
|---|
| 24 | BEGIN { |
|---|
| 25 | $basedir = File.dirname( File.dirname(__FILE__) ) |
|---|
| 26 | |
|---|
| 27 | %w{lib redist}.each do |dir| |
|---|
| 28 | $LOAD_PATH.unshift( File.join($basedir, dir) ) |
|---|
| 29 | end |
|---|
| 30 | } |
|---|
| 31 | |
|---|
| 32 | require "#$basedir/utils" |
|---|
| 33 | require "fm" |
|---|
| 34 | require 'fm/mixins' |
|---|
| 35 | require 'fm/linguistics' |
|---|
| 36 | |
|---|
| 37 | |
|---|
| 38 | ### Clocks can 'tick' |
|---|
| 39 | class TickVerb < FaerieMUD::Verb |
|---|
| 40 | def invoke( instigator, origin=nil ) |
|---|
| 41 | origin ||= instigator |
|---|
| 42 | return FaerieMUD::AuditoryEvent.new( |
|---|
| 43 | :instigator => instigator, |
|---|
| 44 | :origin => origin, |
|---|
| 45 | :verb => self, |
|---|
| 46 | :noticability => 0.05 |
|---|
| 47 | ) |
|---|
| 48 | end |
|---|
| 49 | end |
|---|
| 50 | |
|---|
| 51 | ### ...and also 'chime' |
|---|
| 52 | class ChimeVerb < FaerieMUD::Verb |
|---|
| 53 | def invoke( instigator, origin=nil ) |
|---|
| 54 | origin ||= instigator |
|---|
| 55 | return FaerieMUD::AuditoryEvent.new( |
|---|
| 56 | :instigator => instigator, |
|---|
| 57 | :origin => origin, |
|---|
| 58 | :verb => self, |
|---|
| 59 | :noticability => 0.25 |
|---|
| 60 | ) |
|---|
| 61 | end |
|---|
| 62 | end |
|---|
| 63 | |
|---|
| 64 | ### Clock is a Locus instead of an Instrument for purposes of simplification. |
|---|
| 65 | class Clock < FaerieMUD::Locus |
|---|
| 66 | |
|---|
| 67 | ### Create a new clock object. You can configure the object with the |
|---|
| 68 | ### following attributes: |
|---|
| 69 | ### [:count]:: Specify how many ticks the clock should generate before |
|---|
| 70 | ### returning on its thread of execution. |
|---|
| 71 | ### [:interval]:: Specify how many floating-point seconds to sleep between |
|---|
| 72 | ### ticks. An +interval+ of +0+ causes the clock to tick as |
|---|
| 73 | ### fast as it can. |
|---|
| 74 | def initialize( args={} ) |
|---|
| 75 | @thread = nil |
|---|
| 76 | @tickCount = 0 |
|---|
| 77 | @verbs = { |
|---|
| 78 | :tick => TickVerb.instance, |
|---|
| 79 | :chime => ChimeVerb.instance, |
|---|
| 80 | } |
|---|
| 81 | @count = 120 |
|---|
| 82 | @interval = 0.5 |
|---|
| 83 | |
|---|
| 84 | super |
|---|
| 85 | end |
|---|
| 86 | |
|---|
| 87 | attr_accessor :count, :interval |
|---|
| 88 | |
|---|
| 89 | ### Start a new thread, start ticking in it and return the thread. |
|---|
| 90 | def start |
|---|
| 91 | @thread = Thread.new do |
|---|
| 92 | Thread.current.abort_on_exception = true |
|---|
| 93 | @count.times do |
|---|
| 94 | self.tick |
|---|
| 95 | sleep @interval unless @interval == 0.0 |
|---|
| 96 | end |
|---|
| 97 | end |
|---|
| 98 | end |
|---|
| 99 | |
|---|
| 100 | ### Execute one tick of the clock, sending appropriate events to |
|---|
| 101 | ### the environment. |
|---|
| 102 | def tick |
|---|
| 103 | @tickCount += 1 |
|---|
| 104 | self.log.debug "Tick %d" % [@tickCount] |
|---|
| 105 | events = @verbs[:tick].invoke( self ) |
|---|
| 106 | self.disperse_events( events ) |
|---|
| 107 | self.chime if (@tickCount % 10).zero? |
|---|
| 108 | end |
|---|
| 109 | |
|---|
| 110 | |
|---|
| 111 | ### Send an auditory chime event to the surrounding environment. |
|---|
| 112 | def chime |
|---|
| 113 | self.log.debug "Chime!" |
|---|
| 114 | events = @verbs[:chime].invoke( self ) |
|---|
| 115 | self.disperse_events( events ) |
|---|
| 116 | end |
|---|
| 117 | end |
|---|
| 118 | |
|---|
| 119 | |
|---|
| 120 | ### The quasi-orc can 'say' stuff. |
|---|
| 121 | class SayVerb < FaerieMUD::Verb |
|---|
| 122 | |
|---|
| 123 | # The speaker will always be both instigator and origin, so combine them in |
|---|
| 124 | # the overridden method. Ventriloquism doesn't count. =:P |
|---|
| 125 | |
|---|
| 126 | def invoke( speaker, language, words ) |
|---|
| 127 | return FaerieMUD::SpeechEvent.new( |
|---|
| 128 | :instigator => speaker, |
|---|
| 129 | :origin => speaker, |
|---|
| 130 | :verb => self, |
|---|
| 131 | :language => language, |
|---|
| 132 | :words => words, |
|---|
| 133 | :noticability => 0.65 |
|---|
| 134 | ) |
|---|
| 135 | end |
|---|
| 136 | end |
|---|
| 137 | |
|---|
| 138 | ### A simulacrum quasi-orc object. |
|---|
| 139 | class QuasiOrc < FaerieMUD::Locus |
|---|
| 140 | |
|---|
| 141 | ### Create a new simulated orc. He will assume he's on duty. |
|---|
| 142 | def initialize |
|---|
| 143 | @onDuty = true |
|---|
| 144 | @chimeCount = 0 |
|---|
| 145 | @sayVerb = SayVerb.instance |
|---|
| 146 | |
|---|
| 147 | super |
|---|
| 148 | end |
|---|
| 149 | |
|---|
| 150 | ### Handle stuff heard from the environment. QuasiOrcs only care about chimes that tell |
|---|
| 151 | ### them they're on duty or off. |
|---|
| 152 | def handleAuditoryEvent( ev ) |
|---|
| 153 | if ev.verb.class.name =~ /chime/i |
|---|
| 154 | self.log.debug "Heard a chime!" |
|---|
| 155 | results = self.respondToChime |
|---|
| 156 | self.log.debug "Result from responding to chime: %p" % |
|---|
| 157 | [ results ] |
|---|
| 158 | self.disperse_events( results ) if results |
|---|
| 159 | else |
|---|
| 160 | self.log.debug "Ignoring %s" % [ ev.class.name ] |
|---|
| 161 | # Ignore |
|---|
| 162 | end |
|---|
| 163 | end |
|---|
| 164 | |
|---|
| 165 | |
|---|
| 166 | ### Respond to hearing a chime -- he is on duty every three hours. |
|---|
| 167 | def respondToChime |
|---|
| 168 | @chimeCount += 1 |
|---|
| 169 | self.changeDutyStatus if ( @chimeCount % 3 ).zero? |
|---|
| 170 | end |
|---|
| 171 | |
|---|
| 172 | |
|---|
| 173 | ### After hearing a third chime, either go on duty or come off, and announce it. |
|---|
| 174 | def changeDutyStatus |
|---|
| 175 | @onDuty = !@onDuty |
|---|
| 176 | |
|---|
| 177 | if @onDuty |
|---|
| 178 | msg = "Back to work!" |
|---|
| 179 | else |
|---|
| 180 | msg = "Ah, off duty now." |
|---|
| 181 | end |
|---|
| 182 | |
|---|
| 183 | return @sayVerb.invoke( self, :trade, msg ) |
|---|
| 184 | end |
|---|
| 185 | end |
|---|
| 186 | |
|---|
| 187 | |
|---|
| 188 | ### A bodiless observer object class -- just reports events observed from its |
|---|
| 189 | ### environment to $defout. |
|---|
| 190 | class Observer < FaerieMUD::Locus |
|---|
| 191 | |
|---|
| 192 | include FaerieMUD::Linguistics |
|---|
| 193 | |
|---|
| 194 | ### Handle events that are sensed through auditory perception |
|---|
| 195 | def handleAuditoryEvent( ev ) |
|---|
| 196 | $defout.puts "Observer heard: %s" % describe_event( ev ) |
|---|
| 197 | end |
|---|
| 198 | |
|---|
| 199 | ### Handle auditory events that contain speech specially. |
|---|
| 200 | def handleSpeechEvent( ev ) |
|---|
| 201 | prelude = "Observer heard: %s" % describe_event( ev ) |
|---|
| 202 | $defout.puts %q{%s, "%s"} % [ prelude, ev.words ] |
|---|
| 203 | end |
|---|
| 204 | |
|---|
| 205 | ### Handle events sensed through visual perception. |
|---|
| 206 | def handleVisualEvent( ev ) |
|---|
| 207 | $defout.puts "Observer saw: %s" % describe_event( ev ) |
|---|
| 208 | end |
|---|
| 209 | |
|---|
| 210 | ### Create a sentence to describe an event. |
|---|
| 211 | def describe_event( ev ) |
|---|
| 212 | $deferr.puts ev.inspect |
|---|
| 213 | |
|---|
| 214 | prep_phrase = "in %s" % objectToNounPhrase( ev.origin ) if ev.origin != ev.instigator |
|---|
| 215 | sentence = Sentence.new( ev.instigator, ev.verb, ev.target, prep_phrase ) |
|---|
| 216 | |
|---|
| 217 | return sentence.to_s |
|---|
| 218 | end |
|---|
| 219 | |
|---|
| 220 | end |
|---|
| 221 | |
|---|
| 222 | |
|---|
| 223 | if $0 == __FILE__ |
|---|
| 224 | if $DEBUG |
|---|
| 225 | FaerieMUD::Logger.global.outputters << |
|---|
| 226 | FaerieMUD::Logger::Outputter.create( 'file', $stderr, "STDERR" ) |
|---|
| 227 | FaerieMUD::Logger.global.level = :debug |
|---|
| 228 | end |
|---|
| 229 | |
|---|
| 230 | # Set up the scene |
|---|
| 231 | topArea = FaerieMUD::Area.new |
|---|
| 232 | punchbag = QuasiOrc.new |
|---|
| 233 | clock = Clock.new |
|---|
| 234 | observer = Observer.new |
|---|
| 235 | |
|---|
| 236 | # Put the clock and punchbag into the void |
|---|
| 237 | topArea << punchbag |
|---|
| 238 | topArea << clock |
|---|
| 239 | topArea << observer |
|---|
| 240 | |
|---|
| 241 | # Start the clock ticking |
|---|
| 242 | clock.interval = 0.1 |
|---|
| 243 | clock.start.join |
|---|
| 244 | end |
|---|
| 245 | |
|---|
| 246 | |
|---|