Skip to content

Commit

Permalink
Bool handling bug fixes, automapper updates
Browse files Browse the repository at this point in the history
* fix a few bugs where "0 == false" and others would not evaluate to true.
* proper map window toggling - if the map window is visible, but not active, the "cmd-m" hotkey will focus the window.  If the map window is the active window "cmd-m" will close the window and focus the game window.
* add `#g`, `#go`, `#walk`, `#walkto` aliases for `#goto`
* add roomobscured variable - set to `1` when room exits is "obscured by a thick fog"
* add roomportals variable - a piped list of non cardinal exits
* when room is obscured by fog, automapper will print the normal room exits
  • Loading branch information
joemcbride committed Sep 14, 2024
1 parent 99ffbe0 commit 6af94eb
Show file tree
Hide file tree
Showing 22 changed files with 312 additions and 82 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Commands

- [x] #alias
Expand All @@ -10,7 +9,7 @@
- [ ] #preset
- [x] #script
- [x] #send
- [ ] #gag
- [x] #gag
- [x] #window (partially complete)
- [x] /tracker (first plugin?)
- [x] #beep
Expand Down
6 changes: 5 additions & 1 deletion app/src/Outlander/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

func sendCommand(_ command: String) {
activeController?.command(command)
if let handler = activeWindow?.contentViewController as? IUICommandHandler {
handler.command(command)
} else if let handler = activeWindow?.windowController as? IUICommandHandler {
handler.command(command)
}
}

@IBAction func checkForUpdates(_: Any) {
Expand Down
1 change: 0 additions & 1 deletion app/src/Outlander/Handlers/EpediaCommandHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import Foundation

class EpediaCommandHandler: ICommandHandler {
var command = "#epedia"

var aliases = ["#epedia", "#wiki", "#drwiki"]

func canHandle(_ command: String) -> Bool {
Expand Down
26 changes: 21 additions & 5 deletions app/src/Outlander/Handlers/GotoCommandHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,38 @@ struct AutomapperPathEvent: Event {
}

class GotoComandHandler: ICommandHandler {
var command = "#goto"

let log = LogManager.getLog(String(describing: GotoComandHandler.self))

var command = "#goto"
var aliases = ["#g", "#go", "#goto", "#walk", "#walkto"]

private var started = Date()

func canHandle(_ command: String) -> Bool {
let commands = command.split(separator: " ", maxSplits: 1)
return commands.count > 0 && aliases.contains(String(commands[0]).lowercased())
}

func handle(_ input: String, with context: GameContext) {
let area = input[command.count...].trimmingCharacters(in: .whitespacesAndNewlines).lowercased().components(separatedBy: "from")
let commands = input.split(separator: " ", maxSplits: 1)
var text = ""
if commands.count > 1 {
text = String(commands[1]).trimLeadingWhitespace()
}

let area = text
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased()
.components(separatedBy: "from")
.map { $0.trimmingCharacters(in: .whitespaces) }

started = Date()

DispatchQueue.global(qos: .userInteractive).async {
if area.count > 1 {
self.gotoArea(area: area[0].trimmingCharacters(in: .whitespaces), from: area[1].trimmingCharacters(in: .whitespaces), context: context)
self.gotoArea(area: area[0], from: area[1], context: context)
} else if area.count > 0 {
self.gotoArea(area: area[0].trimmingCharacters(in: .whitespaces), context: context)
self.gotoArea(area: area[0], context: context)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/Outlander/Infrastructure/BoolExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ extension Bool {
func toYesNoString() -> String {
self ? "yes" : "no"
}

func toZeroOneString() -> String {
self ? "1" : "0"
}
}
16 changes: 11 additions & 5 deletions app/src/Outlander/Infrastructure/GameContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,37 +49,43 @@ extension GameContext {
let objects = globalVars["roomobjs"]
let players = globalVars["roomplayers"]
let exits = globalVars["roomexits"]
let obscured = globalVars["roomobscured"]

var tags: [TextTag] = []
var room = ""

if name != nil, name?.count ?? 0 > 0 {
if !name.isEmptyOrNil {
let tag = TextTag.tagFor(name!, preset: "roomname")
tags.append(tag)
room += "\n"
}

if desc != nil, desc?.count ?? 0 > 0 {
if !desc.isEmptyOrNil {
let tag = TextTag.tagFor("\(room)\(desc!)\n", preset: "roomdesc")
tags.append(tag)
room = ""
}

if objects != nil, objects?.count ?? 0 > 0 {
if !objects.isEmptyOrNil {
room += "\(objects!)\n"
}

if players != nil, players?.count ?? 0 > 0 {
if !players.isEmptyOrNil {
room += "\(players!)\n"
}

if exits != nil, exits?.count ?? 0 > 0 {
if !exits.isEmptyOrNil {
room += "\(exits!)\n"
}

tags.append(TextTag.tagFor(room))

if let zone = mapZone, let currentRoom = findCurrentRoom(zone) {
if obscured?.toBool() == true && currentRoom.cardinalExits().count > 0 {
let cardinalExits = currentRoom.cardinalExits().joined(separator: ", ")
tags.append(TextTag.tagFor("Mapped directions:: \(cardinalExits)", preset: "automapper"))
}

let mappedExits = currentRoom.nonCardinalExists().map(\.move).joined(separator: ", ")
if mappedExits.count > 0 {
tags.append(TextTag.tagFor("Mapped exits: \(mappedExits)", preset: "automapper"))
Expand Down
18 changes: 18 additions & 0 deletions app/src/Outlander/Infrastructure/StringExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,22 @@ extension Double {
let number = NSNumber(value: self)
return Self.valueFormatter.string(from: number)!
}

func toBool() -> Bool? {
if self == 1 {
return true
}

if self == 0 {
return false
}

return nil
}
}

extension Optional where Wrapped: Collection {
var isEmptyOrNil: Bool {
return self?.isEmpty ?? true
}
}
23 changes: 21 additions & 2 deletions app/src/Outlander/Plugins/AutoMapperPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class AutoMapperPlugin: OPlugin {
private var context: GameContext?
private var movedRooms: Bool = false
private var showAfterPrompt: Bool = false
private let risingMistsText = "obscured by a thick fog"
private var hasRisingMists = false

private let logger = LogManager.getLog(String(describing: AutoMapperPlugin.self))

Expand All @@ -33,6 +35,11 @@ class AutoMapperPlugin: OPlugin {
}

func variableChanged(variable: String, value: String) {
if variable == "roomexits" {
hasRisingMists = !value.isEmpty && value.contains(risingMistsText)
host?.set(variable: "roomobscured", value: hasRisingMists.toZeroOneString())
}

guard variable == "zoneid" else {
return
}
Expand Down Expand Up @@ -96,14 +103,26 @@ class AutoMapperPlugin: OPlugin {
if assignRoom {
host?.set(variable: "roomid", value: swapped.id)
host?.set(variable: "roomname", value: swapped.name)
host?.set(variable: "roomnote", value: swapped.notes ?? "")
host?.set(variable: "roomcolor", value: swapped.color ?? "")
let roomPortals = swapped.nonCardinalExists().map(\.move).joined(separator: "|")
host?.set(variable: "roomportals", value: roomPortals)
}

var result = xml

if hasRisingMists, swapped.cardinalExits().count > 0 {
let cardinalExits = swapped.cardinalExits().joined(separator: ", ")
let tag = "<preset id='automapper'>Mapped directions: \(cardinalExits)</preset>\n"
result.insert(contentsOf: tag, at: insertionIdx.lowerBound)
}

guard exits.count > 0 else {
return xml
return result
}

let tag = "<preset id='automapper'>Mapped exits: \(exits)</preset>\n"
var result = xml

result.insert(contentsOf: tag, at: insertionIdx.lowerBound)
return result
}
Expand Down
87 changes: 63 additions & 24 deletions app/src/Outlander/Scripting/ExpressionEvaluator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ public extension AnyExpression {
// print("evaluating \(input)")
let replaced = ExpressionEvaluator.replaceSingleOperators(input)
let exp = Expression.parse(replaced, usingCache: true)

func equalBools(a: Bool?, b: Bool?) -> Bool {
guard let a = a, let b = b else {
return false
}

return a == b
}

func andBools(a: Bool?, b: Bool?) -> Bool {
guard let a = a, let b = b else {
return false
}

return a && b
}

func orBools(a: Bool?, b: Bool?) -> Bool {
return a == true || b == true
}

self.init(
exp,
impureSymbols: { symbol in
Expand All @@ -126,10 +147,10 @@ public extension AnyExpression {
case let lhs as String:
return !(lhs.toBool() == true)
case let lhs as Double:
return lhs == 0 ? true : false
return !(lhs.toBool() == true)
default:
let types = args.map { "\(type(of: $0))" }.joined(separator: ", ")
throw Expression.Error.message("Arguments \(types) are not compatible with \(symbol)")
throw Expression.Error.message("! arguments \(types) are not compatible with \(symbol)")
}
}
case .prefix("!!"):
Expand All @@ -140,32 +161,42 @@ public extension AnyExpression {
case let lhs as String:
return lhs.toBool() == true
case let lhs as Double:
return lhs == 0 ? false : true
return lhs.toBool() == true
default:
let types = args.map { "\(type(of: $0))" }.joined(separator: ", ")
throw Expression.Error.message("Arguments \(types) are not compatible with \(symbol)")
throw Expression.Error.message("!! arguments \(types) are not compatible with \(symbol)")
}
}
case .infix("="):
fallthrough
case .infix("=="):
return { args in
switch (args[0], args[1]) {
case let (lhs as Bool, rhs as Bool):
return lhs == rhs
case let (lhs as Double, rhs as Double):
return lhs == rhs
case let (lhs as Double, rhs as Bool):
return lhs != 0 && rhs
return equalBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as Double):
return lhs && rhs != 0
return equalBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Bool):
return lhs.toBool() == true && rhs
return equalBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as String):
return lhs && rhs.toBool() == true
return equalBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Double):
let equal = lhs == "\(rhs)"
guard !equal else { return true }
return equalBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as Double, rhs as String):
let equal = "\(lhs)" == rhs
guard !equal else { return true }
return equalBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as String, rhs as String):
return lhs == rhs
return lhs == rhs || equalBools(a: lhs.toBool(), b: rhs.toBool())
default:
let types = args.map { "\(type(of: $0))" }.joined(separator: ", ")
throw Expression.Error.message("Arguments \(types) are not compatible with \(symbol)")
throw Expression.Error.message("== arguments \(types) are not compatible with \(symbol)")
}
}
case .infix("&&"):
Expand All @@ -174,20 +205,24 @@ public extension AnyExpression {
case let (lhs as Bool, rhs as Bool):
return lhs && rhs
case let (lhs as Double, rhs as Double):
return lhs != 0 && rhs != 0
return andBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as Double, rhs as Bool):
return lhs != 0 && rhs
return andBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as Double):
return lhs && rhs != 0
return andBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Bool):
return lhs.toBool() == true && rhs
return andBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as String):
return lhs && rhs.toBool() == true
return andBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Double):
return andBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as Double, rhs as String):
return andBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as String, rhs as String):
return lhs.toBool() == true && rhs.toBool() == true
return andBools(a: lhs.toBool(), b: rhs.toBool())
default:
let types = args.map { "\(type(of: $0))" }.joined(separator: ", ")
throw Expression.Error.message("Arguments \(types) are not compatible with \(symbol)")
throw Expression.Error.message("&& arguments \(types) are not compatible with \(symbol)")
}
}
case .infix("||"):
Expand All @@ -196,20 +231,24 @@ public extension AnyExpression {
case let (lhs as Bool, rhs as Bool):
return lhs || rhs
case let (lhs as Double, rhs as Double):
return lhs != 0 || rhs != 0
return orBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as Double, rhs as Bool):
return lhs != 0 || rhs
return orBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as Double):
return lhs || rhs != 0
return orBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Bool):
return lhs.toBool() == true || rhs
return orBools(a: lhs.toBool(), b: rhs)
case let (lhs as Bool, rhs as String):
return lhs || rhs.toBool() == true
return orBools(a: lhs, b: rhs.toBool())
case let (lhs as String, rhs as Double):
return orBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as Double, rhs as String):
return orBools(a: lhs.toBool(), b: rhs.toBool())
case let (lhs as String, rhs as String):
return lhs.toBool() == true || rhs.toBool() == true
return orBools(a: lhs.toBool(), b: rhs.toBool())
default:
let types = args.map { "\(type(of: $0))" }.joined(separator: ", ")
throw Expression.Error.message("Arguments \(types) are not compatible with \(symbol)")
throw Expression.Error.message("|| arguments \(types) are not compatible with \(symbol)")
}
}
case let .variable(name):
Expand Down
6 changes: 3 additions & 3 deletions app/src/Outlander/Scripting/Script.swift
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ class Script {
context.currentLineNumber = -1

let diff = Date().timeIntervalSince(started!)
sendText("[Script '\(fileName)' completed after \(diff.formatted)]")
sendText("[Script '\(fileName)' completed after \(diff.formatted)]", preset: "scriptinput")

gameContext.events2.post(ScriptCompleteEvent(name: fileName))
}
Expand Down Expand Up @@ -673,7 +673,7 @@ class Script {

scriptFilePath = scriptFilePath.replacingOccurrences(of: homeDir, with: "~/").hexDecoededString()

sendText("[Starting '\(scriptFilePath)' at \(formattedDate)]")
sendText("[Starting '\(scriptFilePath)' at \(formattedDate)]", preset: "scriptinput")

self.fileName = scriptName
context.variables["scriptname"] = scriptName
Expand Down Expand Up @@ -701,7 +701,7 @@ class Script {
sendText("script '\(scriptName)' cannot include itself!", preset: "scripterror", scriptLine: index, fileName: scriptName)
continue
}
sendText("including '\(includeName)'", preset: "scriptecho", scriptLine: index, fileName: scriptName)
sendText("including '\(includeName)'", preset: "scriptinput", scriptLine: index, fileName: scriptName)
initialize(includeName, isInclude: true)
} else {
let scriptLine = ScriptLine(
Expand Down
Loading

0 comments on commit 6af94eb

Please sign in to comment.