| 1 | #!/usr/bin/ruby |
|---|
| 2 | # |
|---|
| 3 | # This file contains the FaerieMUD::Spirit class. Instances of this class are |
|---|
| 4 | # the permanent record of a player's accomplishments in the world, and also the |
|---|
| 5 | # means by which she controls characters or other AnimatedObjects in the world. |
|---|
| 6 | # |
|---|
| 7 | # == Subversion ID |
|---|
| 8 | # |
|---|
| 9 | # $Id$ |
|---|
| 10 | # |
|---|
| 11 | # == Authors |
|---|
| 12 | # |
|---|
| 13 | # * Michael Granger <ged@FaerieMUD.org> |
|---|
| 14 | # |
|---|
| 15 | # :include: LICENSE |
|---|
| 16 | # |
|---|
| 17 | #--- |
|---|
| 18 | # |
|---|
| 19 | # Please see the file LICENSE for licensing details. |
|---|
| 20 | # |
|---|
| 21 | |
|---|
| 22 | require 'fm/mixins' |
|---|
| 23 | require 'fm/exceptions' |
|---|
| 24 | require 'fm/commandparser' |
|---|
| 25 | require 'fm/statistic' |
|---|
| 26 | |
|---|
| 27 | ### Object class for maintaining a permanent record of a player's |
|---|
| 28 | ### accomplishments in the world. Also the means by which she controls |
|---|
| 29 | ### FaerieMUD::Character objects or other instances of FaerieMUD::AnimatedObject |
|---|
| 30 | ### in the world. |
|---|
| 31 | class FaerieMUD::Spirit < FaerieMUD::Entity |
|---|
| 32 | include FaerieMUD::AccessorFunctions |
|---|
| 33 | |
|---|
| 34 | contributors :ged |
|---|
| 35 | |
|---|
| 36 | # SVN Revision |
|---|
| 37 | SVNRev = %q$Rev$ |
|---|
| 38 | |
|---|
| 39 | # SVN Id |
|---|
| 40 | SVNId = %q$Id$ |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | |
|---|
| 44 | ################################################################# |
|---|
| 45 | ### C O N S T I T U E N T C L A S S E S |
|---|
| 46 | ################################################################# |
|---|
| 47 | |
|---|
| 48 | ### The spirit's interface to the currently-animated object's physical |
|---|
| 49 | ### constituent. |
|---|
| 50 | class PhysicalAspect < FaerieMUD::Entity |
|---|
| 51 | include FaerieMUD::ComposedObject::Constituent |
|---|
| 52 | contributors :ged |
|---|
| 53 | def_statistic :incarnativity, :presence |
|---|
| 54 | end |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | ### The spirit's interface to the currently-animated object's mental |
|---|
| 58 | ### constituent. |
|---|
| 59 | class MentalAspect < FaerieMUD::Entity |
|---|
| 60 | include FaerieMUD::ComposedObject::Constituent |
|---|
| 61 | contributors :ged |
|---|
| 62 | def_statistic :kenning, :cognizance |
|---|
| 63 | end |
|---|
| 64 | |
|---|
| 65 | |
|---|
| 66 | ### The spirit's interface to the currently-animated object's creative |
|---|
| 67 | ### constituent. |
|---|
| 68 | class CreativeAspect < FaerieMUD::Entity |
|---|
| 69 | include FaerieMUD::ComposedObject::Constituent |
|---|
| 70 | contributors :ged |
|---|
| 71 | def_statistic :divinity, :influence |
|---|
| 72 | end |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | ### Define accessor methods for the given aspect. |
|---|
| 76 | def self::def_aspect_methods( *aspects ) |
|---|
| 77 | aspects.each do |aspect| |
|---|
| 78 | aspectClass = const_get( "#{aspect.to_s.capitalize}Aspect" ) or |
|---|
| 79 | raise FaerieMUD::ConstituentError, |
|---|
| 80 | "Cannot define aspect methods for non-existent aspect '#{aspect}'" |
|---|
| 81 | |
|---|
| 82 | # The aspect itself |
|---|
| 83 | define_method( aspect ) { @aspects[aspect] } |
|---|
| 84 | define_method( "#{aspect}=" ) {|arg| |
|---|
| 85 | check_type( arg, aspectClass ) |
|---|
| 86 | @aspects[aspect] = arg |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | # Statistic accessors |
|---|
| 90 | develAspect, linearAspect = aspectClass.statistic_names |
|---|
| 91 | develAspectEq = "#{develAspect}=".intern |
|---|
| 92 | linearAspectEq = "#{linearAspect}=".intern |
|---|
| 93 | |
|---|
| 94 | self.log.debug "Defining statistic accessors: %s/%s" % |
|---|
| 95 | [ develAspect, linearAspect ] |
|---|
| 96 | |
|---|
| 97 | define_method( develAspect ) { |
|---|
| 98 | self.send(aspect).statistic.developmental |
|---|
| 99 | } |
|---|
| 100 | define_method( develAspectEq ) {|arg| |
|---|
| 101 | self.send(aspect).statistic.developmental = arg |
|---|
| 102 | } |
|---|
| 103 | define_method( linearAspect ) { |
|---|
| 104 | self.send(aspect).statistic.linear |
|---|
| 105 | } |
|---|
| 106 | define_method( linearAspectEq ) {|arg| |
|---|
| 107 | self.send(aspect).statistic.linear = arg |
|---|
| 108 | } |
|---|
| 109 | end |
|---|
| 110 | end |
|---|
| 111 | |
|---|
| 112 | |
|---|
| 113 | ################################################################# |
|---|
| 114 | ### I N S T A N C E M E T H O D S |
|---|
| 115 | ################################################################# |
|---|
| 116 | |
|---|
| 117 | ### Create a new FaerieMUD::Spirit object. |
|---|
| 118 | def initialize( args={} ) |
|---|
| 119 | @animated_object = nil |
|---|
| 120 | @parser = FaerieMUD::CommandParser::new |
|---|
| 121 | |
|---|
| 122 | @io_handlers = { |
|---|
| 123 | :input => nil, |
|---|
| 124 | :output => nil, |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | @aspects = { |
|---|
| 128 | :physical => PhysicalAspect::new, |
|---|
| 129 | :mental => MentalAspect::new, |
|---|
| 130 | :creative => CreativeAspect::new, |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | @input_thread = nil |
|---|
| 134 | |
|---|
| 135 | super |
|---|
| 136 | end |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | ###### |
|---|
| 140 | public |
|---|
| 141 | ###### |
|---|
| 142 | |
|---|
| 143 | # The Spirit's FaerieMUD::CommandParser object. |
|---|
| 144 | attr_locked_reader :parser |
|---|
| 145 | |
|---|
| 146 | # The AnimatedObject controlled by this Spirit. |
|---|
| 147 | attr_locked_reader :animated_object |
|---|
| 148 | |
|---|
| 149 | # IO Callbacks |
|---|
| 150 | attr_reader :io_handlers |
|---|
| 151 | |
|---|
| 152 | # Pseudo-ComposedObject accessors |
|---|
| 153 | def_aspect_methods :physical, :mental, :creative |
|---|
| 154 | |
|---|
| 155 | # The input thread |
|---|
| 156 | attr_reader :input_thread |
|---|
| 157 | |
|---|
| 158 | |
|---|
| 159 | ### Provide a +handler+ for output events. |
|---|
| 160 | def on_output( &handler ) |
|---|
| 161 | @io_handlers[:output] = handler |
|---|
| 162 | end |
|---|
| 163 | |
|---|
| 164 | |
|---|
| 165 | ### Provide a +callback+ which will be called when the spirit is ready for |
|---|
| 166 | ### input. |
|---|
| 167 | def on_input_ready( &handler ) |
|---|
| 168 | @io_handlers[:input] = handler |
|---|
| 169 | end |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | ### Add the specified +object+ (FaerieMUD::AnimatedObject) to the list |
|---|
| 173 | ### being controlled by the Spirit. |
|---|
| 174 | def animate( object ) |
|---|
| 175 | self.writelocked do |
|---|
| 176 | object.controller = self |
|---|
| 177 | @animated_object = object |
|---|
| 178 | self.update_parser |
|---|
| 179 | end |
|---|
| 180 | end |
|---|
| 181 | |
|---|
| 182 | |
|---|
| 183 | ### Remove the specified +object+ (FaerieMUD::AnimatedObject) from the |
|---|
| 184 | ### list being controlled by the Spirit. |
|---|
| 185 | def deanimate |
|---|
| 186 | return if @animated_object.nil? |
|---|
| 187 | |
|---|
| 188 | self.writelocked do |
|---|
| 189 | @animated_object.controller = nil |
|---|
| 190 | @animated_object = nil |
|---|
| 191 | end |
|---|
| 192 | end |
|---|
| 193 | alias_method :unanimate, :deanimate |
|---|
| 194 | |
|---|
| 195 | |
|---|
| 196 | ### Synchronize the parser's verbset with the set of verbs from the |
|---|
| 197 | ### currently-animated objects. |
|---|
| 198 | def update_parser |
|---|
| 199 | verbs = @animated_object.verbs |
|---|
| 200 | @parser.update_verb_set( *verbs ) |
|---|
| 201 | end |
|---|
| 202 | |
|---|
| 203 | |
|---|
| 204 | ### Output the given +message+ to the player controlling the Spirit. |
|---|
| 205 | def output( message ) |
|---|
| 206 | @io_handlers[:output].call( message ) |
|---|
| 207 | end |
|---|
| 208 | |
|---|
| 209 | |
|---|
| 210 | ### Start the input loop of the spirit to fetch user input. |
|---|
| 211 | def start |
|---|
| 212 | self.log.debug "Starting input thread" |
|---|
| 213 | @input_thread = Thread::new( &(method(:input_loop)) ) |
|---|
| 214 | self.log.debug "Input thread started: %p" % [@input_thread] |
|---|
| 215 | end |
|---|
| 216 | |
|---|
| 217 | |
|---|
| 218 | ### Returns +true+ if the Spirit's input thread is running |
|---|
| 219 | def running? |
|---|
| 220 | @input_thread.alive? && @input_thread[:running] |
|---|
| 221 | end |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | ######### |
|---|
| 226 | protected |
|---|
| 227 | ######### |
|---|
| 228 | |
|---|
| 229 | ### Wait for input from the Player and pass it to the CommandParser for |
|---|
| 230 | ### action. |
|---|
| 231 | def input_loop |
|---|
| 232 | self.log.debug " in input loop on %p" % [self] |
|---|
| 233 | Thread.current.abort_on_exception = true |
|---|
| 234 | |
|---|
| 235 | Thread.current[:running] = true |
|---|
| 236 | while Thread.current[:running] |
|---|
| 237 | self.log.debug " calling input iohandler" |
|---|
| 238 | rawInput = @io_handlers[:input].call or next |
|---|
| 239 | |
|---|
| 240 | self.log.debug " Got input %p" % [rawInput] |
|---|
| 241 | @parser.parse( rawInput ) |
|---|
| 242 | end |
|---|
| 243 | end |
|---|
| 244 | |
|---|
| 245 | end # class FaerieMUD::Spirit |
|---|