Compare commits
6 commits
909ee0932b
...
74538756d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 74538756d5 | |||
| 5a98adb6ee | |||
| c8d9bdfae9 | |||
| fd977b91a2 | |||
| b81bc3edc8 | |||
| ac1d16095e |
5 changed files with 58 additions and 6 deletions
|
|
@ -36,6 +36,20 @@ class EmbeddedIFSession:
|
||||||
self._zmachine = ZMachine(story_bytes, self._ui)
|
self._zmachine = ZMachine(story_bytes, self._ui)
|
||||||
self._filesystem = self._ui.filesystem
|
self._filesystem = self._ui.filesystem
|
||||||
|
|
||||||
|
def _strip_prompt(self, output: str) -> str:
|
||||||
|
"""Strip trailing > prompt from game output (matches dfrotz behavior)."""
|
||||||
|
has_prompt = (
|
||||||
|
output.endswith("> ") or output.endswith(">\n") or output.endswith(">\r\n")
|
||||||
|
)
|
||||||
|
text = output.rstrip()
|
||||||
|
if text.endswith("\n>"):
|
||||||
|
return text[:-2].rstrip()
|
||||||
|
if text == ">":
|
||||||
|
return ""
|
||||||
|
if has_prompt and text.endswith(">"):
|
||||||
|
return text[:-1].rstrip()
|
||||||
|
return output
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def save_path(self) -> Path:
|
def save_path(self) -> Path:
|
||||||
safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", self.player.name)
|
safe_name = re.sub(r"[^a-zA-Z0-9_-]", "_", self.player.name)
|
||||||
|
|
@ -100,6 +114,7 @@ class EmbeddedIFSession:
|
||||||
# buffer), producing unwanted output. Suppress it and only show the
|
# buffer), producing unwanted output. Suppress it and only show the
|
||||||
# restore confirmation.
|
# restore confirmation.
|
||||||
return "restoring saved game...\r\nrestored."
|
return "restoring saved game...\r\nrestored."
|
||||||
|
output = self._strip_prompt(output)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
async def handle_input(self, text: str) -> IFResponse:
|
async def handle_input(self, text: str) -> IFResponse:
|
||||||
|
|
@ -130,6 +145,7 @@ class EmbeddedIFSession:
|
||||||
await loop.run_in_executor(None, wait_for_next_input)
|
await loop.run_in_executor(None, wait_for_next_input)
|
||||||
|
|
||||||
output = self._screen.flush()
|
output = self._screen.flush()
|
||||||
|
output = self._strip_prompt(output)
|
||||||
if self._done and self._error:
|
if self._done and self._error:
|
||||||
output = f"{output}\r\n{self._error}" if output else self._error
|
output = f"{output}\r\n{self._error}" if output else self._error
|
||||||
return IFResponse(output=output, done=self._done)
|
return IFResponse(output=output, done=self._done)
|
||||||
|
|
@ -149,7 +165,8 @@ class EmbeddedIFSession:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
logger.error(f"Interpreter crashed:\n{tb}")
|
logger.error(f"Interpreter crashed:\n{tb}")
|
||||||
self._error = f"interpreter error: {e}"
|
msg = str(e) or type(e).__name__
|
||||||
|
self._error = f"interpreter error: {msg}"
|
||||||
finally:
|
finally:
|
||||||
self._done = True
|
self._done = True
|
||||||
self._keyboard._waiting.set()
|
self._keyboard._waiting.set()
|
||||||
|
|
|
||||||
|
|
@ -315,8 +315,7 @@ async def shell(
|
||||||
if player.mode == "editor" and player.editor:
|
if player.mode == "editor" and player.editor:
|
||||||
_writer.write(f" {player.editor.cursor + 1}> ")
|
_writer.write(f" {player.editor.cursor + 1}> ")
|
||||||
elif player.mode == "if" and player.if_session:
|
elif player.mode == "if" and player.if_session:
|
||||||
# IF mode: game writes its own prompt, don't add another
|
_writer.write("> ")
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
_writer.write("mud> ")
|
_writer.write("mud> ")
|
||||||
await _writer.drain()
|
await _writer.drain()
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ class MudInputStream(zstream.ZInputStream):
|
||||||
text = self._input_queue.get()
|
text = self._input_queue.get()
|
||||||
if text:
|
if text:
|
||||||
return ord(text[0])
|
return ord(text[0])
|
||||||
return 0
|
# Player hit Enter with no text — return 13 (ZSCII Enter/Return)
|
||||||
|
# so "press any key" prompts work in a line-oriented MUD client.
|
||||||
|
return 13
|
||||||
|
|
||||||
def feed(self, text: str):
|
def feed(self, text: str):
|
||||||
self._input_queue.put(text)
|
self._input_queue.put(text)
|
||||||
|
|
|
||||||
|
|
@ -226,17 +226,27 @@ class ZCpu:
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def step_fast(self):
|
def step_fast(self):
|
||||||
"""Execute a single instruction without tracing.
|
"""Execute a single instruction with lightweight tracing.
|
||||||
|
|
||||||
Returns True if execution should continue.
|
Returns True if execution should continue.
|
||||||
"""
|
"""
|
||||||
|
current_pc = self._opdecoder.program_counter
|
||||||
(opcode_class, opcode_number, operands) = self._opdecoder.get_next_instruction()
|
(opcode_class, opcode_number, operands) = self._opdecoder.get_next_instruction()
|
||||||
entry = self._dispatch[opcode_class][opcode_number]
|
entry = self._dispatch[opcode_class][opcode_number]
|
||||||
if entry is None:
|
if entry is None:
|
||||||
raise ZCpuIllegalInstruction
|
self._trace.append(f" {current_pc:06x} ILLEGAL")
|
||||||
|
self._dump_trace()
|
||||||
|
raise ZCpuIllegalInstruction(
|
||||||
|
f"illegal opcode class={opcode_class} num={opcode_number}"
|
||||||
|
f" at PC={current_pc:#x}"
|
||||||
|
)
|
||||||
implemented, func = entry
|
implemented, func = entry
|
||||||
if not implemented:
|
if not implemented:
|
||||||
return False
|
return False
|
||||||
|
self._trace.append(
|
||||||
|
f" {current_pc:06x} {func.__name__}"
|
||||||
|
f"({', '.join(str(x) for x in operands)})"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
func(self, *operands)
|
func(self, *operands)
|
||||||
except (ZCpuQuit, ZCpuRestart):
|
except (ZCpuQuit, ZCpuRestart):
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,8 @@ class ZObjectParser:
|
||||||
def get_attribute(self, objectnum, attrnum):
|
def get_attribute(self, objectnum, attrnum):
|
||||||
"""Return value (0 or 1) of attribute number ATTRNUM of object
|
"""Return value (0 or 1) of attribute number ATTRNUM of object
|
||||||
number OBJECTNUM."""
|
number OBJECTNUM."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
object_addr = self._get_object_addr(objectnum)
|
object_addr = self._get_object_addr(objectnum)
|
||||||
|
|
||||||
|
|
@ -183,6 +185,8 @@ class ZObjectParser:
|
||||||
|
|
||||||
def set_attribute(self, objectnum, attrnum):
|
def set_attribute(self, objectnum, attrnum):
|
||||||
"""Set attribute number ATTRNUM of object number OBJECTNUM to 1."""
|
"""Set attribute number ATTRNUM of object number OBJECTNUM to 1."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return
|
||||||
|
|
||||||
object_addr = self._get_object_addr(objectnum)
|
object_addr = self._get_object_addr(objectnum)
|
||||||
|
|
||||||
|
|
@ -206,6 +210,8 @@ class ZObjectParser:
|
||||||
|
|
||||||
def clear_attribute(self, objectnum, attrnum):
|
def clear_attribute(self, objectnum, attrnum):
|
||||||
"""Clear attribute number ATTRNUM of object number OBJECTNUM to 0."""
|
"""Clear attribute number ATTRNUM of object number OBJECTNUM to 0."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return
|
||||||
|
|
||||||
object_addr = self._get_object_addr(objectnum)
|
object_addr = self._get_object_addr(objectnum)
|
||||||
|
|
||||||
|
|
@ -247,18 +253,24 @@ class ZObjectParser:
|
||||||
|
|
||||||
def get_parent(self, objectnum):
|
def get_parent(self, objectnum):
|
||||||
"""Return object number of parent of object number OBJECTNUM."""
|
"""Return object number of parent of object number OBJECTNUM."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
||||||
return parent
|
return parent
|
||||||
|
|
||||||
def get_child(self, objectnum):
|
def get_child(self, objectnum):
|
||||||
"""Return object number of child of object number OBJECTNUM."""
|
"""Return object number of child of object number OBJECTNUM."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def get_sibling(self, objectnum):
|
def get_sibling(self, objectnum):
|
||||||
"""Return object number of sibling of object number OBJECTNUM."""
|
"""Return object number of sibling of object number OBJECTNUM."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
[parent, sibling, child] = self._get_parent_sibling_child(objectnum)
|
||||||
return sibling
|
return sibling
|
||||||
|
|
@ -298,6 +310,8 @@ class ZObjectParser:
|
||||||
|
|
||||||
def remove_object(self, objectnum):
|
def remove_object(self, objectnum):
|
||||||
"""Detach object OBJECTNUM from its parent (unlink from sibling chain)."""
|
"""Detach object OBJECTNUM from its parent (unlink from sibling chain)."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return
|
||||||
|
|
||||||
parent = self.get_parent(objectnum)
|
parent = self.get_parent(objectnum)
|
||||||
if parent == 0:
|
if parent == 0:
|
||||||
|
|
@ -335,6 +349,8 @@ class ZObjectParser:
|
||||||
Per the Z-spec: if new_child already has a parent, it is first
|
Per the Z-spec: if new_child already has a parent, it is first
|
||||||
removed from that parent's child list, then made the first child
|
removed from that parent's child list, then made the first child
|
||||||
of parent_object."""
|
of parent_object."""
|
||||||
|
if parent_object == 0 or new_child == 0:
|
||||||
|
return
|
||||||
|
|
||||||
# Remove from old parent first (spec says "first removed")
|
# Remove from old parent first (spec says "first removed")
|
||||||
self.remove_object(new_child)
|
self.remove_object(new_child)
|
||||||
|
|
@ -347,6 +363,8 @@ class ZObjectParser:
|
||||||
|
|
||||||
def get_shortname(self, objectnum):
|
def get_shortname(self, objectnum):
|
||||||
"""Return 'short name' of object number OBJECTNUM as ascii string."""
|
"""Return 'short name' of object number OBJECTNUM as ascii string."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
addr = self._get_proptable_addr(objectnum)
|
addr = self._get_proptable_addr(objectnum)
|
||||||
return self._stringfactory.get(addr + 1)
|
return self._stringfactory.get(addr + 1)
|
||||||
|
|
@ -354,6 +372,8 @@ class ZObjectParser:
|
||||||
def get_prop(self, objectnum, propnum):
|
def get_prop(self, objectnum, propnum):
|
||||||
"""Return either a byte or word value of property PROPNUM of
|
"""Return either a byte or word value of property PROPNUM of
|
||||||
object OBJECTNUM."""
|
object OBJECTNUM."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
(addr, size) = self.get_prop_addr_len(objectnum, propnum)
|
(addr, size) = self.get_prop_addr_len(objectnum, propnum)
|
||||||
if size == 1:
|
if size == 1:
|
||||||
return self._memory[addr]
|
return self._memory[addr]
|
||||||
|
|
@ -467,6 +487,8 @@ class ZObjectParser:
|
||||||
def get_property_data_address(self, objectnum, propnum):
|
def get_property_data_address(self, objectnum, propnum):
|
||||||
"""Return the address of property PROPNUM's data bytes for object
|
"""Return the address of property PROPNUM's data bytes for object
|
||||||
OBJECTNUM. Return 0 if the object doesn't have that property."""
|
OBJECTNUM. Return 0 if the object doesn't have that property."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
addr, size = self.get_prop_addr_len(objectnum, propnum)
|
addr, size = self.get_prop_addr_len(objectnum, propnum)
|
||||||
|
|
@ -484,6 +506,8 @@ class ZObjectParser:
|
||||||
"""If PROPNUM is 0, return the first property number of object OBJECTNUM.
|
"""If PROPNUM is 0, return the first property number of object OBJECTNUM.
|
||||||
Otherwise, return the property number after PROPNUM in the property list.
|
Otherwise, return the property number after PROPNUM in the property list.
|
||||||
Return 0 if there are no more properties."""
|
Return 0 if there are no more properties."""
|
||||||
|
if objectnum == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
if propnum == 0:
|
if propnum == 0:
|
||||||
# Return first property number
|
# Return first property number
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue