diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/player.lisp b/lisp/player.lisp index c989c22..66328bf 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -12,17 +12,19 @@ (defstruct player (name "") - (race NIL) - (class NIL) + (race "") + (class "") (strength 0) (dexterity 0) (constitution 0) (intelligence 0) (item NIL) - (weapon NIL) + (weapon "") (place "") (experience 0) - (health 0)) + (max-health 50) + (health 50) + (game-admin NIL)) (defstruct race @@ -41,6 +43,8 @@ (special-ability NIL)) +;; This function is probably superfluous, as the player struct should only store +;; names of game objects (the actual objects are stored in *world*) (let ((list-function (make-list-function 'player NIL))) (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/player.lisp b/lisp/player.lisp index c989c22..66328bf 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -12,17 +12,19 @@ (defstruct player (name "") - (race NIL) - (class NIL) + (race "") + (class "") (strength 0) (dexterity 0) (constitution 0) (intelligence 0) (item NIL) - (weapon NIL) + (weapon "") (place "") (experience 0) - (health 0)) + (max-health 50) + (health 50) + (game-admin NIL)) (defstruct race @@ -41,6 +43,8 @@ (special-ability NIL)) +;; This function is probably superfluous, as the player struct should only store +;; names of game objects (the actual objects are stored in *world*) (let ((list-function (make-list-function 'player NIL))) (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." diff --git a/lisp/ui.lisp b/lisp/ui.lisp new file mode 100644 index 0000000..f8cb7cf --- /dev/null +++ b/lisp/ui.lisp @@ -0,0 +1,300 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The client module is responsible for the actual user interface presented +;;; to a player. (Warning: this will likely change significantly, currently +;;; I am only implementing a mock-up before I get the networking part working.) +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 21/05/2015 +;;; + +(let ((player NIL)) + (defun play-game (player-name) + "The main game loop" + (clear-screen) + ;; Initialize the player if necessary + (when (null player) + (setf player (get-game-object 'player player-name))) + (when (null player) + (setf player (create-player player-name)) + (when (null (list-world-objects 'player)) + (setf (player-game-admin player) T)) + (add-game-object player) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player))) + ;; The actual game loop + (clear-screen) + (let ((place (get-game-object 'place (player-place player)))) + (describe-place place) + (input-string command) + (while (not (or (equalp command "quit") (equalp command "exit"))) + (game-command command player) + (input-string command)) + (format t "~&Goodbye!")))) + +(defun create-player (player-name) + "The user creates a new player" + ;; XXX This function feels somewhat ugly - any possibility of a cleanup? + (let ((player (make-player :name player-name + :place (world-starting-place *world*))) + (char-attr + '((strength 0) (dexterity 0) + (constitution 0) (intelligence 0))) + (items NIL) (weapon "") + (character-points NIL)) + (format t "~&The name you have chosen is not registered on this game.") + (unless (y-or-n-p "~&Create a new player?") (start-menu)) + ;; Chose race and class + (format t "~&Please chose a race:") + (setf (player-race player) (choose-option (list-world-objects 'race))) + (format t "~&Please chose a class:") + (setf (player-class player) + (choose-option (list-world-objects 'character-class))) + (dolist (i (character-class-special-item + (get-game-object 'character-class (player-class player)))) + (set-object-attribute player 'item i)) + ;; Set character attributes + (while (or (< (reduce #'+ character-points) 24) ; XXX magic number! + (not (set-p character-points))) + (set-list (1+ (random 20)) a b c d) + (setf character-points (list a b c d))) + (setf text " +Now distribute your attribute points. Random numbers have been chosen, +you may assign one number to each of the following attributes:") + (format t "~&~A~%~A~%~%The numbers are:" + text (string-from-list (keys char-attr))) + ;; TODO I should replace simple-input with something offering 'magic' + (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) + (safe-nth i (keys char-attr))) + (val (cassoc attr char-attr) (cassoc attr char-attr))) + ((= i (length char-attr)) player) + (format t "~&~A" (string-from-list character-points)) + (simple-input val (concatenate 'string (symbol-name attr) ":")) + (while (not (member val character-points)) + (format t "~&Sorry, invalid number chosen. Please reenter:") + (simple-input val (concatenate 'string (symbol-name attr) ":"))) + (let ((player-fn (build-symbol "player-" attr))) + ;; XXX Kludge ?! + (eval `(setf (,player-fn ,player) ,val))) + (setf character-points + (remove-if #'(lambda (x) (= x val)) character-points))))) + +(defun describe-place (p) + "Print out a complete description of place p" + (when (stringp p) (setf p (get-game-object 'place p))) + (format t "~&~A" (string-upcase (place-name p))) + (format t "~&~%~A" (place-description p)) + (format t "~&~%Neighbouring places: ~A" + (string-from-list (place-neighbour p))) + (format t "~&Players present: ~A" (string-from-list (place-player p))) + (format t "~&Items: ~A" (string-from-list (place-item p))) + (format t "~&NPCs: ~A" (string-from-list (place-npc p))) + (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) + +(defun game-command (cmd player) + "Execute a typed-in game command" + (let* ((command (read-from-string cmd)) + (space (position #\Space cmd)) + (arg (if space (second (cut-string cmd (1+ space))) NIL))) + (if (member command *commands*) + (if space (funcall command player arg) + (funcall command player)) + (progn (format t "~&Sorry, this command does not exist!") + (format t "~&Type 'help' for a list of commands."))))) + + +;;; +;;; Here follow the functions that define the in-game commands. +;;; + + +;; A list of all in-game commands. Each new command must be registered here. +(defvar *commands* + '(help place player + goto pickup drop talk + equip fight shoot + about save clear)) + +;;; The following commands don't take any arguments except for a player + +(defun help (player) + "Print out a list of in-game commands" + (setf help-text " +Commands: +help - Show this list of game commands +quit/exit - Exit the game +clear - Clear the screen +place - Describe the current location +player - Describe your player +goto - Go to a neighbouring location +about - Show a description of this entity +talk - Talk to an NPC +pickup - Pick up an item lying around +drop - Drop the item +equip - Equip this item as your weapon +shoot - Take a shot at a monster +fight - Fight a monster +save - Save the game to file") + (format t "~A" help-text)) + +(defun clear (player) + "Clear the screen (wrapper function)" + (clear-screen) + (place player)) + +;; XXX Will the following two functions give problems? (Their name is +;; identical with the struct name) Probably not, but best to be aware. +(defun place (player) + "Describe the player's current location (wrapper function)" + (describe-place (player-place player))) + +(defun player (p) + "Print a description of this player" + (let ((tab (string #\tab))) + (when (stringp p) (setf p (get-game-object 'player p))) + (format t "~&Player ~A:" (player-name p)) + (format t "~&~%Current place: ~A" (player-place p)) + (format t "~&Race: ~A~AClass: ~A" (player-race p) tab (player-class p)) + (format t "~&=====") + (format t "~&Attributes:") + (format t "~&Intelligence: ~A~AStrength: ~A" + (player-intelligence p) tab (player-strength p)) + (format t "~&Constitution: ~A~ADexterity: ~A" + (player-constitution p) tab (player-dexterity p)) + (format t "~&=====") + (format t "~&Weapon: ~A" (player-weapon p)) + (format t "~&Items: ~A" (string-from-list (player-item p))) + (format t "~&=====") + (format t "~&Max health: ~A~ACurrent health: ~A" + (player-max-health p) tab (player-health p)) + (format t "~&Experience: ~A" (player-experience p)))) + +;;; These next functions have to take two arguments (the argument +;;; to the function and a player instance). + +(let ((last-save NIL)) + (defun save (player &optional game-file) + "Save a game to file (wrapper method around save-world)" + ;; XXX Include a permissions check (only allow admins to save)? + ;; Could give problems in single-player mode. + (cond (game-file (setf last-save game-file)) + ((and last-save (not game-file)) (setf game-file last-save)) + ((not (or last-save game-file)) + (format t "~&Where do you want to save the game?") + (input-string game-file))) + (when (y-or-n-p "Save game to ~A?" game-file) + (save-world game-file) + (format t "~&Game saved.")))) + +(defun goto (player &optional location) + "Go to the specified location" + (unless location + (format t "~&Please specify a location!") + (return-from goto)) + (when (symbolp location) (setf location (symbol-name location))) + (when (not (member location + (place-neighbour (get-game-object 'place + (player-place player))) + :test #'equalp)) + (format t "~&This place does not border your current location!") + (return-from goto)) + (clear-screen) + (debugging "~&~A is going to ~A." (player-name player) location) + (remove-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (set-object-attribute player 'place location) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (describe-place location)) + +(defun about (player &optional object-name) + "Print a description of this object" + (unless object-name + (format t "~&Please specify the object you wish to inspect!") + (return-from about)) + ;; TODO What about objects that the player is carrying? + ;; And there's probably a more elegant way of doing this... + (let ((place (get-game-object 'place (player-place player))) + (description NIL)) + (macrolet ((set-descr (type) + (let ((place-descr (build-symbol type "-description")) + (place-object (build-symbol "place-" type))) + `(when (member object-name (,place-object place) + :test #'equalp) + (setf description (,place-descr + (get-game-object ',type + object-name))))))) + (set-descr item) + (set-descr monster) + (set-descr npc)) + (if description + (format t "~&(~A) ~A" object-name description) + (format t "~&Could not find ~A!" object-name)))) + +(defun talk (player &optional npc-name) + "Talk to the desired NPC" + ;; TODO Add interactive facility + (unless npc-name + (format t "~&Please specify an NPC to talk to!") + (return-from talk)) + (let* ((place (get-game-object 'place (player-place player))) + (npc (when (member npc-name (place-npc place) :test #'equalp) + (get-game-object 'npc npc-name)))) + (if npc + (format t "~&~A: ~A" (string-upcase npc-name) (npc-says npc)) + (format t "~&~A is not here!" npc-name)))) + +(defun pickup (player &optional item-name) + "The player picks up an item" + (unless item-name + (format t "~&Please specify an item to pick up!") + (return-from pickup)) + (let ((place (get-game-object 'place (player-place player))) + (item (get-game-object 'item item-name))) + (if (member item-name (place-item place) :test #'equalp) + (progn + (set-object-attribute player 'item item-name) + (when (item-function item) + (funcall (item-function item))) + (remove-object-attribute place 'item item-name) + (format t "~&You have picked up: ~A" item-name)) + (format t "~&Sorry, this item is not here!")))) + +(defun drop (player &optional item) + "The player drops the specified item" + (unless item + (format t "~&Please specify an item to drop!") + (return-from drop)) + (if (member item (player-item player) :test #'equalp) + (progn + (remove-object-attribute player 'item item) + (when (equalp (item-weapon (get-game-object 'item item)) "yes") + (set-object-attribute player 'weapon "")) + (set-object-attribute + (get-game-object 'place (player-place player)) 'item item) + (format t "~&You have dropped: ~A" item)) + (format t "~&You do not possess this item!"))) + +(defun equip (player &optional new-weapon) + "The player sets another item to be his weapon" + (when (or (not new-weapon) (equalp new-weapon "none")) + (setf (player-weapon player) "") + (format t "~&You no longer have any weapon equipped.") + (return-from weapon)) + (if (and (member new-weapon (player-item player) :test #'equalp) + (equalp (item-weapon (get-game-object 'item new-weapon)) "yes")) + (progn + (setf (player-weapon player) new-weapon) + (format t "~&You have equipped: ~A" new-weapon)) + (format t "~&Sorry, this item is not available as a weapon!"))) + +(defun fight (player &optional opponent) + "The player enters combat" + (unless opponent + (format t "~&Please specify an opponent!") + (return-from fight)) + ;; TODO + ) diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/player.lisp b/lisp/player.lisp index c989c22..66328bf 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -12,17 +12,19 @@ (defstruct player (name "") - (race NIL) - (class NIL) + (race "") + (class "") (strength 0) (dexterity 0) (constitution 0) (intelligence 0) (item NIL) - (weapon NIL) + (weapon "") (place "") (experience 0) - (health 0)) + (max-health 50) + (health 50) + (game-admin NIL)) (defstruct race @@ -41,6 +43,8 @@ (special-ability NIL)) +;; This function is probably superfluous, as the player struct should only store +;; names of game objects (the actual objects are stored in *world*) (let ((list-function (make-list-function 'player NIL))) (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." diff --git a/lisp/ui.lisp b/lisp/ui.lisp new file mode 100644 index 0000000..f8cb7cf --- /dev/null +++ b/lisp/ui.lisp @@ -0,0 +1,300 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The client module is responsible for the actual user interface presented +;;; to a player. (Warning: this will likely change significantly, currently +;;; I am only implementing a mock-up before I get the networking part working.) +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 21/05/2015 +;;; + +(let ((player NIL)) + (defun play-game (player-name) + "The main game loop" + (clear-screen) + ;; Initialize the player if necessary + (when (null player) + (setf player (get-game-object 'player player-name))) + (when (null player) + (setf player (create-player player-name)) + (when (null (list-world-objects 'player)) + (setf (player-game-admin player) T)) + (add-game-object player) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player))) + ;; The actual game loop + (clear-screen) + (let ((place (get-game-object 'place (player-place player)))) + (describe-place place) + (input-string command) + (while (not (or (equalp command "quit") (equalp command "exit"))) + (game-command command player) + (input-string command)) + (format t "~&Goodbye!")))) + +(defun create-player (player-name) + "The user creates a new player" + ;; XXX This function feels somewhat ugly - any possibility of a cleanup? + (let ((player (make-player :name player-name + :place (world-starting-place *world*))) + (char-attr + '((strength 0) (dexterity 0) + (constitution 0) (intelligence 0))) + (items NIL) (weapon "") + (character-points NIL)) + (format t "~&The name you have chosen is not registered on this game.") + (unless (y-or-n-p "~&Create a new player?") (start-menu)) + ;; Chose race and class + (format t "~&Please chose a race:") + (setf (player-race player) (choose-option (list-world-objects 'race))) + (format t "~&Please chose a class:") + (setf (player-class player) + (choose-option (list-world-objects 'character-class))) + (dolist (i (character-class-special-item + (get-game-object 'character-class (player-class player)))) + (set-object-attribute player 'item i)) + ;; Set character attributes + (while (or (< (reduce #'+ character-points) 24) ; XXX magic number! + (not (set-p character-points))) + (set-list (1+ (random 20)) a b c d) + (setf character-points (list a b c d))) + (setf text " +Now distribute your attribute points. Random numbers have been chosen, +you may assign one number to each of the following attributes:") + (format t "~&~A~%~A~%~%The numbers are:" + text (string-from-list (keys char-attr))) + ;; TODO I should replace simple-input with something offering 'magic' + (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) + (safe-nth i (keys char-attr))) + (val (cassoc attr char-attr) (cassoc attr char-attr))) + ((= i (length char-attr)) player) + (format t "~&~A" (string-from-list character-points)) + (simple-input val (concatenate 'string (symbol-name attr) ":")) + (while (not (member val character-points)) + (format t "~&Sorry, invalid number chosen. Please reenter:") + (simple-input val (concatenate 'string (symbol-name attr) ":"))) + (let ((player-fn (build-symbol "player-" attr))) + ;; XXX Kludge ?! + (eval `(setf (,player-fn ,player) ,val))) + (setf character-points + (remove-if #'(lambda (x) (= x val)) character-points))))) + +(defun describe-place (p) + "Print out a complete description of place p" + (when (stringp p) (setf p (get-game-object 'place p))) + (format t "~&~A" (string-upcase (place-name p))) + (format t "~&~%~A" (place-description p)) + (format t "~&~%Neighbouring places: ~A" + (string-from-list (place-neighbour p))) + (format t "~&Players present: ~A" (string-from-list (place-player p))) + (format t "~&Items: ~A" (string-from-list (place-item p))) + (format t "~&NPCs: ~A" (string-from-list (place-npc p))) + (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) + +(defun game-command (cmd player) + "Execute a typed-in game command" + (let* ((command (read-from-string cmd)) + (space (position #\Space cmd)) + (arg (if space (second (cut-string cmd (1+ space))) NIL))) + (if (member command *commands*) + (if space (funcall command player arg) + (funcall command player)) + (progn (format t "~&Sorry, this command does not exist!") + (format t "~&Type 'help' for a list of commands."))))) + + +;;; +;;; Here follow the functions that define the in-game commands. +;;; + + +;; A list of all in-game commands. Each new command must be registered here. +(defvar *commands* + '(help place player + goto pickup drop talk + equip fight shoot + about save clear)) + +;;; The following commands don't take any arguments except for a player + +(defun help (player) + "Print out a list of in-game commands" + (setf help-text " +Commands: +help - Show this list of game commands +quit/exit - Exit the game +clear - Clear the screen +place - Describe the current location +player - Describe your player +goto - Go to a neighbouring location +about - Show a description of this entity +talk - Talk to an NPC +pickup - Pick up an item lying around +drop - Drop the item +equip - Equip this item as your weapon +shoot - Take a shot at a monster +fight - Fight a monster +save - Save the game to file") + (format t "~A" help-text)) + +(defun clear (player) + "Clear the screen (wrapper function)" + (clear-screen) + (place player)) + +;; XXX Will the following two functions give problems? (Their name is +;; identical with the struct name) Probably not, but best to be aware. +(defun place (player) + "Describe the player's current location (wrapper function)" + (describe-place (player-place player))) + +(defun player (p) + "Print a description of this player" + (let ((tab (string #\tab))) + (when (stringp p) (setf p (get-game-object 'player p))) + (format t "~&Player ~A:" (player-name p)) + (format t "~&~%Current place: ~A" (player-place p)) + (format t "~&Race: ~A~AClass: ~A" (player-race p) tab (player-class p)) + (format t "~&=====") + (format t "~&Attributes:") + (format t "~&Intelligence: ~A~AStrength: ~A" + (player-intelligence p) tab (player-strength p)) + (format t "~&Constitution: ~A~ADexterity: ~A" + (player-constitution p) tab (player-dexterity p)) + (format t "~&=====") + (format t "~&Weapon: ~A" (player-weapon p)) + (format t "~&Items: ~A" (string-from-list (player-item p))) + (format t "~&=====") + (format t "~&Max health: ~A~ACurrent health: ~A" + (player-max-health p) tab (player-health p)) + (format t "~&Experience: ~A" (player-experience p)))) + +;;; These next functions have to take two arguments (the argument +;;; to the function and a player instance). + +(let ((last-save NIL)) + (defun save (player &optional game-file) + "Save a game to file (wrapper method around save-world)" + ;; XXX Include a permissions check (only allow admins to save)? + ;; Could give problems in single-player mode. + (cond (game-file (setf last-save game-file)) + ((and last-save (not game-file)) (setf game-file last-save)) + ((not (or last-save game-file)) + (format t "~&Where do you want to save the game?") + (input-string game-file))) + (when (y-or-n-p "Save game to ~A?" game-file) + (save-world game-file) + (format t "~&Game saved.")))) + +(defun goto (player &optional location) + "Go to the specified location" + (unless location + (format t "~&Please specify a location!") + (return-from goto)) + (when (symbolp location) (setf location (symbol-name location))) + (when (not (member location + (place-neighbour (get-game-object 'place + (player-place player))) + :test #'equalp)) + (format t "~&This place does not border your current location!") + (return-from goto)) + (clear-screen) + (debugging "~&~A is going to ~A." (player-name player) location) + (remove-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (set-object-attribute player 'place location) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (describe-place location)) + +(defun about (player &optional object-name) + "Print a description of this object" + (unless object-name + (format t "~&Please specify the object you wish to inspect!") + (return-from about)) + ;; TODO What about objects that the player is carrying? + ;; And there's probably a more elegant way of doing this... + (let ((place (get-game-object 'place (player-place player))) + (description NIL)) + (macrolet ((set-descr (type) + (let ((place-descr (build-symbol type "-description")) + (place-object (build-symbol "place-" type))) + `(when (member object-name (,place-object place) + :test #'equalp) + (setf description (,place-descr + (get-game-object ',type + object-name))))))) + (set-descr item) + (set-descr monster) + (set-descr npc)) + (if description + (format t "~&(~A) ~A" object-name description) + (format t "~&Could not find ~A!" object-name)))) + +(defun talk (player &optional npc-name) + "Talk to the desired NPC" + ;; TODO Add interactive facility + (unless npc-name + (format t "~&Please specify an NPC to talk to!") + (return-from talk)) + (let* ((place (get-game-object 'place (player-place player))) + (npc (when (member npc-name (place-npc place) :test #'equalp) + (get-game-object 'npc npc-name)))) + (if npc + (format t "~&~A: ~A" (string-upcase npc-name) (npc-says npc)) + (format t "~&~A is not here!" npc-name)))) + +(defun pickup (player &optional item-name) + "The player picks up an item" + (unless item-name + (format t "~&Please specify an item to pick up!") + (return-from pickup)) + (let ((place (get-game-object 'place (player-place player))) + (item (get-game-object 'item item-name))) + (if (member item-name (place-item place) :test #'equalp) + (progn + (set-object-attribute player 'item item-name) + (when (item-function item) + (funcall (item-function item))) + (remove-object-attribute place 'item item-name) + (format t "~&You have picked up: ~A" item-name)) + (format t "~&Sorry, this item is not here!")))) + +(defun drop (player &optional item) + "The player drops the specified item" + (unless item + (format t "~&Please specify an item to drop!") + (return-from drop)) + (if (member item (player-item player) :test #'equalp) + (progn + (remove-object-attribute player 'item item) + (when (equalp (item-weapon (get-game-object 'item item)) "yes") + (set-object-attribute player 'weapon "")) + (set-object-attribute + (get-game-object 'place (player-place player)) 'item item) + (format t "~&You have dropped: ~A" item)) + (format t "~&You do not possess this item!"))) + +(defun equip (player &optional new-weapon) + "The player sets another item to be his weapon" + (when (or (not new-weapon) (equalp new-weapon "none")) + (setf (player-weapon player) "") + (format t "~&You no longer have any weapon equipped.") + (return-from weapon)) + (if (and (member new-weapon (player-item player) :test #'equalp) + (equalp (item-weapon (get-game-object 'item new-weapon)) "yes")) + (progn + (setf (player-weapon player) new-weapon) + (format t "~&You have equipped: ~A" new-weapon)) + (format t "~&Sorry, this item is not available as a weapon!"))) + +(defun fight (player &optional opponent) + "The player enters combat" + (unless opponent + (format t "~&Please specify an opponent!") + (return-from fight)) + ;; TODO + ) diff --git a/lisp/util.lisp b/lisp/util.lisp index eeeb46d..4437124 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -20,6 +20,10 @@ syms) ,@body)) +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + ;; TODO DEPRECATED - Needs to be replaced in the current code (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -27,12 +31,14 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) +;; XXX Very useful for debugging, but represents a major security hole +;; when used in a network setting (defmacro magic (var) "Execute typed-in Lisp code" `(when (eq ,var 'magic) (repl))) -; potentially inefficient if called often +;; XXX potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" (do* ((expr (list 'setf)) (vl var-list (cdr vl)) (var (car vl) (car vl))) @@ -94,7 +100,6 @@ ; Some of these functions are probably quite inefficient (lots of consing) - ;; XXX DEPRECATED Not actually needed anywhere (defun call-function (function-name &rest args) "Save myself some quoting when calling a function from a generated symbol" @@ -115,6 +120,15 @@ (T (concatenate 'string (to-string (first lst)) (to-string separator) (string-from-list (cdr lst) separator))))) +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + (defun cut-string (s i) "Cut string s in two at index i and return the two substrings in a list" (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) @@ -144,6 +158,12 @@ (when (funcall test search-term (elt search-sequence i)) (incf count))))) +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + (defun to-list (vector &optional (next-elt 0)) "Turn the vector into a list" (if (= next-elt (1- (length vector))) NIL @@ -157,6 +177,11 @@ (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + (defun build-symbol (&rest components) "Concatenate the passed components into a single symbol" (read-from-string (string-from-list components ""))) @@ -174,6 +199,29 @@ (setf name-list (cons (funcall get-object-name object) name-list)))))) +(defun choose-number-option (option-list) + "The user chooses one out of a list of options, the index is returned" + (dotimes (i (length option-list)) + (format t "~&~S) ~A" (1+ i) (nth i option-list))) + (simple-input choice) + (while (or (not (numberp choice)) (< choice 1) + (> choice (length option-list))) + (format t "~&Invalid choice! Please choose again:") + (simple-input choice)) + (1- choice)) + +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +(defun clear-screen () + "Clear the screen in an OS-dependent manner" + ;; NOTE: only works with CLISP! (ext:shell function used) + (cond ((member ':unix *features*) (ext:shell "clear")) + ((member ':win32 *features*) (ext:shell "cls")) + (t (debugging "~&clear-screen is not supported on this operating system!")))) + (defun repl () "Launch a read-eval-print loop" (let ((expr (simple-input expr "lisp >"))) diff --git a/ATL/ATL-item-conversion.ods b/ATL/ATL-item-conversion.ods new file mode 100644 index 0000000..a5442c2 --- /dev/null +++ b/ATL/ATL-item-conversion.ods Binary files differ diff --git a/ATL/atl-mode.el b/ATL/atl-mode.el index 8d2dcfd..efab9cf 100644 --- a/ATL/atl-mode.el +++ b/ATL/atl-mode.el @@ -13,7 +13,7 @@ ;; define commands '("define-place" "define-item" "define-monster" "define-npc" "define-race" "define-place" "define-weapon" - "name-world" "load-file" "start-place") + "define-class" "name-world" "load-file" "start-place") '() ;; other commands (adjust this?) '("\\.atl$") ;; files for which to activate this mode '(linum-mode) ;; other functions to call diff --git a/ATL/creator-test.atl b/ATL/creator-test.atl new file mode 100644 index 0000000..fa40b88 --- /dev/null +++ b/ATL/creator-test.atl @@ -0,0 +1,185 @@ +;; This code has been automatically generated by the +;; Atlantis world creator. + +define-item Rope (20m) + cost 5 + +define-item Backpack + cost 5 + +define-item Knife + cost 10 + +define-item Short sword + cost 40 + +define-item Long sword + cost 80 + +define-item Dagger + cost 20 + +define-item Spear + cost 25 + +define-item Short bow + cost 30 + +define-item Longbow + cost 60 + +define-item Slingshot + cost 15 + +define-item Throwing knife + cost 15 + +define-item Quiver of arrows + cost 20 + +define-item Leather armour + cost 60 + +define-item Chain mail + cost 120 + +define-item Plate armour + cost 200 + +define-item Buckler + cost 35 + +define-item Full shield + cost 45 + +define-item Candles (3x) + cost 5 + +define-item Matches + cost 1 + +define-item Food rations (5x) + cost 10 + +define-item Grappling hook + cost 15 + +define-item Fishing net + cost 25 + +define-item Fishing rod + cost 15 + +define-item Tent (2 people) + cost 30 + +define-item Cloak + cost 15 + +define-item Hat + cost 10 + +define-item Water bottle + cost 5 + +define-item Compass + cost 20 + +define-item Quill and ink + cost 5 + +define-item Parchment + cost 5 + +define-item Blanket + cost 5 + +define-item Book of Legends + cost 55 + +define-item Book of Lore + cost 55 + +define-item Medicinal herbs + cost 10 + +define-item Bandages (3x) + cost 5 + +define-item Tea leaves + cost 5 + +define-item Sleeping potion + cost 15 + +define-item Leather pouch + cost 2 + +define-item Face mask + cost 10 + +define-item Dye + cost 5 + +define-item Cloth + cost 5 + +define-item Scissors + cost 15 + +define-item Needle and thread + cost 3 + +define-item Canvas + cost 8 + +define-item String + cost 2 + +define-item Hatchet + cost 20 + +define-item Map + cost 35 + +define-item Pony + cost 100 + +define-item Horse + cost 180 + +define-item Saddle and bridle + cost 45 + +define-item Saddlebag + cost 35 + +define-item Handmirror + cost 20 + +define-item Magnifying glass + cost 20 + +define-item Telescope + cost 40 + +define-item Rope ladder + cost 15 + +define-item Torches (3x) + cost 10 + +define-item Staff + cost 10 + +define-item Cooking pot + cost 8 + +define-item Soap + cost 2 + +define-item Towel + cost 5 + +define-item Boots + cost 10 \ No newline at end of file diff --git a/ATL/game-objects.atl b/ATL/game-objects.atl index 2bc47fa..a860e54 100644 --- a/ATL/game-objects.atl +++ b/ATL/game-objects.atl @@ -12,4 +12,20 @@ define-weapon "fire-whip" description "A 10-foot long whip, blazing with magical fire" - damage 2 \ No newline at end of file + damage 2 + +define-item "Anaklusmos" + description "Riptide, a sword for heroes!" + weapon "yes" + +define-weapon "Anaklusmos" + description "Riptide, a sword for heroes!" + damage 4 + +define-npc "Hades" + description "Hades, Lord of the Dead!" + says "Beware, mortal - do not tempt me!" + +define-npc "Charon" + description "A robe-clad ghoul, dread ferryman of the Styx." + says "..." \ No newline at end of file diff --git a/ATL/lisp-test.atl b/ATL/lisp-test.atl index e8e0faf..5687b44 100644 --- a/ATL/lisp-test.atl +++ b/ATL/lisp-test.atl @@ -1,4 +1,4 @@ -; This is a simple test ATL file to test whatever I have implemented so far. +; This is a simple ATL file to test whatever I have implemented so far. ; @author Daniel Vedder ; @date 04/05/2015 @@ -8,21 +8,39 @@ description "Welcome to Nowhere! You are in the void, the space between the worlds. Around you is black. Black, except for one tiny pin-prick of light to the north." - neighbour "Elysium" + neighbour "Styx" define-place "Elysium" description "This is where you want to be when you are six feet under..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Fields of Punishment" + neighbour "Fields of Asphodel" define-place "Fields of Punishment" description "Precisely where you do NOT want to end up..." - neighbour "Nowhere" + neighbour "Styx" neighbour "Elysium" + neighbour "Fields of Asphodel" monster "Fury" +define-place "Fields of Asphodel" + description "Nothing special. Really, nothing special at all. +Just a whole load of dead people..." + neighbour "Fields of Punishment" + neighbour "Elysium" + npc "Hades" + +define-place "Styx" + description "The great river that all must cross - but woe to those who do!" + neighbour "Fields of Punishment" + neighbour "Elysium" + neighbour "Nowhere" + item "Anaklusmos" + npc "Charon" + load-file lisp-test.atl ;Testing whether recursive loading is prevented load-file races-classes.atl load-file game-objects.atl +load-file creator-test.atl -start-place "Nowhere" \ No newline at end of file +start-place "Styx" \ No newline at end of file diff --git a/ATL/test-extension.lisp b/ATL/test-extension.lisp index 3f261b4..f01660a 100644 --- a/ATL/test-extension.lisp +++ b/ATL/test-extension.lisp @@ -2,4 +2,4 @@ ;; author: Daniel Vedder ;; date: 21/05/2015 -(format t "~&Loaded Lisp extension file.") +(debugging "~&Loaded Lisp extension file.") diff --git a/lisp/atlantis.lisp b/lisp/atlantis.lisp index eeca782..2d248a4 100644 --- a/lisp/atlantis.lisp +++ b/lisp/atlantis.lisp @@ -7,40 +7,49 @@ ;;; date: 09/05/2015 ;;; -(defconstant ATLANTIS-VERSION '(0 1 1)) +(defconstant ATLANTIS-VERSION '(0 1 3)) (load "util.lisp") -(load "networking.lisp") (load "game-objects.lisp") (load "player.lisp") (load "world.lisp") (load "interpreter.lisp") -(load "client.lisp") +(load "ui.lisp") +(load "creator.lisp") +(defvar *debugging* NIL) (defun development () "A method to easily test whatever feature I am currently developing" + (setf *debugging* T) (load-file "../ATL/lisp-test.atl") (let ((player (make-player :name "Bilbo" - :race (get-game-object 'race "Hobbit") - :class (get-game-object 'character-class "Burglar") + :race "Hobbit" :class "Burglar" :place (world-starting-place *world*) :strength 6 :constitution 12 - :dexterity 19 :intelligence 14))) + :dexterity 19 :intelligence 14 + :game-admin T))) (add-game-object player) (set-object-attribute (get-game-object 'place (player-place player)) 'player (player-name player)) - (setf (world-game-manager *world*) (player-name player)) (play-game (player-name player)))) +(defun not-available () + "Before I tackle networking..." + (format t "~&Sorry, multiplayer is currently not supported!") + (format t "~&Please press ENTER") + (y-or-n-p "~&OK?") + (start-menu)) + (defun start-server () "Start a new game on a server" + ;; TODO Doesn't actually start a server yet (format t "~&What world file do you want to load?") (input-string world-file) - (format t "~&What port should the game run on?") + (format t "~&What port should the game run on?") (while (not (numberp (input port))) (format t "~&Not a number: ~A. Please reenter:" port)) - (format t "~&Loading file ~S on port ~A" world-file port) + (debugging "~&Loading file ~S on port ~A" world-file port) (load-file world-file)) (defun join-game () @@ -50,19 +59,38 @@ (while (not (= (count-instances #\. server-ip) 3)) (format t "~&Not an IP address: ~A. Please reenter:" server-ip) (input-string server-ip)) - (setf (cassoc ip *server-address*) server-ip) + ;(setf (cassoc ip *server-address*) server-ip) (format t "~&What port does the game run on?") (while (not (numberp (input server-port))) (format t "~&Not a number: ~A. Please reenter:" server-port)) - (setf (cassoc port *server-address*) server-port) + ;(setf (cassoc port *server-address*) server-port) (format t "~&What is your player name?") (input-string name) - (format t "~&Joining game on ~A:~A as ~A" server-ip server-port name) + (debugging "~&Joining game on ~A:~A as ~A" server-ip server-port name) (play-game name)) +(defun single-player () + "Start a single-player game" + (format t "~&What do you want to do?") + (setf options '("Start a new game" "Load a game" "Back to menu")) + (case (choose-number-option options) + (0 (format t "~&What world file do you want to load?") + (input-string world-file) + (format t "~&What is your name?") + (input-string name) + (load-file world-file) + (play-game name)) + (1 (format t "~&What game file do you want to load?") + (input-string game) + (format t "~&What is your name?") + (input-string name) + (load-game game) + (play-game name)) + (2 (start-menu)))) + (defun print-version () - (format t "~&Lisp Atlantis ~A.~A.~A" + (format t "~&Atlantis ~A.~A.~A" (first ATLANTIS-VERSION) (second ATLANTIS-VERSION) (third ATLANTIS-VERSION)) @@ -71,27 +99,24 @@ (defun start-menu () "Show the start menu and take a choice from the user" - (dolist (line (load-text-file "banner.txt")) - (unless (null line) (format t "~%~A" line))) + (clear-screen) + (print-text-file "banner.txt") (format t "~&~%Welcome! What do you want to do?") - (format t "~&-> (S)tart a server") - (format t "~&-> (J)oin a game") - (format t "~&-> (A)bout") - (format t "~&-> (E)xit") - (format t "~&-> (D)evelop") ;XXX Remove later - (input choice) - (case choice - ('s (start-server)) - ('j (join-game)) - ('a (print-version) - (when (y-or-n-p "~%Show the license text?") - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line)))) + (setf options '("Start a server" "Join a game" "Play single-player" + "Create worlds" "Develop" "About" "Exit")) + (case (choose-number-option options) + (0 (not-available)) + (1 (not-available)) + (2 (single-player)) + (3 (world-creator)) + (4 (development)) + (5 (print-version) + (when (y-or-n-p "Show the license text?") + (print-text-file "../LICENSE")) (start-menu)) - ('e (format t "~&Goodbye!") (quit)) - ('d (development)) - (t (format t "~&Invalid choice!") (start-menu)))) - + (6 (format t "~&Goodbye!") + (quit)))) + (defun cmd-parameter (name &optional truth-value) "Return the value of the parameter 'name'. Or T for present if truth-value." (let ((argument (member name *args* :test #'equalp))) @@ -101,32 +126,38 @@ (defun print-help () (print-version) - (format t "~&~%Commandline options:") - (let ((tab (string #\Tab))) - (format t "~&-v --version~AShow the version number and exit" tab) - (format t "~&-h --help~AShow this help text and exit" tab) - (format t "~&--license~AShow the license text" tab) - (format t "~&--server ~AStart a server on (requires --world)" tab) - (format t "~&--world ~AThe ATL file to load (requires --server)" tab) - (format t "~&--client :~AConnect to the game server at :" tab))) + (setf help-text " +Commandline options: +-v --version Show the version number and exit +-h --help Show this help text and exit +--license Show the license text +--debugging Switch on debug mode +--single-player Start a single-player game +--server Start a server on (requires --world) +--world The ATL file to load (requires --server) +--client : Connect to the game server at :") + (format t "~A" help-text)) (defun parse-commandline-args () ;; TODO clean this up? (should give error message with unknown params) - (when (or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) + (cond ((or (cmd-parameter "--version" T) (cmd-parameter "-v" T)) (print-version) (quit)) - (when (or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) - (print-help) (quit)) - (when (cmd-parameter "--license" T) - (dolist (line (load-text-file "../LICENSE")) - (unless (null line) (format t "~%~A" line))) - (quit)) + ((or (cmd-parameter "--help" T) (cmd-parameter "-h" T)) + (print-help) (quit)) + ((cmd-parameter "--license" T) + (dolist (line (load-text-file "../LICENSE")) + (unless (null line) (format t "~%~A" line))) + (quit)) + ((cmd-parameter "--debugging") + (setf *debugging* T)) + ((cmd-parameter "--single-player" T) + (single-player))) (let ((server (cmd-parameter "--server")) (world-file (cmd-parameter "--world")) (client (cmd-parameter "--client"))) (unless (or server world-file client) (format t "~&Invalid commandline parameter!") (quit)) - ;; TODO change OR to AND, change function calls - (if (or world-file server) + (if (and world-file server) (load-file world-file) (join-game)))) diff --git a/lisp/client.lisp b/lisp/client.lisp deleted file mode 100644 index e51c597..0000000 --- a/lisp/client.lisp +++ /dev/null @@ -1,188 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; The client module is responsible for the actual user interface presented -;;; to a player. (Warning: this will likely change significantly, currently -;;; I am only implementing a mock-up before I get the networking part working.) -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 21/05/2015 -;;; - -(let ((player NIL)) - (defun play-game (player-name) - "The main game loop" - ;(update-world) - ;; Initialize the player if necessary - (when (null player) - (setf player (get-game-object 'player player-name))) - (when (null player) - (setf player (create-player player-name)) - (when (null (list-world-objects 'player)) - (setf (world-game-manager *world*) (player-name player))) - (add-game-object player) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player))) - ;; The actual game loop - (let ((place (get-game-object 'place (player-place player)))) - (describe-place place) - (input-string command) - (while (not (or (equalp command "quit") (equalp command "exit"))) - (game-command command player) - ;(server-send (game-command command player)) - ;(update-world) - (input-string command)) - (format t "~&Goodbye!")))) - -(defun create-player (player-name) - "The user creates a new player" - ;; XXX This function feels somewhat inelegant - lot's of repetetive stuff. - ;; Is it worth cleaning up? - (let ((player (make-player :name player-name - :place (world-starting-place *world*))) - (race NIL) (character-class NIL) - (char-attr - '((strength 0) (dexterity 0) - (constitution 0) (intelligence 0))) - (items NIL) (weapon "") - (character-points NIL)) - (format t "~&The name you have chosen is not registered on this game.") - (unless (y-or-n-p "~&Create a new player?") (start-menu)) - ;; Chose race and class - (format t "~&Please chose a race:") - (format t "~&Options: ~A" (string-from-list (list-world-objects 'race))) - (setf race (input-string)) - (while (not (member race (list-world-objects 'race) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf race (input-string))) - (setf (player-race player) (get-game-object 'race race)) - (format t "~&Please chose a class:") - (format t "~&Options: ~A" (string-from-list - (list-world-objects 'character-class))) - (setf character-class (input-string)) - (while (not (member character-class - (list-world-objects 'character-class) :test #'equalp)) - (format t "~&Invalid choice. Please reenter:") - (setf character-class (input-string))) - (setf (player-class player) - (get-game-object 'character-class character-class)) - ;; Set character attributes - (while (< (reduce #'+ character-points) 24) ; Warning: magic number! - (set-list (1+ (random 20)) a b c d) - (setf character-points (list a b c d))) - (setf text " -Now distribute your attribute points. Random numbers have been chosen, -you may assign one number to each of the following attributes:") - (format t "~&~A~%~A~%~%The numbers are:" - text (string-from-list (keys char-attr))) - ;; TODO I should replace simple-input with something offering 'magic' - (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) - (safe-nth i (keys char-attr))) - (val (cassoc attr char-attr) (cassoc attr char-attr))) - ((= i (length char-attr)) player) - (format t "~&~A" (string-from-list character-points)) - (simple-input val (concatenate 'string (symbol-name attr) ":")) - (while (not (member val character-points)) - (format t "~&Sorry, invalid number chosen. Please reenter:") - (simple-input val (concatenate 'string (symbol-name attr) ":"))) - ;; FIXME Gives problems if two equal numbers are in char-points - (let ((player-fn (build-symbol "player-" attr))) - ;; XXX Kludge ?! - (eval `(setf (,player-fn ,player) ,val))) - (setf character-points - (remove-if #'(lambda (x) (= x val)) character-points))))) - -(defun describe-place (p) - "Print out a complete description of place p" - (when (stringp p) (setf p (get-game-object 'place p))) - (format t "~&~%~A" (place-description p)) - (format t "~&Neighbouring places: ~A" (string-from-list (place-neighbour p))) - (format t "~&Players present: ~A" (string-from-list (place-player p))) - (format t "~&Items: ~A" (string-from-list (place-item p))) - (format t "~&NPCs: ~A" (string-from-list (place-npc p))) - (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) - -(defun game-command (cmd player) - "Execute a typed-in game command" - (let* ((command (read-from-string cmd)) - (space (position #\Space cmd)) - (arg (if space (second (cut-string cmd (1+ space))) NIL))) - (if (member command *commands*) - (if space (funcall command player arg) - (funcall command player)) - (progn (format t "~&Sorry, this command does not exist!") - (format t "~&Type 'help' for a list of commands."))))) - - -;;; -;;; Here follow the functions that define the in-game commands. -;;; - - -;; A list of all in-game commands. Each new command must be registered here. -(defvar *commands* - '(help place player goto)) - -;;; The following commands don't take any arguments except for a player - -(defun help (player) - "Print out a list of in-game commands" - ;; TODO Prettify the typesetting (instead of using tabs) - (let ((tab (string #\tab))) - (format t "~&Commands:~%") - (format t "~&help~A-~AShow this list of game commands" tab tab) - (format t "~&quit/exit~A-~AExit the game" tab tab) - (format t "~&place~A-~ADescribe the current location" tab tab) - (format t "~&player~A-~ADescribe your player" tab tab) - (format t "~&goto ~A-~AGo to a neighbouring location" tab tab) - (when (equalp (player-name player) (world-game-manager *world*)) - (format t "~&load ~A-~ALoad a saved game" tab tab) - (format t "~&save ~A-~ASave the game to file" tab tab)))) - -;; XXX Will the following two functions give problems? (Their name is -;; identical with the struct name) Probably not, but best to be aware. -(defun place (player) - "Describe the player's current location" - (describe-place (player-place player))) - -(defun player (p) - "Print a description of this player" - (when (stringp p) (setf p (get-game-object 'player p))) - (format t "~&Player ~A:" (player-name p)) - (format t "~&~%Current place: ~A" (player-place p)) - (format t "~&Race: ~A~AClass: ~A" - (race-name (player-race p)) (string #\Tab) - (character-class-name (player-class p))) - (format t "~&=====") - (format t "~&Attributes:") - (format t "~&Intelligence: ~A~AStrength: ~A" - (player-intelligence p) (string #\Tab) (player-strength p)) - (format t "~&Constitution: ~A~ADexterity: ~A" - (player-constitution p) (string #\Tab) (player-dexterity p)) - (format t "~&=====") - (format t "~&Weapon: ~A" - (if (player-weapon p) (weapon-name (player-weapon p)) "")) - (format t "~&Items: ~A" (string-from-list (list-player-objects 'item p)))) - -;;; These next functions have to take exactly two argument (the argument -;;; to the function and a player instance). - -(defun goto (player location) - "Go to the specified location" - (format t "~&~A is going to ~A." (player-name player) location) - (when (symbolp location) (setf location (symbol-name location))) - (when (not (member location - (place-neighbour (get-game-object 'place - (player-place player))) - :test #'equalp)) - (format t "~&This place does not border your current location!") - (return-from goto NIL)) - (remove-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (set-object-attribute player 'place location) - (set-object-attribute (get-game-object 'place (player-place player)) - 'player (player-name player)) - (describe-place location)) - diff --git a/lisp/creator.lisp b/lisp/creator.lisp new file mode 100644 index 0000000..e0bb854 --- /dev/null +++ b/lisp/creator.lisp @@ -0,0 +1,56 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; This is an ATL code-generating module to ease the world creation +;;; process for non-coders (and those too lazy to write more than +;;; necessary...). +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 20/06/2015 +;;; + +(defun import-spreadsheet (spreadsheet atl-file) + "Import and convert a spreadsheet (requires LibreOffice)" + (let* ((ods-pathname (parse-namestring spreadsheet)) + (csv-pathname (make-pathname + :directory (pathname-directory ods-pathname) + :name (pathname-name ods-pathname) + :type "csv"))) + ;; Convert the spreadsheet to csv (only works with clisp!) + (ext:shell (format nil "libreoffice --headless --convert-to csv ~A~A~A" + (namestring ods-pathname) " --outdir " + (namestring (make-pathname :directory + (pathname-directory ods-pathname))))) + (csv-to-atl csv-pathname atl-file))) + +(defun csv-to-atl (csv-pathname atl-file &aux (atl-code "")) + "Convert a csv file to ATL" + (setf atl-code ";; This code has been automatically generated by the +;; Atlantis world creator.") + (do* ((csv-file (load-text-file csv-pathname)) (line-nr 2 (1+ line-nr)) + (object-type (read-from-string + (first (split-string (first csv-file) #\,)))) + (object-characteristics (split-string (second csv-file) #\,)) + (line (nth line-nr csv-file) (nth line-nr csv-file)) + (line-values (split-string line #\,) (split-string line #\,))) + ((null line) NIL) + ;; Start a new define-command + (setf atl-code (format NIL "~A~&~%~A" atl-code + (concatenate 'string "define-" + (string-downcase (to-string object-type)) + " " (first line-values)))) + ;; Enter the value for each characteristic + (dotimes (i (1- (length object-characteristics))) + (setf atl-code (format NIL "~A~&~A" atl-code + (concatenate 'string (to-string #\tab) + (nth (1+ i) object-characteristics) " " + (nth (1+ i) line-values)))))) + ;; Write the generated code to file + (with-open-file (atl atl-file :direction :output) + (format atl "~A" atl-code))) + +(defun world-creator () + "The UI for the functions in this module" + (format t "~&Sorry, not yet available!")) diff --git a/lisp/game-objects.lisp b/lisp/game-objects.lisp index add4f71..47e400c 100644 --- a/lisp/game-objects.lisp +++ b/lisp/game-objects.lisp @@ -38,7 +38,8 @@ (defstruct item (name "") (description "") - (weapon "") + (cost 0) + (weapon "no") (function NIL)) (defstruct weapon @@ -55,7 +56,7 @@ ;; I'm not sure how elegant it is to call (eval) explicitly, but in this ;; case I couldn't avoid it - I needed a mix between a macro and a function (let ((command (build-symbol (type-of game-object) "-" property))) - ;; XXX This following section is rather ugly... + ;; TODO This following section is rather ugly... (eval `(if (or (null (,command ,game-object)) (listp (,command ,game-object))) (setf (,command ,game-object) @@ -68,11 +69,14 @@ (let ((command (build-symbol (type-of game-object) "-" property))) (eval `(if (listp (,command ,game-object)) ;; FIXME This is going to give problems with multiple values + ;; (but will that scenario ever take place?) (setf (,command ,game-object) (remove-if #'(lambda (x) (equalp x ,value)) (,command ,game-object))) (setf (,command ,game-object) NIL))))) +;; XXX This function is probably superfluous, as all place objects are stored +;; in world, with only their names recorded in the place (let ((list-function (make-list-function 'place NIL))) (defun list-place-objects (object-type place) "Get a list of the names of all the place's objects of this type." diff --git a/lisp/interpreter.lisp b/lisp/interpreter.lisp index b0af3c7..f5d5656 100644 --- a/lisp/interpreter.lisp +++ b/lisp/interpreter.lisp @@ -9,15 +9,21 @@ ;;; date: 09/05/2015 ;;; +;; A list of ATL language constructs +;; (Note: not complete - each (defcommand) appends to this list) +(defvar *atl-commands* + '(load-file start-place name-world)) (defun build-define-command (object-type) "Build a new define command function for the specified object type" #'(lambda (name) - (format t "~&Making ~A ~A" + (debugging "~&Making ~A ~A" (string-downcase (to-string object-type)) name) (funcall (build-symbol "make-" object-type) :name name))) (defmacro defcommand (command-name object-type) + ;; XXX Macros should not have side-effects? + (setf *atl-commands* (cons command-name *atl-commands*)) `(defun ,command-name (name) (funcall ,(build-define-command object-type) name))) @@ -29,9 +35,11 @@ (defcommand define-class character-class) (defcommand define-monster monster) (defcommand define-weapon weapon) +(defcommand define-item item) +(defcommand define-npc npc) (defun start-place (place) - (format t "~&Starting place is ~A" place) + (debugging "~&Starting place is ~A" place) (setf (world-starting-place *world*) place) NIL) @@ -74,13 +82,15 @@ ((not (or (eql (aref line 0) #\;) (eql (aref line 0) #\SPACE) (eql (aref line 0) #\TAB))) - ;; TODO Catch syntax errors - (setf current-object (funcall (symbol-function - (read-from-string line)) - ;; this is a kludge to work around a clisp bug (not - ;; recognizing the :start keyword in read-from-string) - (read-from-string (second - (cut-string line (position #\space line))))))) + (let ((def-cmd (read-from-string line))) + (if (member def-cmd *atl-commands*) + (setf current-object + (funcall def-cmd + ;; clisp doesn't recognize the :start + ;; keyword in read-from-string + (read-from-string (second (cut-string line + (position #\space line)))))) + (error "~&ERROR: unrecognized syntax: '~A'" line)))) ;; interpret an option command ((or (eql (aref line 0) #\Space) (eql (aref line 0) #\Tab)) @@ -88,6 +98,6 @@ (set-object-attribute current-object (read-from-string line) (read-from-string (second (cut-string line (position #\space line)))))) - (T (format t "~&ERROR: unrecognized syntax on line ~A: '~A'" - ;; can't happen - (1+ line-nr) line)))))) + (T ;; can't happen + (error "~&ERROR: unrecognized syntax: '~A'" line)))))) + diff --git a/lisp/networking.lisp b/lisp/networking.lisp deleted file mode 100644 index 4ca6b9e..0000000 --- a/lisp/networking.lisp +++ /dev/null @@ -1,39 +0,0 @@ -;;; -;;; Atlantis is a framework for creating multi-user dungeon worlds. -;;; This is the Common Lisp implementation. -;;; -;;; This module provides the necessary Lisp networking functions. The actual -;;; server will be implemented in C at some later stage. -;;; Currently, this file should be treated as a mockup. -;;; -;;; Licensed under the terms of the MIT license. -;;; author: Daniel Vedder -;;; date: 30/05/2015 -;;; - -(defvar *server-address* - '((ip "127.0.0.1") (port 8888))) - -;; XXX Does this need to be a macro? (so the request doesn't have to be quoted) -(defun send-server-request (ip port request) - "Send a request string to the server" - ;; TODO this is a mockup function - (server-process-request request)) - -(defmacro server-send (request) - "Saves some quoting" - `(send-server-request - (cassoc ip *server-address*) (cassoc port *server-address*) - ',request)) - -(defun server-process-request (request) - "The game server processes a request" - ;; XXX Is simply calling (eval) on the request good enough? - ;; Or should I come up with a proper networking protocol? - ;; (Considering a possible Python implementation.) - (break) - (eval request)) - -(defun update-world () - "Update the world to match the one on the server" - (setf *world* (server-send *world*))) diff --git a/lisp/player.lisp b/lisp/player.lisp index c989c22..66328bf 100644 --- a/lisp/player.lisp +++ b/lisp/player.lisp @@ -12,17 +12,19 @@ (defstruct player (name "") - (race NIL) - (class NIL) + (race "") + (class "") (strength 0) (dexterity 0) (constitution 0) (intelligence 0) (item NIL) - (weapon NIL) + (weapon "") (place "") (experience 0) - (health 0)) + (max-health 50) + (health 50) + (game-admin NIL)) (defstruct race @@ -41,6 +43,8 @@ (special-ability NIL)) +;; This function is probably superfluous, as the player struct should only store +;; names of game objects (the actual objects are stored in *world*) (let ((list-function (make-list-function 'player NIL))) (defun list-player-objects (object-type player) "Get a list of the names of all the player's objects of this type." diff --git a/lisp/ui.lisp b/lisp/ui.lisp new file mode 100644 index 0000000..f8cb7cf --- /dev/null +++ b/lisp/ui.lisp @@ -0,0 +1,300 @@ +;;; +;;; Atlantis is a framework for creating multi-user dungeon worlds. +;;; This is the Common Lisp implementation. +;;; +;;; The client module is responsible for the actual user interface presented +;;; to a player. (Warning: this will likely change significantly, currently +;;; I am only implementing a mock-up before I get the networking part working.) +;;; +;;; Licensed under the terms of the MIT license. +;;; author: Daniel Vedder +;;; date: 21/05/2015 +;;; + +(let ((player NIL)) + (defun play-game (player-name) + "The main game loop" + (clear-screen) + ;; Initialize the player if necessary + (when (null player) + (setf player (get-game-object 'player player-name))) + (when (null player) + (setf player (create-player player-name)) + (when (null (list-world-objects 'player)) + (setf (player-game-admin player) T)) + (add-game-object player) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player))) + ;; The actual game loop + (clear-screen) + (let ((place (get-game-object 'place (player-place player)))) + (describe-place place) + (input-string command) + (while (not (or (equalp command "quit") (equalp command "exit"))) + (game-command command player) + (input-string command)) + (format t "~&Goodbye!")))) + +(defun create-player (player-name) + "The user creates a new player" + ;; XXX This function feels somewhat ugly - any possibility of a cleanup? + (let ((player (make-player :name player-name + :place (world-starting-place *world*))) + (char-attr + '((strength 0) (dexterity 0) + (constitution 0) (intelligence 0))) + (items NIL) (weapon "") + (character-points NIL)) + (format t "~&The name you have chosen is not registered on this game.") + (unless (y-or-n-p "~&Create a new player?") (start-menu)) + ;; Chose race and class + (format t "~&Please chose a race:") + (setf (player-race player) (choose-option (list-world-objects 'race))) + (format t "~&Please chose a class:") + (setf (player-class player) + (choose-option (list-world-objects 'character-class))) + (dolist (i (character-class-special-item + (get-game-object 'character-class (player-class player)))) + (set-object-attribute player 'item i)) + ;; Set character attributes + (while (or (< (reduce #'+ character-points) 24) ; XXX magic number! + (not (set-p character-points))) + (set-list (1+ (random 20)) a b c d) + (setf character-points (list a b c d))) + (setf text " +Now distribute your attribute points. Random numbers have been chosen, +you may assign one number to each of the following attributes:") + (format t "~&~A~%~A~%~%The numbers are:" + text (string-from-list (keys char-attr))) + ;; TODO I should replace simple-input with something offering 'magic' + (do* ((i 0 (1+ i)) (attr (safe-nth i (keys char-attr)) + (safe-nth i (keys char-attr))) + (val (cassoc attr char-attr) (cassoc attr char-attr))) + ((= i (length char-attr)) player) + (format t "~&~A" (string-from-list character-points)) + (simple-input val (concatenate 'string (symbol-name attr) ":")) + (while (not (member val character-points)) + (format t "~&Sorry, invalid number chosen. Please reenter:") + (simple-input val (concatenate 'string (symbol-name attr) ":"))) + (let ((player-fn (build-symbol "player-" attr))) + ;; XXX Kludge ?! + (eval `(setf (,player-fn ,player) ,val))) + (setf character-points + (remove-if #'(lambda (x) (= x val)) character-points))))) + +(defun describe-place (p) + "Print out a complete description of place p" + (when (stringp p) (setf p (get-game-object 'place p))) + (format t "~&~A" (string-upcase (place-name p))) + (format t "~&~%~A" (place-description p)) + (format t "~&~%Neighbouring places: ~A" + (string-from-list (place-neighbour p))) + (format t "~&Players present: ~A" (string-from-list (place-player p))) + (format t "~&Items: ~A" (string-from-list (place-item p))) + (format t "~&NPCs: ~A" (string-from-list (place-npc p))) + (format t "~&Monsters: ~A" (string-from-list (place-monster p)))) + +(defun game-command (cmd player) + "Execute a typed-in game command" + (let* ((command (read-from-string cmd)) + (space (position #\Space cmd)) + (arg (if space (second (cut-string cmd (1+ space))) NIL))) + (if (member command *commands*) + (if space (funcall command player arg) + (funcall command player)) + (progn (format t "~&Sorry, this command does not exist!") + (format t "~&Type 'help' for a list of commands."))))) + + +;;; +;;; Here follow the functions that define the in-game commands. +;;; + + +;; A list of all in-game commands. Each new command must be registered here. +(defvar *commands* + '(help place player + goto pickup drop talk + equip fight shoot + about save clear)) + +;;; The following commands don't take any arguments except for a player + +(defun help (player) + "Print out a list of in-game commands" + (setf help-text " +Commands: +help - Show this list of game commands +quit/exit - Exit the game +clear - Clear the screen +place - Describe the current location +player - Describe your player +goto - Go to a neighbouring location +about - Show a description of this entity +talk - Talk to an NPC +pickup - Pick up an item lying around +drop - Drop the item +equip - Equip this item as your weapon +shoot - Take a shot at a monster +fight - Fight a monster +save - Save the game to file") + (format t "~A" help-text)) + +(defun clear (player) + "Clear the screen (wrapper function)" + (clear-screen) + (place player)) + +;; XXX Will the following two functions give problems? (Their name is +;; identical with the struct name) Probably not, but best to be aware. +(defun place (player) + "Describe the player's current location (wrapper function)" + (describe-place (player-place player))) + +(defun player (p) + "Print a description of this player" + (let ((tab (string #\tab))) + (when (stringp p) (setf p (get-game-object 'player p))) + (format t "~&Player ~A:" (player-name p)) + (format t "~&~%Current place: ~A" (player-place p)) + (format t "~&Race: ~A~AClass: ~A" (player-race p) tab (player-class p)) + (format t "~&=====") + (format t "~&Attributes:") + (format t "~&Intelligence: ~A~AStrength: ~A" + (player-intelligence p) tab (player-strength p)) + (format t "~&Constitution: ~A~ADexterity: ~A" + (player-constitution p) tab (player-dexterity p)) + (format t "~&=====") + (format t "~&Weapon: ~A" (player-weapon p)) + (format t "~&Items: ~A" (string-from-list (player-item p))) + (format t "~&=====") + (format t "~&Max health: ~A~ACurrent health: ~A" + (player-max-health p) tab (player-health p)) + (format t "~&Experience: ~A" (player-experience p)))) + +;;; These next functions have to take two arguments (the argument +;;; to the function and a player instance). + +(let ((last-save NIL)) + (defun save (player &optional game-file) + "Save a game to file (wrapper method around save-world)" + ;; XXX Include a permissions check (only allow admins to save)? + ;; Could give problems in single-player mode. + (cond (game-file (setf last-save game-file)) + ((and last-save (not game-file)) (setf game-file last-save)) + ((not (or last-save game-file)) + (format t "~&Where do you want to save the game?") + (input-string game-file))) + (when (y-or-n-p "Save game to ~A?" game-file) + (save-world game-file) + (format t "~&Game saved.")))) + +(defun goto (player &optional location) + "Go to the specified location" + (unless location + (format t "~&Please specify a location!") + (return-from goto)) + (when (symbolp location) (setf location (symbol-name location))) + (when (not (member location + (place-neighbour (get-game-object 'place + (player-place player))) + :test #'equalp)) + (format t "~&This place does not border your current location!") + (return-from goto)) + (clear-screen) + (debugging "~&~A is going to ~A." (player-name player) location) + (remove-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (set-object-attribute player 'place location) + (set-object-attribute (get-game-object 'place (player-place player)) + 'player (player-name player)) + (describe-place location)) + +(defun about (player &optional object-name) + "Print a description of this object" + (unless object-name + (format t "~&Please specify the object you wish to inspect!") + (return-from about)) + ;; TODO What about objects that the player is carrying? + ;; And there's probably a more elegant way of doing this... + (let ((place (get-game-object 'place (player-place player))) + (description NIL)) + (macrolet ((set-descr (type) + (let ((place-descr (build-symbol type "-description")) + (place-object (build-symbol "place-" type))) + `(when (member object-name (,place-object place) + :test #'equalp) + (setf description (,place-descr + (get-game-object ',type + object-name))))))) + (set-descr item) + (set-descr monster) + (set-descr npc)) + (if description + (format t "~&(~A) ~A" object-name description) + (format t "~&Could not find ~A!" object-name)))) + +(defun talk (player &optional npc-name) + "Talk to the desired NPC" + ;; TODO Add interactive facility + (unless npc-name + (format t "~&Please specify an NPC to talk to!") + (return-from talk)) + (let* ((place (get-game-object 'place (player-place player))) + (npc (when (member npc-name (place-npc place) :test #'equalp) + (get-game-object 'npc npc-name)))) + (if npc + (format t "~&~A: ~A" (string-upcase npc-name) (npc-says npc)) + (format t "~&~A is not here!" npc-name)))) + +(defun pickup (player &optional item-name) + "The player picks up an item" + (unless item-name + (format t "~&Please specify an item to pick up!") + (return-from pickup)) + (let ((place (get-game-object 'place (player-place player))) + (item (get-game-object 'item item-name))) + (if (member item-name (place-item place) :test #'equalp) + (progn + (set-object-attribute player 'item item-name) + (when (item-function item) + (funcall (item-function item))) + (remove-object-attribute place 'item item-name) + (format t "~&You have picked up: ~A" item-name)) + (format t "~&Sorry, this item is not here!")))) + +(defun drop (player &optional item) + "The player drops the specified item" + (unless item + (format t "~&Please specify an item to drop!") + (return-from drop)) + (if (member item (player-item player) :test #'equalp) + (progn + (remove-object-attribute player 'item item) + (when (equalp (item-weapon (get-game-object 'item item)) "yes") + (set-object-attribute player 'weapon "")) + (set-object-attribute + (get-game-object 'place (player-place player)) 'item item) + (format t "~&You have dropped: ~A" item)) + (format t "~&You do not possess this item!"))) + +(defun equip (player &optional new-weapon) + "The player sets another item to be his weapon" + (when (or (not new-weapon) (equalp new-weapon "none")) + (setf (player-weapon player) "") + (format t "~&You no longer have any weapon equipped.") + (return-from weapon)) + (if (and (member new-weapon (player-item player) :test #'equalp) + (equalp (item-weapon (get-game-object 'item new-weapon)) "yes")) + (progn + (setf (player-weapon player) new-weapon) + (format t "~&You have equipped: ~A" new-weapon)) + (format t "~&Sorry, this item is not available as a weapon!"))) + +(defun fight (player &optional opponent) + "The player enters combat" + (unless opponent + (format t "~&Please specify an opponent!") + (return-from fight)) + ;; TODO + ) diff --git a/lisp/util.lisp b/lisp/util.lisp index eeeb46d..4437124 100644 --- a/lisp/util.lisp +++ b/lisp/util.lisp @@ -20,6 +20,10 @@ syms) ,@body)) +(defmacro debugging (str &rest format-args) + "If *debugging* is true, print str" + `(when *debugging* (format t ,str ,@format-args))) + ;; TODO DEPRECATED - Needs to be replaced in the current code (defmacro simple-input (var &optional (prompt ">>>")) "Take input from terminal and store it in var" @@ -27,12 +31,14 @@ (format t "~&~A " ,prompt) (setf ,var (read)))) +;; XXX Very useful for debugging, but represents a major security hole +;; when used in a network setting (defmacro magic (var) "Execute typed-in Lisp code" `(when (eq ,var 'magic) (repl))) -; potentially inefficient if called often +;; XXX potentially inefficient if called often (defmacro set-list (value &rest var-list) "Set each symbol in var-list to value" (do* ((expr (list 'setf)) (vl var-list (cdr vl)) (var (car vl) (car vl))) @@ -94,7 +100,6 @@ ; Some of these functions are probably quite inefficient (lots of consing) - ;; XXX DEPRECATED Not actually needed anywhere (defun call-function (function-name &rest args) "Save myself some quoting when calling a function from a generated symbol" @@ -115,6 +120,15 @@ (T (concatenate 'string (to-string (first lst)) (to-string separator) (string-from-list (cdr lst) separator))))) +(defun split-string (str separator) + "Split the string up into a list of strings along the separator character" + (cond ((equalp str (to-string separator)) NIL) + ((zerop (count-instances separator str)) (list str)) + (T (let ((split-elt (cut-string str (position separator str)))) + (cons (first split-elt) + (split-string (second (cut-string (second split-elt) 1)) + separator)))))) + (defun cut-string (s i) "Cut string s in two at index i and return the two substrings in a list" (do* ((c 0 (1+ c)) (letter (aref s c) (aref s c)) @@ -144,6 +158,12 @@ (when (funcall test search-term (elt search-sequence i)) (incf count))))) +(defun set-p (lst) + "Is lst a set (i.e. no elements occur more than once)?" + (cond ((null lst) T) + ((member (car lst) (cdr lst)) NIL) + (T (set-p (cdr lst))))) + (defun to-list (vector &optional (next-elt 0)) "Turn the vector into a list" (if (= next-elt (1- (length vector))) NIL @@ -157,6 +177,11 @@ (file-lines (list line) (append file-lines (list line)))) ((null line) file-lines)))) +(defun print-text-file (file-name) + "Print out the contents of this text file" + (dolist (line (load-text-file file-name)) + (unless (null line) (format t "~%~A" line)))) + (defun build-symbol (&rest components) "Concatenate the passed components into a single symbol" (read-from-string (string-from-list components ""))) @@ -174,6 +199,29 @@ (setf name-list (cons (funcall get-object-name object) name-list)))))) +(defun choose-number-option (option-list) + "The user chooses one out of a list of options, the index is returned" + (dotimes (i (length option-list)) + (format t "~&~S) ~A" (1+ i) (nth i option-list))) + (simple-input choice) + (while (or (not (numberp choice)) (< choice 1) + (> choice (length option-list))) + (format t "~&Invalid choice! Please choose again:") + (simple-input choice)) + (1- choice)) + +(defun choose-option (option-list) + "Like choose-number-option, but return the value of the choice" + ;; Basically just a utility wrapper + (nth (choose-number-option option-list) option-list)) + +(defun clear-screen () + "Clear the screen in an OS-dependent manner" + ;; NOTE: only works with CLISP! (ext:shell function used) + (cond ((member ':unix *features*) (ext:shell "clear")) + ((member ':win32 *features*) (ext:shell "cls")) + (t (debugging "~&clear-screen is not supported on this operating system!")))) + (defun repl () "Launch a read-eval-print loop" (let ((expr (simple-input expr "lisp >"))) diff --git a/lisp/world.lisp b/lisp/world.lisp index 0b84a15..c6f01fe 100644 --- a/lisp/world.lisp +++ b/lisp/world.lisp @@ -26,8 +26,7 @@ (npcs NIL) (items NIL) (weapons NIL) - (starting-place "") - (game-manager "")) ;The player in charge of the game + (starting-place "")) (setf *world* (make-world)) ;XXX Move this to another module? @@ -54,7 +53,23 @@ (defun name-world (name) "Set the name of the *world*" - (format t "~&The name of the world is ~A." name) + (debugging "~&The name of the world is ~A." name) (setf (world-name *world*) name) NIL) +(defun load-game (game-file) + "Load a saved game from disk" + (with-open-file (g game-file) + (let ((version-number (read g)) + (loaded-world (read g))) + (when (!= version-number ATLANTIS-VERSION :test equal) + (format t "~&WARNING: The loaded game was saved by a ") + (format t "different version of Atlantis!")) + (if (world-p loaded-world) + (setf *world* loaded-world) + (error "World file ~A is corrupted!" game-file))))) + +(defun save-world (game-file) + "Save a game to file" + (with-open-file (g game-file :direction :output) + (format g "~S~%~S~%" ATLANTIS-VERSION *world*)))