Flatland RPG - With World Builder & Map Editor

User projects written in or related to FreeBASIC.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Flatland RPG - With World Builder & Map Editor

Postby rolliebollocks » Mar 05, 2009 11:43

http://freefile.kristopherw.us/uploads/rolliebollocks/flatland_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
Last edited by rolliebollocks on Mar 12, 2009 17:40, edited 5 times in total.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 05, 2009 11:44

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.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 05, 2009 11:45

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.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 05, 2009 11:59

If you try out Flatland .exe use the X key to quit the game. Escape resets the game state.

Ideas are welcome and appreciated.

The engine is currently being developed for game play which means the editor is on hiatus until the game is finished.

rb
vdecampo
Posts: 2977
Joined: Aug 07, 2007 23:20
Location: Maryland, USA
Contact:

Postby vdecampo » Mar 08, 2009 20:06

Did the latest version of KwikGUI solve your issue with the popup window?
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 09, 2009 10:37

Yep! It's working perfectly.

rb
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 09, 2009 12:34

Update!

-Dialog fixed and improved.

It now runs like a proper demo should run.

There are a lot of bugs, and things I just haven't figured out how to attack yet.

Please take a look, and let me know what you think.

rb
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 09, 2009 12:42

BE WARNED!

If you should begin attempting to play around with the editor you will notice a glitch where the new NPC that you put on the map always reloads as the player1 image...

This needs to be manually updated in the .agentdata file. The reference to the file I believe is recorded wrong.

rb
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 09, 2009 14:56

Update!

Optimized map routines. It is now several gajillion times faster and can load on a modest machine a 2000x2000 grid.

rb
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 10, 2009 20:39

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:

Image[/img]
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 10, 2009 21:24

Oh yeah. I made a script testing program like the move the robot demo.

Script Tester.exe
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 11, 2009 11:52

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

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


Simple add the reference at the end of the list.

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_
   
    ...



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

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 )

...


6: Somewhere in the bounds of the select case block add your function like so:

Code: Select all

CASE _MYNEWFUNCTION_
    end


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
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 11, 2009 20:44

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:

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 )


What?

Code: Select all

this.animus->widge->GUI->FindControl(this.animus->nam_ & "pic")->text = inexpr->rhs


Whenever you assign a value to be sent to the Message Handler, you will assign it to this:

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


Underneath Mouse_Over we will have to check our EventData.button:

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.
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 12, 2009 13:43

Lesson 3: Everything you need to know about the NPC data type.

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


Okay. Now step by step through the functions. Starting with the Constructor.

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 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

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
rolliebollocks
Posts: 2655
Joined: Aug 28, 2008 10:54
Location: new york

Postby rolliebollocks » Mar 12, 2009 17:37

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.

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"


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:

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


...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.

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)


The files are located in Flatland\Sound, and can be added simply with this macro:

Code: Select all

#macro LoadSample ( varname, filename )
   dim shared as integer ptr varname
   varname = FSOUND_Sample_Load(FSOUND_FREE, filename, 0, 0, 0)
#endmacro


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?

*Thing to Do: Play with sound...

Luckily, for us, there are:

Code: Select all

PlaySample ( scream )
sleep


...Add this and compile...

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.



...Back to dimming vars...

the first thing we must do is tell the compiler we are using the HappyMap namespace:

Code: Select all

Using HappyMap


Then we instantiate our World, our GUI's, and variables which can be used by the message handler to pass information:

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


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.

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)


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.

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


Now, add the line underneath "case Mouse_Over":

Code: Select all

Consout(.tagname)


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...

Code: Select all

if EventData.button = 1 then
    Consout (.tagname)
endif


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...

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


are generally useful.

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


Next up... The Dialog Engine

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

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 2 guests