Author Topic: Smartlava  (Read 3417 times)

Offline Mcshay

  • Administrator
  • Sr. Member
  • *****
  • Posts: 404
Smartlava
« on: September 28, 2006, 04:48:30 PM »
Description

SmartLava provides coders with a way to make random multi-stage lava flows. The coder controls when the volcanoes erupt, how far they can erupt, how fast they erupt, and where they can erupt. Volcanoes running on SmartLava, as opposed to a normal op2 volcano, are designed to erupt in many stages. The coder must plot out a series of locations to be considered LavaNodes, each with it's own maximum radius (extent of lava), and a list of other nodes around it. Each volcano will periodicaly expand to a new node, expand a previous node, or connect two nodes. The time between these changes can be controlled by the coder. The coder can also specify which cell types the lava can spread on, place pathways through the lava, and turn a volcano on or off.

Files:
Current Version: 1.0

Ziped
SmartLava.zip: version 1.0
UnZiped
SmartLava.h: version 1.0
LStructs.h: version 1.0
Lava.lib: version 1.0
ExampleCode
SLexample.zip: version 1.0

Note: SLexample may need to have some linker paths changed to compile.

Instructions:
(using CodeBlocks, I don't have MSCV)

First, download either all of the Unziped files, or the ziped file. Place LStructs.h and SmartLava.h in your op2 SDK's include folder. Also place Lava.lib in your SDK's lib folder.

Then start a new op2 project, and goto: Project -> Build options -> Linker tab. Add both user32.lib and Lava.lib to the list.

Finaly you have to add a few things to Main.cpp:
First, add this under the other #include statements:
Code: [Select]
#include "SmartLava.h"

Then change DLLMain to this:
Code: [Select]
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
if(fdwReason == DLL_PROCESS_ATTACH)
  DisableThreadLibraryCalls(hinstDLL);
    else if(fdwReason == DLL_PROCESS_DETACH)
        SmartLavaCleanUp();

    return TRUE;
}

Next add a call to SmartLavaInit somewhere near the begining of InitProc (before any other SmartLava calls, except SetDoShowErrors). Directly after this call both SetLavaCells and SetImpassibleCells. Finaly add a call to LavaAIProc at the begining of AIProc.

Notes on using SmartLava

Make sure you read the Warnings section near the end of this post!

Plotting LavaNodes

Each project that uses SmartLava also requires an array of LavaNodes, and an array of VolcanoTrees (they can be named anything you want). There are two ways you can do this.

The first, and easiest involves creating both arrays empty, and creating a third array of BasicNodes. BasicNodes are simpler versions of LavaNodes designed for easy array creation. You must convert the BasicNodes to LavaNodes after you call SmartLavaInit. Also, remember that the constructor for a Basic Node is as follows: BasicNode(LOCATION loc, int maxradius, int numNeighbors, ...); Meaning that after it's location, and radius, you must put the number of neighbor indexes that follow in this call (also note that an index is the place in the array of the neighbor, starting at zero).

The second way is similar to the first. You create the LavaNode array in a similar fashion to the BasicNode array, however it doesn't take the list of neighbor indexes. You must have another line of code in InitProc for each LavaNode. This may seem alright for a small map, but stupid on a large map.

Note: the below node definitions may look really weird in op2, I just gave them random locations to show how to declare them.

The First Way:
The Second Way:
Code: [Select]
int NumNodes = 4;
LavaNode LavaNodes[4];
BasicNode DefNodes[4] = {
    BasicNode(LOCATION(15+31,10-1), 3, 2, 2, 3),
    BasicNode(LOCATION(21+31,13-1), 4, 1, 2),
    BasicNode(LOCATION( 8+31, 4-1), 6, 3, 0, 1, 3),
    BasicNode(LOCATION(17+31,12-1), 5, 2, 0, 2)
};

int NumVolcanoes = 2;
VolcanoTree Volcanoes[2];

int InitProc()
{
    SmartLavaInit(LavaNodes, NumNodes, Volcanoes, NumVolcanoes);
    
    for(int i = 0; i < NumNodes; i++)
        LavaNodes[i] = LavaNode(&DefNodes[i]);
    ...//other code
    return 1;
}

The Second Way:
Code: [Select]
int NumNodes = 4;
LavaNode LavaNodes[4] = {
    LavaNode(LOCATION(15+31,10-1), 3),
    LavaNode(LOCATION(21+31,13-1), 4),
    LavaNode(LOCATION( 8+31, 4-1), 6),
    LavaNode(LOCATION(17+31,12-1), 5)
};

int NumVolcanoes = 2;
VolcanoTree Volcanoes[2];

int InitProc()
{
    SmartLavaInit(LavaNodes, NumNodes, Volcanoes, NumVolcanoes);
    
    PopulateNeighbors(&LavaNodes[0], 2, &LavaNodes[2], &LavaNodes[3]);
    PopulateNeighbors(&LavaNodes[1], 1, &LavaNodes[2]);
    PopulateNeighbors(&LavaNodes[2], 3, &LavaNodes[0], &LavaNodes[1], &LavaNodes[3]);
    PopulateNeighbors(&LavaNodes[3], 2, &LavaNodes[0], &LavaNodes[2]);
    ...//other code
    return 1;
}

Erupting a volcano

The way you erupt a volcano is mostly up to you. However you are required to register the volcano in an empty slot in the VolcanoTree array. Here are two ways to erupt the same volcano (assuming that we use the same code as above):

First way (most control):
Code: [Select]
int InitProc()
{
    ...//Initialization code goes here
    SetLavaCells(2, cellSlowPassible1, cellMediumPassible2);//tells SmartLava to let the lava spread on S1 and M2 cell types
    SetImpassibleCells(5, cellImpassible1, cellImpassible2, cellNorthCliffs,
        cellCliffsHighSide, cellCliffsLowSide);//specifies the impassible cell types
    
    CreateTimeTrigger(true, true, 400, "SetVolcano1");//fires at mark 4
    return 1;
}

SCRIPT_API void SetVolcano1() //lava flowing animation
{
    FlowSW(LOCATION(66+31, 69-1));
    CreateTimeTrigger(true, true, 100, "TriggerVolcano1");//1 mark
}

SCRIPT_API void TriggerVolcano1()//sets the actual eruption
{
    GameMap::SetLavaPossible(LOCATION(65+31, 71-1), true);
    TethysGame::SetEruption(65+31, 71-1, 100);
    CreateTimeTrigger(true, true, 1000, "EruptVolcano1");//10 marks
}

SCRIPT_API void EruptVolcano1()//creates the volcano tree when the eruption goes off
{
    FreezeSW(LOCATION(66+31, 69-1));
    LoadVolcano(&Volcanoes[0], -1, &LavaNodes[0], LavaNodes[0].MaxRadius, true, 3000, 3750);
}

Second way (erupts at mark 10 every time):
Code: [Select]
int InitProc()
{
    ...//Initialization code goes here
    SetLavaCells(2, cellSlowPassible1, cellMediumPassible2);//tells SmartLava to let the lava spread on S1 and M2 cell types
    SetImpassibleCells(5, cellImpassible1, cellImpassible2, cellNorthCliffs,
        cellCliffsHighSide, cellCliffsLowSide);//specifies the impassible cell types
    
    GameMap::SetLavaPossible(LOCATION(65+31, 71-1), true);
    TethysGame::SetEruption(65+31, 71-1, 100);
    CreateTimeTrigger(true, true, 1000, "LoadVolcano1");//fires at mark 10
    return 1;
}

SCRIPT_API void EruptVolcano1()
{
    LoadVolcano(&Volcanoes[0], -1, &LavaNodes[0], LavaNodes[0].MaxRadius, true, 3000, 3750);
}

The last two sections can be seen in action in the coding example linked too earlier. It will run on op2.

Paths

Paths give you a way to stop lava from covering a random area, perhaps to randomly create a pass through a channel of lava. The below code assumes a project has an array of 6 LavaNodes in a row (that is, each one has a neighbor to it's left and right, exept the end two):

Code: [Select]
int PathGrp1Size = 4;
Path PathGrp1[4] = {Path(1, 1), Path(1, 2), Path(1, 3), Path(1, 4)};
//The path array contains 4 possible paths, each Path constructor takes the number of LavaNode indexes (starting at 0) that the path consists of.

int InitProc()
{
    ...//Initialization code goes here
    SetLavaCells(2, cellSlowPassible1, cellMediumPassible2);//tells SmartLava to let the lava spread on S1 and M2 cell types
    SetImpassibleCells(5, cellImpassible1, cellImpassible2, cellNorthCliffs,
        cellCliffsHighSide, cellCliffsLowSide);//specifies the impassible cell types
    //The next two lines assume functions exist to create two volcanoes. The volcanoes will fill up the nodes around the path
    CreateTimeTrigger(true, true, 1000, "SetVolcano1");
    CreateTimeTrigger(true, true, 1000, "SetVolcano2");
    MarkPath(PathGrp1, PathGrp1Size);//The path is randomly selected and preserved
    return 1;
}

Sending Messages

You can send VolcanoMessages to each initialised VolcanoTree using the WriteMessage(VMessage VMdesc, int ProcessTick, int VolcanoIndex, VPointer BodyA, VPointer BodyB, VPointer BodyC); function, like so:
Code: [Select]
WriteMessage(vmNewNode, 5000, 0, &LavaNodes[1], &LavaNodes[0], Alloc(4));

That message tells VolcanoTree[0] to spread to LavaNode[0] from LavaNode[1] at mark 50. If the ProcessTick parameter is less than or equal to the current tick, the message will be processed as soon as it can be. All non-pointer parameters (read: ints and bools) passed to WriteMessage need to be placed inside of the Alloc() function.

Debuging Features

SmartLava makes heavy use of the MessageBox windows function to help the coder find errors in his/her maps (hence the user32.lib link). To turn on the message box feature, place a call to SetDoShowErrors before as the first line of InitProc with a value of true. You can call it again with a value of false to turn it off.

You can also call the DrawAllConnections and FloodAllNodes functions to change the tiles on the map to show LavaNode relations, and the extent of the lava flow (FloodAllNodes requires an eruption to fill it in).

DrawAllConnections will draw a line between two nodes if one node considers the other a neighbor, the line is color coded to this list:
- orange: one way connection
- white: both ways
- ice tiles: frozen connection

How it all Works
This next section isn't nessisary to use SmartLava, but it can help understand how it works, and how to use it better.

SmartLava has the coder create two fixed arrays. One full of LavaNodes, and one full of VolcanoTrees. The LavaNode array must be initialised with locations, radii, and each node's neighbors.

Each LavaNode consists of it's max radius, current radius, location, NodeType, and a vector of NeighborData classes. Each NeighborData contains a pointer to the neighbor being referenced and the type of connection between them. Any two nodes have one of three possible relationships: no relation (neither of them list eachother as neighbors), a one way relation (only one of them lists the other as a neighbor), or a two way relation (they both list each other). Any listed relations can be temorarily frozen, meaning they arn't considered neighbors by the spreading code, but are still connected.

When SmartLavaInit is called, the coder sends SmartLava a pointer to both arrays, and their size. All four parameters are saved for future use. In a way, the coder owns all the nodes and volcanoes, but SmartLava controls them from a distance.

When SmartLavaInit is called, a copy of the map's cell layer is made. This copy is what SmartLava does all cell checking against. The ChangeMapCellType function changes a cell on this copy.

When a volcano is loaded, the specified VolcanoTree is filled with the information provided, and is given a starting node. It assumes an eruption has been triggered on this node. Each volcano keeps a timer as to when it will spread next. The spreading can be stopped and started by the coder.

Each volcano is also given an index used by SmartLava to access it's message queue. Each volcano is assigned a message queue when it is loaded.

When LavaAIProc is called, each volcano's timer is updated, and spreads if it needs to. Each volcano's message queue is also checked for messages that need to be read. Note: unloaded volcanoes don't do anything in LavaAIProc.

When a lava flow spreads, each tile to be flooded is checked against the provided list of lava passable cell types. If it is a match, it will be flooded. It's the coder's responsiblity to make sure no white or orange tiles are flooded.

Also, note that marking a path only freezes the connections between the nodes on the path and their neighbors.

Current Warnings:

- Do not use SmartLava with a world map, a patch is planned for this
- Do not load a volcano more than once, bad things will happen
- Do not pass any function an index greater than the number of items in the array minus 1
- Make sure the two arrays given to SmartLavaInit are global
- Make sure you fill in all needed functions properly
- Make sure you specify the correct number of items in any function that takes a list

Fun Facts

- There are over 1,500 lines of code in SmartLava
- There are 11 hidden functions
- There are 16 hidden variables
- There is a hidden structure definition
- This post took an hour and a half to write

Offline 7842303

  • Jr. Member
  • **
  • Posts: 81
Smartlava
« Reply #1 on: September 12, 2007, 08:02:22 PM »
all i have to say is...    


COOL, WOW, AWSOME, ECT.!!!!!!!!!!!!!!!!!!!!!! (thumbsup)  
onword to battle!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
unless your tired, of cource

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Smartlava
« Reply #2 on: September 12, 2007, 08:19:31 PM »
Hmm, yes, an interesting project.
 

Offline Leviathan

  • Hero Member
  • *****
  • Posts: 4055
Smartlava
« Reply #3 on: September 12, 2007, 08:31:25 PM »
Great work Mcshay! If I had the time I would like to play around with this.

Care to post a sample DLL of it in action?

Thanks :)

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1269
Smartlava
« Reply #4 on: September 12, 2007, 08:43:39 PM »
You've only noticed this thread nearly a year after it was posted?

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Smartlava
« Reply #5 on: September 12, 2007, 09:44:07 PM »
Yeah, basically. Just kind of assumed it was new since McShay seems to be active doing stuff lately.  

Offline Leviathan

  • Hero Member
  • *****
  • Posts: 4055
Smartlava
« Reply #6 on: September 12, 2007, 09:55:43 PM »
Haha lol. I did not see that time stamp.

Offline 7842303

  • Jr. Member
  • **
  • Posts: 81
Smartlava
« Reply #7 on: October 14, 2007, 01:12:31 AM »
that is quite a thread resurection!!!!!!!!!!!!!!!! :blink:  
onword to battle!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
unless your tired, of cource