@@ -126,6 +126,25 @@ object PLAYER
126126 return !is_player(args[1]);
127127 endverb
128128
129+ verb command_scope_for (this none this) owner: HACKER flags: "rxd"
130+ "Contribute the player and their held inventory to the command scope.";
131+ {actor, ?context = []} = args;
132+ if (actor != this)
133+ return {this};
134+ endif
135+ entries = `pass(@args) ! E_TYPE, E_VERBNF => {this}';
136+ typeof(entries) == LIST || (entries = {entries});
137+ inventory = this.contents;
138+ inventory || return entries;
139+ for item in (inventory)
140+ if (!valid(item))
141+ continue;
142+ endif
143+ entries = {@entries, item};
144+ endfor
145+ return entries;
146+ endverb
147+
129148 verb mk_emote_event (this none this) owner: HACKER flags: "rxd"
130149 return $event:mk_emote(this, $sub:nc(), " ", args[1]):with_this(this.location);
131150 endverb
@@ -164,4 +183,288 @@ object PLAYER
164183 endif
165184 return <$look, [what -> this, title -> this:name(), description -> description], {@this.contents}>;
166185 endverb
167- endobject
186+
187+ verb command_environment (this none this) owner: HACKER flags: "rxd"
188+ "Assemble the environment list used for command parsing.";
189+ {command, ?options = []} = args;
190+ "Capture contextual state for downstream scope hooks.";
191+ context = ['actor -> this, 'command -> command, 'options -> options];
192+ location = this.location;
193+ "Gather ambient command holders from the actor and their location.";
194+ ambient_providers = this:_command_ambient_providers(context);
195+ ambient = this:_collect_command_ambient(ambient_providers, context);
196+ "Seed the breadth-first scope walk with the player and their location.";
197+ seeds = this:_command_scope_initial(context);
198+ seeds || (seeds = {this});
199+ queue = seeds;
200+ processed = {};
201+ environment = {};
202+ while (length(queue) > 0)
203+ current = queue[1];
204+ if (length(queue) == 1)
205+ queue = {};
206+ else
207+ queue = queue[2..$];
208+ endif
209+ if (typeof(current) != OBJ)
210+ continue;
211+ endif
212+ if (!valid(current))
213+ continue;
214+ endif
215+ if (current in processed)
216+ continue;
217+ endif
218+ processed = {@processed, current};
219+ "Ask each scope provider for additional entries.";
220+ additions = `current:command_scope_for(this, context) ! E_TYPE, E_VERBNF => {current}';
221+ typeof(additions) == LIST || (additions = {additions});
222+ for entry in (additions)
223+ normalized = this:_normalize_command_scope_entry(entry);
224+ if (!normalized)
225+ continue;
226+ endif
227+ if (typeof(normalized) == OBJ)
228+ entry_obj = normalized;
229+ else
230+ entry_obj = normalized[1];
231+ endif
232+ queue = this:_queue_scope_object(queue, processed, entry_obj);
233+ if (!this:_scope_entry_visible(entry_obj, normalized, context))
234+ continue;
235+ endif
236+ "Record every normalized entry so we can layer ordering afterwards.";
237+ environment = this:_merge_scope_environment(environment, normalized);
238+ endfor
239+ endwhile
240+ "Recreate the classic MOO ordering: player → inventory → room → room contents.";
241+ actor_entry = 0;
242+ location_entry = 0;
243+ ambient_extras = {};
244+ for entry in (ambient)
245+ entry_obj = this:_entry_object(entry);
246+ if (entry_obj == this && !actor_entry)
247+ actor_entry = entry;
248+ continue;
249+ endif
250+ if (valid(location) && entry_obj == location && !location_entry)
251+ location_entry = entry;
252+ continue;
253+ endif
254+ ambient_extras = {@ambient_extras, entry};
255+ endfor
256+ actor_entry || (actor_entry = this);
257+ if (valid(location))
258+ location_entry || (location_entry = location);
259+ endif
260+ inventory_entries = this:_select_entries_by_location(environment, this);
261+ location_entries = valid(location) ? this:_select_entries_by_location(environment, location) | {};
262+ final_env = {};
263+ final_env = this:_merge_scope_environment(final_env, actor_entry);
264+ for entry in (inventory_entries)
265+ final_env = this:_merge_scope_environment(final_env, entry);
266+ endfor
267+ "Place the room and its visible contents immediately after the actor's inventory.";
268+ if (valid(location))
269+ final_env = this:_merge_scope_environment(final_env, location_entry);
270+ for entry in (location_entries)
271+ final_env = this:_merge_scope_environment(final_env, entry);
272+ endfor
273+ endif
274+ "Append any extra ambient affordances after the core room structure.";
275+ for entry in (ambient_extras)
276+ final_env = this:_merge_scope_environment(final_env, entry);
277+ endfor
278+ "Finally include any remaining scoped objects (containers, fixtures, etc.).";
279+ for entry in (environment)
280+ final_env = this:_merge_scope_environment(final_env, entry);
281+ endfor
282+ final_env || return {this};
283+ return final_env;
284+ endverb
285+
286+ verb _command_scope_initial (this none this) owner: HACKER flags: "rxd"
287+ "Return the initial seed objects for command scope gathering.";
288+ {context} = args;
289+ seeds = {this};
290+ location = this.location;
291+ valid(location) && (seeds = {@seeds, location});
292+ return seeds;
293+ endverb
294+
295+ verb _normalize_command_scope_entry (this none this) owner: HACKER flags: "rxd"
296+ "Coerce a scope entry into either OBJ or {OBJ, aliases...}.";
297+ {entry} = args;
298+ typeof(entry) == ERR && return 0;
299+ typeof(entry) == OBJ && return entry;
300+ typeof(entry) != LIST && return 0;
301+ entry && typeof(entry[1]) == OBJ || return 0;
302+ length(entry) == 1 && return entry[1];
303+ return {entry[1], @entry[2..$]};
304+ endverb
305+
306+ verb _merge_scope_environment (this none this) owner: HACKER flags: "rxd"
307+ "Insert or merge a scope entry into the environment list.";
308+ {environment, entry} = args;
309+ typeof(entry) == OBJ && return this:_merge_scope_entry_obj(environment, entry);
310+ typeof(entry) != LIST && return environment;
311+ entry_obj = entry[1];
312+ idx = this:_find_scope_entry(environment, entry_obj);
313+ idx || return {@environment, entry};
314+ current = environment[idx];
315+ typeof(current) == OBJ && return this:_replace_scope_entry(environment, idx, entry);
316+ names = entry[2..$];
317+ names || return environment;
318+ merged = this:_merge_scope_names(current[2..$], names);
319+ environment[idx] = {entry_obj, @merged};
320+ return environment;
321+ endverb
322+
323+ verb _merge_scope_entry_obj (this none this) owner: HACKER flags: "rxd"
324+ "Insert an object-only entry if it is not already present.";
325+ {environment, entry_obj} = args;
326+ idx = this:_find_scope_entry(environment, entry_obj);
327+ idx && return environment;
328+ return {@environment, entry_obj};
329+ endverb
330+
331+ verb _merge_scope_names (this none this) owner: HACKER flags: "rxd"
332+ "Combine alias lists without duplicates.";
333+ {existing, extras} = args;
334+ extras || return existing;
335+ typeof(existing) != LIST && (existing = {});
336+ for name in (extras)
337+ if (name in existing)
338+ continue;
339+ endif
340+ existing = {@existing, name};
341+ endfor
342+ return existing;
343+ endverb
344+
345+ verb _find_scope_entry (this none this) owner: HACKER flags: "rxd"
346+ "Locate the index of an object inside the environment list.";
347+ {environment, entry_obj} = args;
348+ idx = 1;
349+ for entry in (environment)
350+ candidate = typeof(entry) == OBJ ? entry | entry[1];
351+ candidate == entry_obj && return idx;
352+ idx = idx + 1;
353+ endfor
354+ return 0;
355+ endverb
356+
357+ verb _replace_scope_entry (this none this) owner: HACKER flags: "rxd"
358+ "Replace an environment entry at a specific index.";
359+ {environment, idx, entry} = args;
360+ environment[idx] = entry;
361+ return environment;
362+ endverb
363+
364+ verb _queue_scope_object (this none this) owner: HACKER flags: "rxd"
365+ "Queue an object for later scope expansion if it has not been processed.";
366+ {queue, processed, entry_obj} = args;
367+ typeof(entry_obj) == OBJ || return queue;
368+ valid(entry_obj) || return queue;
369+ entry_obj in processed && return queue;
370+ for pending in (queue)
371+ pending == entry_obj && return queue;
372+ endfor
373+ return {@queue, entry_obj};
374+ endverb
375+
376+ verb _scope_entry_visible (this none this) owner: HACKER flags: "rxd"
377+ "Ask an object whether it should be exposed in the command scope.";
378+ {entry_obj, entry, context} = args;
379+ typeof(entry_obj) == OBJ || return false;
380+ valid(entry_obj) || return false;
381+ visible = `entry_obj:command_scope_visible(this, context) ! ANY => false';
382+ return visible ? true | false;
383+ endverb
384+
385+ verb _command_ambient_providers (this none this) owner: HACKER flags: "rxd"
386+ "Return the list of objects allowed to provide ambient verbs.";
387+ {context} = args;
388+ providers = {this};
389+ location = this.location;
390+ valid(location) && (providers = {@providers, location});
391+ return providers;
392+ endverb
393+
394+ verb _collect_command_ambient (this none this) owner: HACKER flags: "rxd"
395+ "Gather ambient verb holders from the allowed providers.";
396+ {providers, context} = args;
397+ ambient = {};
398+ typeof(providers) != LIST && return ambient;
399+ for provider in (providers)
400+ if (typeof(provider) != OBJ)
401+ continue;
402+ endif
403+ if (!valid(provider))
404+ continue;
405+ endif
406+ entries = `provider:command_ambient_for(this, context) ! E_TYPE, E_VERBNF => {provider}';
407+ typeof(entries) == LIST || (entries = {entries});
408+ for entry in (entries)
409+ normalized = this:_normalize_command_scope_entry(entry);
410+ if (!normalized)
411+ continue;
412+ endif
413+ if (typeof(normalized) == OBJ)
414+ ambient_obj = normalized;
415+ else
416+ ambient_obj = normalized[1];
417+ endif
418+ if (!this:_ambient_entry_visible(ambient_obj, normalized, context))
419+ continue;
420+ endif
421+ ambient = this:_merge_scope_environment(ambient, normalized);
422+ endfor
423+ endfor
424+ return ambient;
425+ endverb
426+
427+ verb _ambient_entry_visible (this none this) owner: HACKER flags: "rxd"
428+ "Check whether an ambient entry should be exposed.";
429+ {ambient_obj, entry, context} = args;
430+ typeof(ambient_obj) == OBJ || return false;
431+ valid(ambient_obj) || return false;
432+ visible = `ambient_obj:command_ambient_visible(this, context) ! ANY => false';
433+ return visible ? true | false;
434+ endverb
435+
436+ verb _select_entries_by_location (this none this) owner: HACKER flags: "rxd"
437+ "Return entries whose underlying object is located inside the target container.";
438+ {entries, container} = args;
439+ result = {};
440+ typeof(entries) != LIST && return result;
441+ typeof(container) != OBJ && return result;
442+ for entry in (entries)
443+ entry_obj = this:_entry_object(entry);
444+ if (!entry_obj)
445+ continue;
446+ endif
447+ if (entry_obj == container)
448+ continue;
449+ endif
450+ if (entry_obj.location != container)
451+ continue;
452+ endif
453+ result = {@result, entry};
454+ endfor
455+ return result;
456+ endverb
457+
458+ verb _entry_object (this none this) owner: HACKER flags: "rxd"
459+ "Extract the object referred to by a scope entry.";
460+ {entry} = args;
461+ if (typeof(entry) == OBJ)
462+ return entry;
463+ endif
464+ if (typeof(entry) == LIST && entry && typeof(entry[1]) == OBJ)
465+ return entry[1];
466+ endif
467+ return 0;
468+ endverb
469+
470+ endobject
0 commit comments