Flatland RPG - With World Builder & Map Editor
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Flatland RPG - With World Builder & Map Editor
http://freefile.kristopherw.us/uploads/ ... land_7.zip
Flatland RPG
Release #3:
What you see is what you get style Map Editor and World Builder is now building the game itself!
NPC's:
NPC's can be either characters who drive the plot, or bosses, powerful enemies. The NPC's feature a scripting engine with basic-like syntax and a LISP style data type and parser.
NPC's feature a dialog engine which can link into the scripting engine, so that certain responses will provoke certain actions. User types in say "mutagenic cancer" and is given a response. More versatility will be forth coming.
Map Objects:
Map objects pop up a narration box which you can enter commands into, like takebottle, gazebottle etc... Type help for a detailed explanation of how the dialog box interacts with the user. It is very similar to a text adventure. You can use objects and they can work with multiple commands from the dialog box.
Inventory:
A basic inventory has been added, but functionality is low.
~~~
I think I'll add some kind of bartering system to this map object thing too.
If you get a chance and take a look let me know if you have any ideas on ways to make it better.
Figure it being like a puzzle type game that you have to sort of figure out.
rb
Flatland RPG
Release #3:
What you see is what you get style Map Editor and World Builder is now building the game itself!
NPC's:
NPC's can be either characters who drive the plot, or bosses, powerful enemies. The NPC's feature a scripting engine with basic-like syntax and a LISP style data type and parser.
NPC's feature a dialog engine which can link into the scripting engine, so that certain responses will provoke certain actions. User types in say "mutagenic cancer" and is given a response. More versatility will be forth coming.
Map Objects:
Map objects pop up a narration box which you can enter commands into, like takebottle, gazebottle etc... Type help for a detailed explanation of how the dialog box interacts with the user. It is very similar to a text adventure. You can use objects and they can work with multiple commands from the dialog box.
Inventory:
A basic inventory has been added, but functionality is low.
~~~
I think I'll add some kind of bartering system to this map object thing too.
If you get a chance and take a look let me know if you have any ideas on ways to make it better.
Figure it being like a puzzle type game that you have to sort of figure out.
rb
Last edited by rolliebollocks on Mar 12, 2009 17:40, edited 5 times in total.
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Preface/Intro/Plot Background for the RPG
Ages pass over the turning earth, and a state of dismal woe. The great human race, by far the epoch of earth's evolution, had constructed from their collective genius a machine so advanced it replaced the need for science. It was worshipped as a God. It was ever-present, all pervasive, omniscient, beneficient.
But the earthlings came to depend on their creation for their very survival. The Great Artilect regulated crop growing, medicine, food distribution, utilizing the collective agricultural, scientific, and political knowledge of mankind in a vast database. No one had to remember. No one needed to hunt, or even venture forth in their SUV's to the grocery stores. To merely speak the name of food, was enough to bring it into being. To speak the name of a disease, brought the cure into being.
Eventually it came to pass that the individual had no choices to make for his or herself. The machine could simply determine your nutritional requirements based on a scan of your body, and a meal would appear before the hapless human, and it would eat the meal, and give thanks to the machine which made it. The man would return three times a day to be scanned, like clockwork at the same time everyday. Man was no longer programming machine, it was the one great machine, the Artilect that humanity had crafted out of its own hands to serve as the arbitrator of society that programmed man.
On February 30th, 2109, the Artilect's main core exploded.
And mankind was left like like a legless spider in a glass of water. People died in droves by the millions, not from the explosion itself, but rather the inability to survive. The instincts that the mind possessed, those that provided for the needs of the body, were lost on many who were provided for. And they simply starved to death, standing in their scanning beds, waiting for their meal.
In others awoke savage instincts they never realized they possessed. The instinct to horde, to hunt, to scavange, resulted in packs of wandering semi-humans who organized themselves like dogs, murdering, stealing and collecting any edible resource.
In still others remained the faint echo of a love that acted as the most perfect social glue. A force so strong that even God himself is said to have shuddered at it. And though few if any possessed the desire to learn such a thing, stories still remained of a great Tower at a place called Babel that God destroyed to punish mankind for its arrogance.
For them, an endless war emerged against the various hordes of raiders.
Ages pass over the turning earth, and a state of dismal woe. The great human race, by far the epoch of earth's evolution, had constructed from their collective genius a machine so advanced it replaced the need for science. It was worshipped as a God. It was ever-present, all pervasive, omniscient, beneficient.
But the earthlings came to depend on their creation for their very survival. The Great Artilect regulated crop growing, medicine, food distribution, utilizing the collective agricultural, scientific, and political knowledge of mankind in a vast database. No one had to remember. No one needed to hunt, or even venture forth in their SUV's to the grocery stores. To merely speak the name of food, was enough to bring it into being. To speak the name of a disease, brought the cure into being.
Eventually it came to pass that the individual had no choices to make for his or herself. The machine could simply determine your nutritional requirements based on a scan of your body, and a meal would appear before the hapless human, and it would eat the meal, and give thanks to the machine which made it. The man would return three times a day to be scanned, like clockwork at the same time everyday. Man was no longer programming machine, it was the one great machine, the Artilect that humanity had crafted out of its own hands to serve as the arbitrator of society that programmed man.
On February 30th, 2109, the Artilect's main core exploded.
And mankind was left like like a legless spider in a glass of water. People died in droves by the millions, not from the explosion itself, but rather the inability to survive. The instincts that the mind possessed, those that provided for the needs of the body, were lost on many who were provided for. And they simply starved to death, standing in their scanning beds, waiting for their meal.
In others awoke savage instincts they never realized they possessed. The instinct to horde, to hunt, to scavange, resulted in packs of wandering semi-humans who organized themselves like dogs, murdering, stealing and collecting any edible resource.
In still others remained the faint echo of a love that acted as the most perfect social glue. A force so strong that even God himself is said to have shuddered at it. And though few if any possessed the desire to learn such a thing, stories still remained of a great Tower at a place called Babel that God destroyed to punish mankind for its arrogance.
For them, an endless war emerged against the various hordes of raiders.
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Intro to the Documentation for the World Builder/Game Engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(Outlines the goals of this project)
1. Getting Started
What is FlatlandLand RPG?
Flatland RPG is a role playing game engine and development tool. It features a two dimensional tile map with pixel scrolling and screen scrolling, the ability to link multiple maps in a single world, an intricate NPC type, a basic enemy type, map objects and eye candy to break the monotony of repeating tiles.
Flatland RPG is designed both as a tutelary aid to novice programmers looking to learn the ropes of video game programming and to those who want to make an RPG game without designing the engine itself.
For all that, Flatland is rather quirky in its implementation, borrowing heavily from the foundations of RPG programming to bring the interactive novel back into the fold as a viable RPG design strategy. Yes, in Flatland's dialog engine is a text-adventure based quid-pro-quo with map objects and NPC's. You can ask them questions, program their responses, and even execute lines of Flatland's scripting language from within the dialog engine. This makes it possible to give NPC's commands, which I felt was pretty cool.
1.1.1 The World Builder
The World Builder, while by no means complete, is currently building a game. This means that some of editing will have to be done with a text editor, which is better suited to the task anyway. Currently, the only thing that is not added, which sort of sucks, is the ability to add maps to the world. This you must do by manually editing the .world file.
You can move around by moving the mouse to the edge of the map. If you go past the map's edge you will land in the next map over which is constructed like a grid, like the map itself, which is a two dimensional array of tiles. The world is a two dimensional array of maps. If we so chose, we could implement a two dimensional array of worlds, we might as well call a universe. The elegance of such a system is that the image of the whole is contained in the individual parts, which are just more complex variations on simpler data types, a model of programming that will explored later in the documentation, for those interested.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(Outlines the goals of this project)
1. Getting Started
What is FlatlandLand RPG?
Flatland RPG is a role playing game engine and development tool. It features a two dimensional tile map with pixel scrolling and screen scrolling, the ability to link multiple maps in a single world, an intricate NPC type, a basic enemy type, map objects and eye candy to break the monotony of repeating tiles.
Flatland RPG is designed both as a tutelary aid to novice programmers looking to learn the ropes of video game programming and to those who want to make an RPG game without designing the engine itself.
For all that, Flatland is rather quirky in its implementation, borrowing heavily from the foundations of RPG programming to bring the interactive novel back into the fold as a viable RPG design strategy. Yes, in Flatland's dialog engine is a text-adventure based quid-pro-quo with map objects and NPC's. You can ask them questions, program their responses, and even execute lines of Flatland's scripting language from within the dialog engine. This makes it possible to give NPC's commands, which I felt was pretty cool.
1.1.1 The World Builder
The World Builder, while by no means complete, is currently building a game. This means that some of editing will have to be done with a text editor, which is better suited to the task anyway. Currently, the only thing that is not added, which sort of sucks, is the ability to add maps to the world. This you must do by manually editing the .world file.
You can move around by moving the mouse to the edge of the map. If you go past the map's edge you will land in the next map over which is constructed like a grid, like the map itself, which is a two dimensional array of tiles. The world is a two dimensional array of maps. If we so chose, we could implement a two dimensional array of worlds, we might as well call a universe. The elegance of such a system is that the image of the whole is contained in the individual parts, which are just more complex variations on simpler data types, a model of programming that will explored later in the documentation, for those interested.
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Update!
This is far superior than the earlier update, but my brain is too fried to remember why.
Optimized map routines some more.
Added several functions for NPC's.
Added pixel perfect collision.
Added battle stuff for Friendly NPC's who can fight with you against bad guys.
Started commenting some stuff.
Here's a pic:
[/img]
This is far superior than the earlier update, but my brain is too fried to remember why.
Optimized map routines some more.
Added several functions for NPC's.
Added pixel perfect collision.
Added battle stuff for Friendly NPC's who can fight with you against bad guys.
Started commenting some stuff.
Here's a pic:
[/img]
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Modding the Engine
~~~~~~~~~~~~~
Lesson 1:
How do I add new functions to the scripting engine?
Simple.
1: Open up agent_scripting.bi and agent_scripting.bas.
2: Find this chunk of code in agent_scripting.bi
Simple add the reference at the end of the list.
eg: _MYNEWFUNCTION_
3: Click over to agent_scripting.bas and find:
4: At the bottom of the constructor add:
this.func(_MYNEWFUNCTION_)->idname = "newfunc"
this.func(_MYNEWFUNCTION_)->idx = _MYNEWFUNCTION_
By giving the function the idname "newfunc" you are telling the program that when the user types in "newfunc", _MYNEWFUNCTION_ will be called.
5: Open up agents.bas and find Agent.CallFunction
6: Somewhere in the bounds of the select case block add your function like so:
7: Fire up Script Tester.bas, double click on one of the NPC's. Type in newfunc, and click run.
The program should end immediately.
NEXT:
What is an hm_expression? How do I access it and utilize the data?
rb
~~~~~~~~~~~~~
Lesson 1:
How do I add new functions to the scripting engine?
Simple.
1: Open up agent_scripting.bi and agent_scripting.bas.
2: Find this chunk of code in agent_scripting.bi
Code: Select all
enum hm_FUNCTIONS
_ME_ = 1
_YOU_
_EQ_
_IF_
_PRINT_
_HEADNORTH_
_HEADSOUTH_
_HEADWEST_
_HEADEAST_
_BEEP_
_HALT_
_FLEE_
_CHASE_
_ISEE_
_CHECKLRUD_
_HUNT_
_CANNIBALIZE_
_FOCUS_
_HUNGRY_
_TWITCH_
_PROJECTILEATME_
_APPROACH_
_SAYTHIS_
_NAME_
_POSX_
_POSY_
_VELX_
_VELY_
_DIRECTION_
_FOLLOW_
_ADDITEM_
_FILLHEALTH_
_HEALTHPLUS_
_HEALTHMINUS_
_FIRE_
------> New line goes here...
end enum
eg: _MYNEWFUNCTION_
3: Click over to agent_scripting.bas and find:
Code: Select all
Constructor hm_Function_Map ()
for i as integer = 1 to MAX_SYMBOLS
func(i) = New hm_FUNCTION
next
this.func(_ME_)->idname = "me"
this.func(_ME_)->idx = _ME_
this.func(_YOU_)->idname = "you"
this.func(_YOU_)->idx = _YOU_
...
this.func(_MYNEWFUNCTION_)->idname = "newfunc"
this.func(_MYNEWFUNCTION_)->idx = _MYNEWFUNCTION_
By giving the function the idname "newfunc" you are telling the program that when the user types in "newfunc", _MYNEWFUNCTION_ will be called.
5: Open up agents.bas and find Agent.CallFunction
Code: Select all
sub Agent.CallFunction ( funcname as integer, inexpr as hm_EXPRESSION ptr )
select case funcname
CASE _ADDITEM_
inexpr->rhs = left(inexpr->rhs, len(inexpr->rhs) - 1)
if AddItem ( inexpr->rhs ) = TRUE then
dialogbox ("You now possess the " & inexpr->rhs)
else
dialogbox ("You cannot carry anymore " & inexpr->rhs & "'s")
endif
CASE _IF_
dim as switch istrue
with script
inexpr = .Splice ( inexpr->rhs )
...
Code: Select all
CASE _MYNEWFUNCTION_
end
The program should end immediately.
NEXT:
What is an hm_expression? How do I access it and utilize the data?
rb
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Modding The Engine: Lesson 2
Understanding the Scripting Engine
~~~~~~~~~~~~~~~~~~~~~~
So, I bet you're wondering... How do I go about utilizing the scripting engine to send messages to the GUI, and why do I want to do that?
Well. The second question goes first.
The main entry point of the engine is FlatLandRPG.bas. From here we can control and access any data contained in the whole engine. The same cannot be said of the scripting engine which is a subordinate type of the Agent class. From it you can access backward, the map, the Observer, the tiles etc... But not forward... ie: map_objects and world-specific functions.
This ultra-hackish methodology is meant to delight and amuse both newbies and experts alike. But this is the price you pay, when you don't develop the GUI yourself, and the price is not high for what you get.
First things first.
#1. Open up agent_scripting.bi, agent_scripting.bas and in the manner mentioned in lesson #1 add the function _SENDSIGNAL_ with the keyword "signal". Proceed to step 2 after you have added the case _SENDSIGNAL_ in agent.bas.
#2. As you have probably noticed, inexpr is a parameter of CallFunction, with the function's idname being the other parameter. When the parser strikes a function, it sends the entire line including the function call in the expression.
*Thing to do:
after case _SENDSIGNAL_
add the lines...
Consout ( inexpr->lhs )
Consout ( inexpr->rhs )
Then load up "Script Tester.bas" and double click on an NPC. Type "signal fooey poopy dude" and click Run.
inexpr->lhs = signal
inexpr->rhs = fooey poopy dude
#3. For this routine, all we want to do is send the message "fooey poopy dude" to the GUI, and make it do something.
So... We have to send a "fake message" to the GUI utilizing the Agent's own GUI whatnot. This is not difficult as it merely involves cutting and pasting laughably complicated lines of code...
Code:
What?
Whenever you assign a value to be sent to the Message Handler, you will assign it to this:
Code:
In fact inexpr->rhs is the list of parameters minus the function call. This bit of code simply assigns your message to this piece of GUI data and relays it to the message handler. The rest is irrelevant, and can be copied exactly every single time. But for the record, the click positions for the mouse (.mx, .my) are being assigned the value of the NPC's position. And since there is no 409th mouse button, all we have to do is add a check in the message handler for a message with a phony mouse button.
Code:
Character_Handler ( Mouse_Over, this.animus->faux )
That chunk of code sends the signal.
#4. Receiving the Signal
Now. In Script Tester.bas, find the message handler and this chunk of code:
Code:
Underneath Mouse_Over we will have to check our EventData.button:
Code:
#5. Run "String Tester.bas", double click on an NPC and type "signal blah blah blah" and click Run
#6.
Now all you have to do is...
Code:
if EventData.Button > 3 then
select case .text
case "burn": PlaySample ( scream )
end select
endif
The possibilities here are endless, we can manipulate anything, parse script, run dialog, we have access to every piece of data via the sending of a single string.
Hot damn.
Next up: Manipulating NPC's.
Understanding the Scripting Engine
~~~~~~~~~~~~~~~~~~~~~~
So, I bet you're wondering... How do I go about utilizing the scripting engine to send messages to the GUI, and why do I want to do that?
Well. The second question goes first.
The main entry point of the engine is FlatLandRPG.bas. From here we can control and access any data contained in the whole engine. The same cannot be said of the scripting engine which is a subordinate type of the Agent class. From it you can access backward, the map, the Observer, the tiles etc... But not forward... ie: map_objects and world-specific functions.
This ultra-hackish methodology is meant to delight and amuse both newbies and experts alike. But this is the price you pay, when you don't develop the GUI yourself, and the price is not high for what you get.
First things first.
#1. Open up agent_scripting.bi, agent_scripting.bas and in the manner mentioned in lesson #1 add the function _SENDSIGNAL_ with the keyword "signal". Proceed to step 2 after you have added the case _SENDSIGNAL_ in agent.bas.
#2. As you have probably noticed, inexpr is a parameter of CallFunction, with the function's idname being the other parameter. When the parser strikes a function, it sends the entire line including the function call in the expression.
*Thing to do:
after case _SENDSIGNAL_
add the lines...
Consout ( inexpr->lhs )
Consout ( inexpr->rhs )
Then load up "Script Tester.bas" and double click on an NPC. Type "signal fooey poopy dude" and click Run.
inexpr->lhs = signal
inexpr->rhs = fooey poopy dude
#3. For this routine, all we want to do is send the message "fooey poopy dude" to the GUI, and make it do something.
So... We have to send a "fake message" to the GUI utilizing the Agent's own GUI whatnot. This is not difficult as it merely involves cutting and pasting laughably complicated lines of code...
Code:
Code: Select all
CASE _SENDSIGNAL_
this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")->text = inexpr->rhs
With this.animus->faux
.GUI = this.animus->widge->GUI
.Ctrl = this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")
.Button = 409
.mx = this.animus->p.x
.my = this.animus->p.y
End With
Character_Handler ( Mouse_Over, this.animus->faux )
Code: Select all
this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")->text = inexpr->rhs
Code:
Code: Select all
this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")->text
In fact inexpr->rhs is the list of parameters minus the function call. This bit of code simply assigns your message to this piece of GUI data and relays it to the message handler. The rest is irrelevant, and can be copied exactly every single time. But for the record, the click positions for the mouse (.mx, .my) are being assigned the value of the NPC's position. And since there is no 409th mouse button, all we have to do is add a check in the message handler for a message with a phony mouse button.
Code:
Character_Handler ( Mouse_Over, this.animus->faux )
That chunk of code sends the signal.
#4. Receiving the Signal
Now. In Script Tester.bas, find the message handler and this chunk of code:
Code:
Code: Select all
sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
Dim As _kGUI_Control Ptr Ctrl = Cast(_kGUI_Control Ptr, EventData.Ctrl)
Dim As _kGUI Ptr GUI = Cast(_kGUI Ptr, EventData.GUI)
dim as string key
dim as coord_type click
With *Ctrl
Select Case EventMsg
Case Mouse_Over
Code:
Code: Select all
if EventData.Button > 3 then
Consout (.text)
endif
#5. Run "String Tester.bas", double click on an NPC and type "signal blah blah blah" and click Run
#6.
Now all you have to do is...
Code:
if EventData.Button > 3 then
select case .text
case "burn": PlaySample ( scream )
end select
endif
The possibilities here are endless, we can manipulate anything, parse script, run dialog, we have access to every piece of data via the sending of a single string.
Hot damn.
Next up: Manipulating NPC's.
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Lesson 3: Everything you need to know about the NPC data type.
Okay. Now step by step through the functions. Starting with the Constructor.
The constructor's function is to load the data from a text file which is located in "Playerdata" directory.
The observer data is kept in .chardata, agent data is kept in .agentdata, dialogs and scripts usually have names like .script and .dialog with the agent's name as the left hand side of the filename.
This saves on memory space and loading time.
~~~
This next function is called in any game loop to deal with your NPC. Any suggestions on optimization would be greatly appreciated...
Code: Select all
type Agent
Public:
Declare Constructor ( filename as string ) 'Will load our NPC from a file
Declare Destructor () 'Will destroy observer type
as string file_name 'Eats up memory, but makes the NPC edit go
'Will be explained in detail further...
as observer ptr animus '
as observer ptr ME
as observer ptr YOU
as projectile ptr myProjectile
as string ItemInventory(10) 'Max Amount of Items One can carry
as integer CarryMax = 1 'Max amount of Items one can carry w/o help
as string saythis 'For the dialog engine
as _kGUI_Design ptr db 'The dialog GUI
as Conversation inspeech 'Instantiation of our dialog type
as hm_Parser_Lexer script 'Instantiation of our scripting engine
as hm_Expression nscript(100) 'this is where the script will be stored
as integer Introduced 'This allows us to run a script called "<agentname>.p1_first_time.script", from then after it will run "<agentname>.p1.script".
as switch IFollowYou 'This will be moved back to the Observer type ( explained later )...
'These are our routines
declare sub Save ( )
declare sub Rename ( inName as string )
declare sub Process ( )
declare sub IseeYou ( other as observer ptr )
declare sub Fire ( other as observer ptr )
declare sub Follow ( other as observer ptr )
declare sub Go_Script ( )
declare sub RunScript ( filename as string )
declare sub RunString ( inexpr as string )
declare sub CallFunction ( funcname as integer, inexpr as hm_EXPRESSION ptr )
declare sub DialogBox ( text as string )
declare sub DialogReply ( )
'Here is our dialog switch so that our NPC doesn't fart around while he's speaking...
as switch amspeaking
Private:
'Next -> Visual Field, Memory, Scripting from within
end type
Code: Select all
Constructor Agent ( byval filename as string )
'First, the string is past by value, which means by pointer, which means, 'much faster.
dim as integer f = freefile
'freefile makes certain we are not opening two files in the same slot
if fileexists ( filename ) then
'included in file.bi, makes sure the file exists, and if not, exits the
'program...
this.file_name = filename
open file_name for input as #f
dim as string npcname, obfile, projfile, p11, p1, ass
line input #f, npcname
line input #f, obfile
line input #f, projfile
close #f
animus = new Observer
'This is how we instantiate a new instance the Observer type. To call new,
'is to call the constructor of the Observer type. If animus was not a
'pointer, the constructor would called immediately. This has an advantage,
'but is otherwise indistinct from simply not using a pointer. The advantage
'regards the efficient use of memory.
animus->Load_Observer ( obfile ) ' Then we load it
animus->Rename ( npcname ) ' We rename it
this.myprojectile = new projectile 'We instantiate the projectile
myprojectile->Load ( projfile ) 'We load the projectile
'GUI stuff for dialog box
db = new _kGUI_Design("Widgets/dialog.kwk",@Character_Handler)
db->GUI->FindControl ("dialog_text")->Scrollbar = 1
db->GUI->FindControl ("dialog_text")->Wrap = 1
db->GUI->FindControl ("dialog_text")->Locked = 0
'Dialog data file
if fileexists ( "Playerdata/" & this.animus->nam_ & ".dialog" ) then
this.inspeech.LoadDialog ( "Playerdata/" & this.animus->nam_ & ".dialog" )
'Note how all the files are called by this.animus->nam_ & some_extension... this is to cut down strings in my UDT's...
else
Consout ( "Dialog file missing for " & this.animus->nam_ )
'Warning if the file is missing
endif
else
Consout ( filename & " not found." )
sleep:end
'Fatal error if the file is missing
endif
this.animus->Clearallswitches
ME = this.animus 'Me stays a pointer
YOU = new Observer
'You is loaded as the nearest enemy to any given agent is crucial to
'battle routines...
end Constructor
The observer data is kept in .chardata, agent data is kept in .agentdata, dialogs and scripts usually have names like .script and .dialog with the agent's name as the left hand side of the filename.
This saves on memory space and loading time.
~~~
This next function is called in any game loop to deal with your NPC. Any suggestions on optimization would be greatly appreciated...
Code: Select all
sub Agent.Process ( )
'This is what the little bastard does when you're not around...
this.RunScript ( "Playerdata/" & this.animus->nam_ & ".auto.script" )
'The velocity, position, direction etc... Are all held by the Observer type,
'so to access them we must refer to it in terms of the animus. Since the
'animus is a pointer, we refer to its data via -> as opposed to animus.v.y.
'Otherwise pointers behave identically to non-pointers.
if this.animus->gonorth.isOn = TRUE then this.animus->v.y = -2
if this.animus->gosouth.isOn = TRUE then this.animus->v.y = 2
if this.animus->gowest.isOn = TRUE then this.animus->v.x = -2
if this.animus->goeast.isOn = TRUE then this.animus->v.x = 2
'The direction is an integer value calculated according to the velocity
'magnitudes. The faster velocity sets whether the horizontal or vertical
'dimension wins out, and then whether or not the value is positive or
'negative determines north vs. south, east vs. west
'Pick the pic
if abs(this.animus->v.x) > abs(this.animus->v.y) and this.animus->v.x > 0 then this.animus->direction = 2
if abs(this.animus->v.x) > abs(this.animus->v.y) and this.animus->v.x < 0 then this.animus->direction = 4
if abs(this.animus->v.x) < abs(this.animus->v.y) and this.animus->v.y > 0 then this.animus->direction = 3
if abs(this.animus->v.x) < abs(this.animus->v.y) and this.animus->v.y < 0 then this.animus->direction = 1
'Moving Along
'We simply add the velocity to the Agent's position
this.animus->p.x += this.animus->v.x
this.animus->p.y += this.animus->v.y
'And then we handle our switches...
'Process stunned Switch
if this.animus->stunned.isOn <> TRUE then
if this.animus->v.x > this.animus->max_velocity then this.animus->v.x = this.animus->max_velocity
if this.animus->v.y > this.animus->max_velocity then this.animus->v.y = this.animus->max_velocity
if this.animus->v.x >= (this.animus->max_velocity * .9) then this.animus->energy -= (this.animus->energy * animus->stamina)
if this.animus->v.y >= (this.animus->max_velocity * .9) then this.animus->energy -= (this.animus->energy * animus->stamina)
endif
if this.animus->stunned.isOn = TRUE then
this.animus->Rotangle += 1
this.animus->stunned.counter += 1
if this.animus->stunned.counter = 40 then
this.animus->Stunned.counter = 0
this.animus->Rotangle = 0
if this.animus->health < 0 then this.animus->dies
this.animus->Stunned.isOn = FALSE
end if
endif
if this.animus->spin.isOn = TRUE then
this.animus->spin.counter += 1
select case this.animus->spin.counter mod 4
case 0:this.animus->direction = 1
case 1:this.animus->direction = 2
case 2:this.animus->direction = 3
case 3:this.animus->direction = 4
end select
if this.animus->spin.counter = 40 then
this.animus->spin.isOn = FALSE
this.animus->spin.counter = 0
endif
endif
this.animus->v.x *= .98
this.animus->v.y *= .98
if abs(this.animus->v.x) < .09 then this.animus->v.x = 0
if abs(this.animus->v.y) < .09 then this.animus->v.y = 0
'GUI stuff
this.animus->widge->GUI->ProcessEvents
this.animus->widge->GUI->FindControl(this.animus->nam_ & "energy")->value = (this.animus->energy / this.animus->max_energy) * 100
this.animus->widge->GUI->FindControl(this.animus->nam_ & "health")->value = (this.animus->health / this.animus->max_health) * 100
if this.animus->aflame.isOn = TRUE then
this.animus->aflame.counter +=1
if this.animus->aflame.counter = 1 then
this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")->text = "aflame"
With this.animus->faux
.GUI = this.animus->widge->GUI
.Ctrl = this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")
.Button = 9
.mx = this.animus->p.x
.my = this.animus->p.y
End With
this.animus->widge->GUI->FindControl(this.animus->nam_ & "energy")->value = (this.animus->energy / this.animus->max_energy) * 100
this.animus->widge->GUI->FindControl(this.animus->nam_ & "health")->value = (this.animus->health / this.animus->max_health) * 100
Character_Handler ( Mouse_Over, animus->faux )
this.animus->flight.isOn = TRUE
endif
if this.animus->aflame.counter = 40 then
this.animus->aflame.isOn = FALSE
this.animus->aflame.counter = 0
endif
endif
'Process fight or flight
if this.animus->dead.isOn = TRUE then exit sub
if this.animus->hungry.isOn = TRUE then
'Stuff
endif
if this.animus->fight.isOn = TRUE then
this.RunScript ( "Playerdata/" & this.animus->nam_ & ".battle.script" )
endif
if this.animus->twitch.isOn = TRUE then
if this.animus->v.x + this.animus->v.y = 0 then
this.animus->v.x = rnd * this.animus->max_velocity
this.animus->v.y = rnd * this.animus->max_velocity
endif
this.animus->twitch.isOn = FALSE
endif
if this.nscript(1).lhs <> "" then
this.Go_Script
endif
if this.amspeaking.isOn <> FALSE then
this.db->GUI->Border = 0
this.db->GUI->x = screen_x shr 1 - this.db->GUI->dx shr 1
this.db->GUI->y = screen_y shr 1 - this.db->GUI->dy shr 1
this.db->GUI->FindControl("dialog_text")->text = this.saythis
this.db->GUI->Render
this.db->GUI->ProcessEvents
endif
end sub
-
- Posts: 2655
- Joined: Aug 28, 2008 10:54
- Location: new york
Lesson 4: Agent Manipulation. Oh the things you can do with nothing but strings....
This will take us deeper into the Agent data type and how we can manipulate it, starting right off the enttry point.
#1. Open up "FlatlandRPG.bas" and open up a blank file, save it, and name it whatever you please...
This is the main entry point for the entire system. Also, World Builder.bas, is an entry point, Script Tester.bas is an entry point, Map_editor.bas, and worldtester.bas...
We can crap out entry points at the speed of machines, picking this or that procedure from a vast collection. Note that the map editor, the world builder, the RPG, the scripting tester are all running off (more or less) the same set of procedures (our selection of them).
So let's start with the RPG.
You can copy this code over exactly. Whether or not we need this or that file can be determined later. This fileset is good enough to run everything.
~~~
#2. Copy this over to your new file:
...Try to compile...
If successful we should get a compiler warning, and absolutely nothing else. The compiler warning is more menacing than it sounds. It is also Vince Decampo's fault.
~~~
#3. Now we have to dim our var's before we set up the game loop (next).
Firstly, let's throw some sounds in there.
The files are located in Flatland\Sound, and can be added simply with this macro:
Now, all we have to do instead of cutting and pasting that irritatingly long thing is:
LoadSample ( scream, "Sound/OUCH.wav" )
LoadSample ( lazer, "Sound/LAZER_02.wav" )
Wowee! But we're not finished yet. What good are sounds to us if there are no routines to play them?
the first thing we must do is tell the compiler we are using the HappyMap namespace:
Then we instantiate our World, our GUI's, and variables which can be used by the message handler to pass information:
npc, and inObject record the name of the NPC or Map Object which is clicked upon. It will allow us to search for our Agents and retrieve their information.
Next up is GUI stuff, and setting the default values for the human player.
~~~
#4: Our first game loop
Try to compile...
It will bring a story, you will attempt to hit ok, yet nothing will happen!
#5: Programming for the message handler...
#5: Programming for the message handler...
First of all, get rid of the narration, comment it out, and recompile...
That's my narration and you can't have it!
Anyway, now it's starting to look like something. You should see a blonde child off to the left, a twitchy naked humanoid, and two dudes guarding an angel food cake placed carefully on the table.
But ah! You can't move at all!
This is where the message handler comes into play. Scroll down to your message handler.
Now, add the line underneath "case Mouse_Over":
You will notice that everytime you hover over a GUI object, it returns the tagname.
Now lets set it to do so only when we left click...
Now what we want is the NPCs name. We acquire it like so. Remember, agent.animus->nam_, must
be identical to the value we acquire from the tagname. We do this like so:
if right(.tagname, 3) = "pic" then
npc = left(.tagname, len(.tagname) -3)
endif
We make certain the last 3 characters are "pic" (we set this in the Observer constructor), then we
chop them off, and the result is the NPCs name.
With the NPCs name we can call the inWorld.Find_Agent ( npc ) function, and retrieve the NPC data.
So... now we can retrieve the NPCs name with this:
inWorld.Find_Agent ( npc )->animus->nam_
Remember before how we referred to the human as inWorld.human->animus->nam_.
This function searches a semi-dynamic array for the NPCs animus->nam_ and when it finds
it, it returns a pointer to the NPC. If it cannot find the nam_ it returns a NULL pointer
which is equivalent to 0, and FALSE.
This functions the same way for the human as it does
for the Agent, but Observers would be called using:
inWorld.Find_Observer ( npc )->p.x ( or something )
So, basically. We can access all of the Observer/Agent procedures through a click on the GUI.
Lets play around with them... Here they are...
Save: inWorld.Find_Agent ( npc )->Save ()
ReName: inWorld.Find_Agent ( npc )->Rename ( "Newname" )
Process: should never be called through the GUI
IseeYou: should never be called through the GUI
Fire: needs a helper function to work properly in GUI
Go_Script: inWorld.Find_Agent ( npc )->Go_Script
RunScript: inWorld.Find_Agent ( npc )->RunScript ( "Playerdata/Hodgkins Fairsteed.p1.script")
RunString: inWorld.Find_Agent ( npc )->RunString ("beep:beep:beep")
CallFunction: should never be called through the GUI
DialogBox: inWorld.Find_Agent ( npc )->DialogBox ("You fool!")
DialogReply: inWorld.Find_Agent ( npc )->DialogReply ()
From Observer.bi, only
are generally useful.
Now lets make it run a string on a click...
Next up... The Dialog Engine
Here is the code for the work along source:
This will take us deeper into the Agent data type and how we can manipulate it, starting right off the enttry point.
#1. Open up "FlatlandRPG.bas" and open up a blank file, save it, and name it whatever you please...
This is the main entry point for the entire system. Also, World Builder.bas, is an entry point, Script Tester.bas is an entry point, Map_editor.bas, and worldtester.bas...
We can crap out entry points at the speed of machines, picking this or that procedure from a vast collection. Note that the map editor, the world builder, the RPG, the scripting tester are all running off (more or less) the same set of procedures (our selection of them).
So let's start with the RPG.
Code: Select all
#define screen_x 800
#define screen_y 600
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#include "fbgfx.bi"
#include once "inc/KwikGUI.bi"
#include once "inc/strings.bas"
#libpath "lib"
#inclib "KwikGUI"
declare sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
declare sub KeyHandler ()
#include "inc/sound.bas"
#include "ob.bas"
#include "tiles.bas"
#include "map.bas"
#include "projectile.bas"
#include "agent.bas"
#include "agent_scripting.bas"
#include "map_object.bas"
#include "world.bas"
~~~
#2. Copy this over to your new file:
Code: Select all
sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
Dim As _kGUI_Control Ptr Ctrl = Cast(_kGUI_Control Ptr, EventData.Ctrl)
Dim As _kGUI Ptr GUI = Cast(_kGUI Ptr, EventData.GUI)
dim as coord_type click
With *Ctrl
Select Case EventMsg
Case Mouse_Over
end select
end with
end sub
sub KeyHandler ()
end sub
If successful we should get a compiler warning, and absolutely nothing else. The compiler warning is more menacing than it sounds. It is also Vince Decampo's fault.
~~~
#3. Now we have to dim our var's before we set up the game loop (next).
Firstly, let's throw some sounds in there.
Code: Select all
dim shared as integer ptr lazer: lazer = FSOUND_Sample_Load(FSOUND_FREE, "Sound/LAZER_02.wav", 0, 0, 0)
dim shared as integer ptr scream: scream = FSOUND_Sample_Load(FSOUND_FREE, "Sound/OUCH.wav", 0, 0, 0)
Code: Select all
#macro LoadSample ( varname, filename )
dim shared as integer ptr varname
varname = FSOUND_Sample_Load(FSOUND_FREE, filename, 0, 0, 0)
#endmacro
LoadSample ( scream, "Sound/OUCH.wav" )
LoadSample ( lazer, "Sound/LAZER_02.wav" )
Wowee! But we're not finished yet. What good are sounds to us if there are no routines to play them?
...Back to dimming vars...*Thing to Do: Play with sound...
Luckily, for us, there are:...Add this and compile...Code: Select all
PlaySample ( scream ) sleep
Now find sounds of your own, and download audacity!
The sound routines are located in "inc/sound.bas" and are provided by Lachie Dazdarian.
Note that if PlaySound is set to 0, sound routines will exit out before playing.
Also note what happens when you take away sleep.
the first thing we must do is tell the compiler we are using the HappyMap namespace:
Code: Select all
Using HappyMap
Code: Select all
dim shared as World myWorld
myWorld.Init ( "Worlds/filename.world" )
Dim shared Main As _kGUI_Design = _kGUI_Design("Widgets/game.kwk",@MsgHandler)
dim shared Inventory as _kGUI_Design = _kGUI_Design("Widgets/inventory.kwk",@MsgHandler)
dim shared selection as integer
dim shared as string npc, inObject
Next up is GUI stuff, and setting the default values for the human player.
Code: Select all
myWorld.Find_Agent (myWorld.human->animus->nam_)->Introduced = TRUE
'The human data type is identical to the NPC in every conceivable way,
'but the program will treat it differently in certain instances...
'We can check to see if an NPC or Enemy is the human from this flag
'myWorld.human->animus->isHuman = TRUE/FALSE
Inventory.GUI->CenterMe
Main.GUI->x = 0
Main.GUI->y = 0
'Pop up for map objects...
Dim mapobjectpop As _kGUI_Control = _kGUI_Control(ctrlPOPUP, @MsgHandler)
With mapobjectpop
.tagname = "objselect" 'Tagnames tell the message handler which gui object is sending the message
.dx = 120
.justify = 0
.alpha = 200
.forecolor = 0
.backcolor = &hffffff
.AddItem ( "Take" )
.AddItem ( "Gaze" )
.AddItem ( "Use" )
Main.GUI->AddControl(@mapobjectpop)
End With
'We can set the human's health...
myWorld.human->animus->health = 5
'We can tell a little story
myWorld.DoNarration ( "Worlds/Preface.txt" )
#4: Our first game loop
Code: Select all
do 'Begin Main Loop
sleep 1
'selection = 0 is the default setting, here, only when the
'inventory GUI is active do we not process these events
if selection = 0 then
Main.GUI->ProcessEvents
KeyHandler ( )
endif
screenlock
if selection <= 0 then 'Don't process the world when
myWorld.Process 'the GUI inventory is active
endif
if selection = 1 then
Inventory.GUI->ProcessEvents
Inventory.GUI->Render
endif
if selection = 0 then Main.GUI->Render
screenunlock
sleep 1
loop until multikey(fb.sc_x)
It will bring a story, you will attempt to hit ok, yet nothing will happen!
#5: Programming for the message handler...
#5: Programming for the message handler...
First of all, get rid of the narration, comment it out, and recompile...
That's my narration and you can't have it!
Anyway, now it's starting to look like something. You should see a blonde child off to the left, a twitchy naked humanoid, and two dudes guarding an angel food cake placed carefully on the table.
But ah! You can't move at all!
This is where the message handler comes into play. Scroll down to your message handler.
Code: Select all
sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
Dim As _kGUI_Control Ptr Ctrl = Cast(_kGUI_Control Ptr, EventData.Ctrl)
Dim As _kGUI Ptr GUI = Cast(_kGUI Ptr, EventData.GUI)
dim as coord_type click
With *Ctrl
Select Case EventMsg
Case Mouse_Over
Code: Select all
Consout(.tagname)
Now lets set it to do so only when we left click...
Code: Select all
if EventData.button = 1 then
Consout (.tagname)
endif
be identical to the value we acquire from the tagname. We do this like so:
if right(.tagname, 3) = "pic" then
npc = left(.tagname, len(.tagname) -3)
endif
We make certain the last 3 characters are "pic" (we set this in the Observer constructor), then we
chop them off, and the result is the NPCs name.
With the NPCs name we can call the inWorld.Find_Agent ( npc ) function, and retrieve the NPC data.
So... now we can retrieve the NPCs name with this:
inWorld.Find_Agent ( npc )->animus->nam_
Remember before how we referred to the human as inWorld.human->animus->nam_.
This function searches a semi-dynamic array for the NPCs animus->nam_ and when it finds
it, it returns a pointer to the NPC. If it cannot find the nam_ it returns a NULL pointer
which is equivalent to 0, and FALSE.
This functions the same way for the human as it does
for the Agent, but Observers would be called using:
inWorld.Find_Observer ( npc )->p.x ( or something )
So, basically. We can access all of the Observer/Agent procedures through a click on the GUI.
Lets play around with them... Here they are...
Code: Select all
declare sub Save ( )
declare sub Rename ( inName as string )
declare sub Process ( )
declare sub IseeYou ( other as observer ptr )
declare sub Fire ( other as observer ptr )
declare sub Follow ( other as observer ptr )
declare sub Go_Script ( )
declare sub RunScript ( filename as string )
declare sub RunString ( inexpr as string )
declare sub CallFunction ( funcname as integer, inexpr as hm_EXPRESSION ptr )
declare sub DialogBox ( text as string )
declare sub DialogReply ( )
Save: inWorld.Find_Agent ( npc )->Save ()
ReName: inWorld.Find_Agent ( npc )->Rename ( "Newname" )
Process: should never be called through the GUI
IseeYou: should never be called through the GUI
Fire: needs a helper function to work properly in GUI
Go_Script: inWorld.Find_Agent ( npc )->Go_Script
RunScript: inWorld.Find_Agent ( npc )->RunScript ( "Playerdata/Hodgkins Fairsteed.p1.script")
RunString: inWorld.Find_Agent ( npc )->RunString ("beep:beep:beep")
CallFunction: should never be called through the GUI
DialogBox: inWorld.Find_Agent ( npc )->DialogBox ("You fool!")
DialogReply: inWorld.Find_Agent ( npc )->DialogReply ()
From Observer.bi, only
Code: Select all
inWorld.Find_Agent ( npc )->animus->p.x, inWorld.Find_Agent ( npc )->animus->p.y
Now lets make it run a string on a click...
Code: Select all
if EventData.button = 1 then
if right(.tagname, 3) = "pic" then
npc = left(.tagname, len(.tagname) -3)
if myWorld.Find_Agent ( npc ) <> FALSE then
myWorld.Find_Agent ( npc )->RunString ("beep:beep:beep")
endif
endif
endif
Here is the code for the work along source:
Code: Select all
#define screen_x 800
#define screen_y 600
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#include "fbgfx.bi"
#include once "inc/KwikGUI.bi"
#include once "inc/strings.bas"
#libpath "lib"
#inclib "KwikGUI"
declare sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
declare sub KeyHandler ()
#include "inc/sound.bas"
#include "ob.bas"
#include "tiles.bas"
#include "map.bas"
#include "projectile.bas"
#include "agent.bas"
#include "agent_scripting.bas"
#include "map_object.bas"
#include "world.bas"
#macro LoadSample ( varname, filename )
dim shared as integer ptr varname
varname = FSOUND_Sample_Load(FSOUND_FREE, filename, 0, 0, 0)
#endmacro
Using HappyMap
dim shared as World myWorld
myWorld.Init ( "Worlds/filename.world" )
Dim shared Main As _kGUI_Design = _kGUI_Design("Widgets/game.kwk",@MsgHandler)
dim shared Inventory as _kGUI_Design = _kGUI_Design("Widgets/inventory.kwk",@MsgHandler)
dim shared selection as integer
dim shared as string npc, inObject
myWorld.Find_Agent (myWorld.human->animus->nam_)->Introduced = TRUE
Inventory.GUI->CenterMe
Main.GUI->x = 0
Main.GUI->y = 0
Dim mapobjectpop As _kGUI_Control = _kGUI_Control(ctrlPOPUP, @MsgHandler)
With mapobjectpop
.tagname = "objselect"
.dx = 120
.justify = 0
.alpha = 200
.forecolor = 0
.backcolor = &hffffff
.AddItem ( "Take" )
.AddItem ( "Gaze" )
.AddItem ( "Use" )
Main.GUI->AddControl(@mapobjectpop)
End With
myWorld.human->animus->health = 5
' myWorld.DoNarration ( "Worlds/Preface.txt" )
do 'Begin Main Loop
sleep 1
if selection = 0 then
Main.GUI->ProcessEvents
KeyHandler ( )
endif
screenlock
if selection <= 0 then
myWorld.Process
endif
if selection = 1 then
Inventory.GUI->ProcessEvents
Inventory.GUI->Render
endif
if selection = 0 then Main.GUI->Render
screenunlock
sleep 1
loop until multikey(fb.sc_x)
sub MsgHandler (EventMsg As _EventMsg, EventData As _EventData)
Dim As _kGUI_Control Ptr Ctrl = Cast(_kGUI_Control Ptr, EventData.Ctrl)
Dim As _kGUI Ptr GUI = Cast(_kGUI Ptr, EventData.GUI)
dim as coord_type click
With *Ctrl
Select Case EventMsg
Case Mouse_Over
if EventData.button = 1 then
if right(.tagname, 3) = "pic" then
npc = left(.tagname, len(.tagname) -3)
if myWorld.Find_Agent ( npc ) <> FALSE then
myWorld.Find_Agent ( npc )->RunString ("beep:beep:beep")
endif
endif
endif
end select
end with
end sub
sub KeyHandler ()
end sub