Experimental maze parkour scenario. Collect a ruby and return it to the start to win.

Crashes sometimes during map generation. Couldn't reproduce this in a debug build yet :(
scancodes-fix
Sven Eberhardt 2013-04-06 18:02:56 +02:00
parent c17c971b40
commit f244efbc86
26 changed files with 753 additions and 0 deletions

View File

@ -0,0 +1,5 @@
[DefCore]
id=Goal_RubyHunt
Version=5,2,0,1
Category=C4D_StaticBack|C4D_Goal
Picture=0,0,128,128

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,85 @@
/*--
Ruby hunt
Author: Sven2
Mine one ruby and return to start
--*/
#include Library_Goal
local goal_rect, has_winner;
protected func Initialize()
{
return inherited(...);
}
func SetGoalRect(r)
{
goal_rect = r;
return true;
}
/*-- Goal interface --*/
// The goal is fulfilled if a ruby is in the goal rectangle
public func IsFulfilled()
{
var winner=NO_OWNER, winners, winner_teams;
if (has_winner) return true;
for (var ruby in FindObjects(Find_InRect(goal_rect.x, goal_rect.y, goal_rect.w, goal_rect.h), Find_ID(Ruby)))
{
if (ruby->Contained()) winner = ruby->Contained()->GetOwner();
if (winner==NO_OWNER) winner = ruby->GetController();
if (winner==NO_OWNER) continue;
if (!winners) winners = [winner]; else winners[GetLength(winners)] = winner;
var team = GetPlayerTeam(winner);
if (team) if (!winner_teams) winner_teams = [team]; else winner_teams[GetLength(winner_teams)] = team;
}
if (!winners) return false;
has_winner = true;
var iplr=GetPlayerCount();
while (iplr--)
{
var plr = GetPlayerByIndex(iplr);
if (GetIndexOf(winners, plr) >= 0) continue;
if (winner_teams) if (GetIndexOf(winner_teams, GetPlayerTeam(plr)) >= 0) continue;
EliminatePlayer(plr);
}
return true;
}
// Shows or hides a message window with information.
public func Activate(int plr)
{
// If goal message open -> hide it.
if (GetEffect("GoalMessage", this))
{
CustomMessage("", nil, plr, nil, nil, nil, nil, nil, MSG_HCenter);
RemoveEffect("GoalMessage", this);
return;
}
// Otherwise open a new message.
AddEffect("GoalMessage", this, 100, 0, this);
var message;
if (IsFulfilled())
message = "@$MsgGoalFulfilled$";
else
message = "@$MsgGoalUnfulfilled$";
CustomMessage(message, nil, plr, 0, 16 + 64, 0xffffff, GUI_MenuDeco, this, MSG_HCenter);
return;
}
protected func FxGoalMessageStart() {}
public func GetShortDescription(int plr)
{
return nil;
}
/*-- Proplist --*/
local Name = "$Name$";

View File

@ -0,0 +1,4 @@
Name=Rubinsuche
#Goal window
MsgGoalFulfilled=Ein Rubin wurde abgeliefert.
MsgGoalUnfulfilled=Noch kein Rubin am Start.

View File

@ -0,0 +1,5 @@
Name=Sell gems
#Goal window
MsgGoalFulfilled=A ruby has been delivered.
MsgGoalUnfulfilled=Mine a ruby and deliver it to the starting platform.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,79 @@
/*--
Cool Cavern
Author: Maikel
Cavern with lots of snow, ice, rock and dirt.
--*/
// Randomly placed material specks according to rndchecker.
overlay MatRC {
algo=rndchecker; a=8;
zoomX=-50; zoomY=-50;
turbulence=100; loosebounds=1;
};
// Fills an overlay with earth and materials.
overlay MatFill {
overlay { mat=Earth; tex=earth_dry; loosebounds=1; };
MatRC { mat=Ore; tex=ore; a=20; };
MatRC { mat=Snow; tex=snow1; a=20; };
MatRC { mat=Granite; tex=granite; a=20; };
MatRC { mat=Rock; tex=rock; a=20; };
MatRC { mat=Ore; tex=ore; a=20; };
MatRC { mat=Ice; tex=ice3; a=20; };
MatRC { mat=Snow; tex=snow1; a=20; };
MatRC { mat=Ice; tex=ice3; };
MatRC { mat=Rock; tex=rock; };
MatRC { mat=Tunnel; tex=tunnel; };
MatRC { mat=Earth; tex=earth_dry; };
MatRC { mat=Earth; tex=earth_rough; };
overlay {
algo=lines; a=3; b=16;
rotate=45;
turbulence=100;
mat=Tunnel; tex=tunnel;
};
overlay {
algo=lines; a=3; b=16;
rotate=-45;
turbulence=100;
mat=Tunnel; tex=tunnel;
};
};
// Randomly placed material specks according to bozo.
overlay MatBozo {
algo=bozo; a=5;
turbulence=1000; loosebounds=1;
};
// Fills an overlay with ice, tunnel, rock and granite.
overlay BorderFill {
overlay { mat=Rock; tex=rock; loosebounds=1;
MatBozo { mat=Tunnel; tex=tunnel; };
MatBozo { mat=Rock; tex=rock_cracked; a=6; };
MatBozo { mat=Granite; tex=granite; a=14; };
MatBozo { mat=Ice; tex=ice3; a=8; };
MatBozo { mat=Tunnel; tex=tunnel; };
};
};
// A lengthy vertical cavern surrounded by ice, dirt and rock.
map Cavern {
overlay {
// Cut cavern out of the landscape.
x=40; wdt=20; y=-6; hgt=100;
turbulence=100; lambda=4;
loosebounds=1;
} ^ overlay {
// Fill remaining area with MatFill.
MatFill;
// And create a border around this area.
overlay {
algo=border; a=4; b=4;
// Fill border with BorderFill.
BorderFill;
};
};
};

View File

@ -0,0 +1,261 @@
/*--
Dynamic maze
Author: Sven2
--*/
#include Library_Map
static g_caves;
local caves, n_caves, start_cave, end_cave;
func FindCaves(int n)
{ //n=6;
var mask = CreateLayer();
mask->Draw("Rock");
caves = [];
var min_cave_dist = 12, border = 5;
while (n--)
{
var cave = {};
if (!mask->FindPosition(cave, "Rock", [border,border,this.Wdt-border*2,this.Hgt-border*2])) continue;
mask->Draw("Tunnel", {Algo=MAPALGO_Ellipsis, X=cave.X, Y=cave.Y, Wdt=min_cave_dist, Hgt=min_cave_dist});
cave.links = [];
cave.dirs = 0;
cave.rand = Random(65536);
cave.depth = -1;
caves[n_caves++] = cave;
}
/* caves[0].X = 10; caves[0].Y = 10;
caves[1].X = 30; caves[1].Y = 10;
caves[2].X = 10; caves[2].Y = 30;
caves[3].X = 30; caves[3].Y = 70;
caves[4].X = 50; caves[4].Y = 10;
caves[5].X = 50; caves[5].Y = 30;*/
return n_caves;
}
func GetCaveLinkDir(c1, c2)
{
// Returns if c2 is left (1), right (2), atop (4) or below (8) c1. Returns only one direction.
var dx=c2.X-c1.X, dy=c2.Y-c1.Y, adx=Abs(dx), ady=Abs(dy);
//Log("%d,%d to %d,%d dx=%d dy=%d", c1.X, c1.Y, c2.X, c2.Y, dx, dy);
return (dx<-ady) | (dx>ady)<<1 | (dy<=-adx)<<2 | (dy>=adx)<<3;
}
func IsLineOverlap(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
// Check if line from x1,y1 to x2,y2 crosses the line from x3,y3 to x4,y4
var d1x=x2-x1, d1y=y2-y1, d2x=x4-x3, d2y=y4-y3, d3x=x3-x1, d3y=y3-y1;
var a = d1y*d3x-d1x*d3y;
var b = d2y*d3x-d2x*d3y;
var c = d2y*d1x-d2x*d1y;
if (!c) return !a && Inside(x3, x1,x2) && Inside(y3, y1,y2); // lines are parallel
return a*c>=0 && !(a*a/(c*c+1)) && b*c>=0 && !(b*b/(c*c+1));
}
func FindCaveConnections()
{
var i, j, cave, cave2, caves2, dir, dir2, n, check_link, all_links = [];
// Connect all caves to neighbours
for (i=0; i<n_caves; ++i)
{
cave = caves[i];
caves2 = caves[i+1:n_caves];
for (cave2 in caves2) cave2.d = Distance(cave.X, cave.Y, cave2.X, cave2.Y);
SortArrayByProperty(caves2, "d");
for (cave2 in caves2)
{
// Make sure there's a max of one connection per direction (up/left/right/down)
dir = GetCaveLinkDir(cave, cave2);
//Log("Cave %d to %d direction %d.", GetIndexOf(caves, cave), GetIndexOf(caves, cave2), dir);
if (cave.dirs & dir) continue;
dir2 = dir >> 1& 5|10 &dir << 1;
//Log("dir %d opposite to %d.", dir2, dir);
if (cave2.dirs & dir2) continue;
// Make sure the connectors don't cross each other
// Note that connectors may still overlap the edges of caves, effectively connecting them
// But since nothing really "breaks" on this occasion, just stick with the simple check for now.
var has_overlap = false;
for (check_link in all_links)
if (check_link[0] !== cave && check_link[1] !== cave)
if (check_link[0] !== cave2 && check_link[1] !== cave2)
if (IsLineOverlap(cave.X, cave.Y, cave2.X, cave2.Y, check_link[0].X, check_link[0].Y, check_link[1].X, check_link[1].Y))
{ has_overlap=true; break; }
if (has_overlap) continue;
// Connect these caves
cave.links[GetLength(cave.links)] = cave2;
cave2.links[GetLength(cave2.links)] = cave;
cave.dirs |= dir;
cave2.dirs |= dir2;
// Register and count connections
all_links[n++] = [cave, cave2];
// All directions connected?
if (cave.dirs == 0xf) break;
}
}
return n;
}
func FindStart()
{
SortArrayByProperty(caves, "X");
start_cave = caves[0];
return start_cave;
}
func MakeMaze()
{
// Make maze by removing unnecesseriy links
start_cave.path = [];
var open = [start_cave], n_open = 1;
while (n_open)
{
var i_cave = n_open-1-Random(1+Random(n_open)); // Prefer depth-first generation so stray paths are deeper
var cave = open[i_cave];
open[i_cave] = open[--n_open]; // SetLength(open, n_open) not nessessery because length is stored in n_open
var path_length = GetLength(cave.path);
var path_to_cave = cave.path[:];
path_to_cave[path_length] = cave;
cave.depth = path_length;
for (var cave2 in cave.links[:]) // force a copy because cave.links is modified in the loop
{
if (path_length && cave2 === path_to_cave[path_length-1]) continue;
// Only first path survives
if (cave2.path)
{
// Remove circular path
RemoveCaveLinks(cave, cave2);
}
else
{
// Remember path to this cave
cave2.path = path_to_cave;
open[n_open++] = cave2;
}
}
}
// Sort from start to goal
SortArrayByProperty(caves, "depth");
// Close one connection 2/3rds of the way to the goal
var main_path = caves[n_caves-1].path;
var main_path_length = GetLength(main_path);
if (main_path_length > 5) RemoveCaveLinks(main_path[main_path_length*2/3], main_path[main_path_length*2/3+1]);
// Kill unreachable caves
var i;
for (i=0; i<n_caves; ++i) if (caves[i].depth>=0) break;
caves = caves[i:n_caves];
n_caves -= i;
end_cave = caves[n_caves-1];
return true;
}
func RemoveCaveLinks(c1, c2)
{
var i = GetIndexOf(c1.links, c2), n = GetLength(c1.links) - 1;
if (i>=0)
{
c1.links[i] = c1.links[n];
SetLength(c1.links, n);
c1.dirs &= ~GetCaveLinkDir(c1,c2);
}
i = GetIndexOf(c2.links, c1); n = GetLength(c2.links) - 1;
if (i>=0)
{
c2.links[i] = c2.links[n];
SetLength(c2.links, n);
c2.dirs &= ~GetCaveLinkDir(c2,c1);
}
return true;
}
func DrawVariations(string mat, int ratio, int sx, int sy)
{
var rand_algo = {Algo=MAPALGO_RndChecker, Ratio=ratio, Wdt=sx, Hgt=sy};
var turb_algo = {Algo=MAPALGO_Turbulence, Amplitude=12, Scale=8, Op=rand_algo};
return Draw(mat, turb_algo);
}
func DrawBackground()
{
Draw("Rock");
DrawVariations("Rock-rock_cracked", 50, 5,15);
DrawVariations("Ore", 10, 8,8);
DrawVariations("Sulphur", 8, 12,3);
DrawVariations("Coal", 8, 8,3);
DrawVariations("Gold", 5, 4,4);
DrawVariations("Granite", 14, 15,5);
DrawVariations("Granite", 14, 5,15);
return true;
}
func DrawCaves()
{
for (var cave in caves)
{
Draw("Tunnel", {Algo=MAPALGO_Ellipsis, X=cave.X, Y=cave.Y, Wdt=4, Hgt=4});
//var src = cave.path;
//if (src && GetLength(src)) src = Format("%d,%d", src[GetLength(src)-1].X, src[GetLength(src)-1].Y);
//Log("Cave at %d,%d src %v", cave.X, cave.Y, src);
}
return true;
}
func DrawTunnels()
{
for (var cave in caves)
{
for (var cave2 in cave.links)
{
if (cave2.done) continue;
Draw("Tunnel", {Algo=MAPALGO_Polygon, X=[cave.X, cave2.X], Y=[cave.Y, cave2.Y], Wdt=2, Open=1, Empty=1 });
}
cave.done = true;
}
return true;
}
func DrawStart()
{
Draw("Tunnel", nil, [0, start_cave.Y - 4, start_cave.X, 4]);
Draw("Brick", nil, [0, start_cave.Y, start_cave.X-2, 1]);
return true;
}
func DrawEnd()
{
Draw("Ruby", {Algo=MAPALGO_Ellipsis, X=end_cave.X, Y=end_cave.Y, Wdt=4, Hgt=4});
return true;
}
protected func InitializeMap(map)
{
map->Resize(300,300);
FindCaves(200);
FindCaveConnections();
FindStart();
MakeMaze();
DrawBackground();
DrawTunnels();
DrawCaves();
DrawStart();
DrawEnd();
// Caves to global var: Need to clean up circular prop list references because they cause crashes
var cave;
for (cave in end_cave.path)
{
cave.is_main_path = true;
}
for (cave in caves)
{
cave.n_links = GetLength(cave.links);
cave.links = cave.path = nil;
}
g_caves = caves;
return true;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,13 @@
[Material]
Name=Ruby
Shape=Rough
Density=70
Friction=15
BlastFree=1
Blast2Object=Ruby
Blast2ObjectRatio=100
MaxAirSpeed=100
MaxSlide=1
Corrode=60
Placement=21
TextureOverlay=Ruby

View File

@ -0,0 +1,46 @@
OverloadMaterials
OverloadTextures
10=Tunnel-tunnel
12=Tunnel-brickback
13=BrickSoft-brick1
19=DuroLava-lava_red
20=Water-water1-water2-water3-water1-water3-water2
22=Acid-acid
23=Lava-lava_red
25=Water-water
28=Earth-earth
29=Earth-earth_dry
30=Earth-earth_rough
31=Earth-earth_topsoil
32=Earth-earth_midsoil
33=Ashes-ashes
36=Ore-ore
40=Granite-granite
42=Granite-rock
45=Gold-gold
50=Rock-rock
51=Rock-rock_cracked
53=Sulphur-sulphur
54=Coal-coal
55=Sand-sand_rough
56=Sand-sand_smooth
60=Ruby-Ruby
65=Ice-ice2
67=Ice-ice3
70=Snow-snow1
73=Brick-brick1

View File

@ -0,0 +1,40 @@
[Head]
Icon=34
Title=Maze
Version=5,2,0,1
Difficulty=20
[Definitions]
Definition1=Objects.ocd
Definition2=Experimental.ocd\Gems.ocd
[Game]
Goals=Goal_RubyHunt=1;
[Player1]
Crew=Clonk=1
[Player2]
Crew=Clonk=1
[Player3]
Crew=Clonk=1
[Player4]
Crew=Clonk=1
[Landscape]
Sky=Clouds2
MapWidth=100
MapHeight=100
MapZoom=10
TopOpen=0
BottomOpen=0
SkyScrollMode=2
[Weather]
Climate=0,0,0,100
StartSeason=0,0,0,100
YearSpeed=0,0,0,100
Wind=1,100,-100,100

View File

@ -0,0 +1,123 @@
/*--
Maze
Author: Sven2
Dynamic maze
--*/
static g_caves;
local goal_cave;
func InitializePlayer(int plr)
{
// Harsh zoom range
for (var flag in [PLRZOOM_LimitMax, PLRZOOM_Direct])
SetPlayerZoomByViewRange(plr,300,200,flag);
//SetPlayerZoomByViewRange(plr,LandscapeWidth(),LandscapeHeight(),flag);
SetPlayerViewLock(plr, true);
// Position and materials
LaunchPlayer(plr);
return true;
}
func LaunchPlayer(int plr)
{
// Position and materials
var starting_cave = g_caves[0];
var i, crew, obj;
for (i=0; crew=GetCrew(plr,i); ++i)
{
crew->SetPosition(starting_cave.X/2, starting_cave.Y-18);
for (var tool in [Pickaxe, GrappleBow])
if (obj = FindObject(Find_ID(tool), Find_Owner(plr), Find_NoContainer()))
obj->Enter(crew);
else
crew->CreateContents(tool);
crew->CreateContents(Loam,2);
crew->CreateContents(Dynamite,2);
}
return true;
}
func RelaunchPlayer(int plr)
{
var clonk = CreateObject(Clonk,0,0,plr);
if (!clonk) return false;
clonk->MakeCrewMember(plr);
SetCursor(plr, clonk);
return LaunchPlayer(plr);
}
func CreateBonus(int x, int y, int value)
{
var obj;
if (Random(value) > 50)
{
obj = CreateObject(Signpost, x,y);
if (obj)
{
if (Random(value) > 5)
{
var dx = goal_cave.X - x, dy = goal_cave.Y - y;
if (Abs(dx) > Abs(dy))
if (dx>0) obj->SetText("$MsgGoalRight$"); else obj->SetText("$MsgGoalLeft$");
else
if (dy>0) obj->SetText("$MsgGoalBelow$"); else obj->SetText("$MsgGoalAbove$");
}
}
}
else
{
obj = CreateObject(Chest, x,y);
if (obj)
{
if (Random(value) > 90) obj->CreateContents(Shovel);
if (Random(value) > 90) obj->CreateContents(JarOfWinds);
if (Random(value) > 90) obj->CreateContents(TeleGlove);
if (Random(value) > 90) obj->CreateContents(Sword);
if (Random(value) > 5) obj->CreateContents(Loam, 1+Random(2));
if (Random(value) > 5) obj->CreateContents(Dynamite, 1+Random(2));
if (Random(value) > 25) obj->CreateContents(DynamiteBox, 1+Random(2));
if (Random(value) > 10) obj->CreateContents(Bread, 1+Random(2));
}
}
return obj;
}
protected func Initialize()
{
var zoom = 10, cave, n_caves = GetLength(g_caves);
for (cave in g_caves) { cave.X *= zoom; cave.Y *= zoom; }
// Goal
var starting_cave = g_caves[0];
var goal = FindObject(Find_ID(Goal_RubyHunt));
if (!goal) goal = CreateObject(Goal_RubyHunt);
goal->SetGoalRect(Rectangle(0, starting_cave.Y-25, starting_cave.X-20, 25));
goal_cave = g_caves[n_caves-1];
// Place extra elements in caves (except at start/end)
for (cave in g_caves)
{
if (cave == g_caves[0] || cave == g_caves[n_caves-1]) continue;
var x=cave.X, y=cave.Y;
while (!GBackSolid(x,y)) ++y;
if (cave.n_links <= 1)
{
// This is a dead end.
if (cave.dirs == 8)
{
// Facing downwards. Hard to reach, but cannot place a chest here :(
CreateObject(Trunk, cave.X, cave.Y)->SetR(160+Random(41));
}
else
{
CreateBonus(x, y, 100);
}
}
else if (!(cave.dirs & 8))
{
// Connecting cave without bottom
CreateBonus(x, y, 25 + 25 * !cave.is_main_path);
}
}
return true;
}

View File

@ -0,0 +1,17 @@
[DefCore]
id=Signpost
Version=5,2,0,1
Category=C4D_StaticBack
Width=32
Height=32
Offset=-16,-16
Vertices=1
VertexX=4
VertexY=15
VertexCNAT=8
VertexFriction=100
Components=Wood=1;
Construction=1
Value=5
Mass=5
Rotate=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -0,0 +1,27 @@
/*
Signpost
Author: Sven2
Storage for text.
*/
local text;
func SetText(string t) { text=t; }
public func IsInteractable() { return GetCon() >= 100; }
public func GetInteractionMetaInfo(object clonk)
{
return { Description = "$MsgRead$", IconName = nil, IconID = nil };
}
// Read on interaction
public func Interact(object clonk)
{
var message = Format("%s ", text ?? "$MsgUnreadable$");
CustomMessage(message, nil, clonk->GetController(), 150,150, nil, GUI_MenuDeco, Signpost);
return true;
}
local Name = "$Name$";

View File

@ -0,0 +1,3 @@
Name=Schild
MsgRead=Lesen
MsgUnreadable=Unlesbar.

View File

@ -0,0 +1,3 @@
Name=Signpost
MsgRead=Read
MsgUnreadable=Unreadable.

View File

@ -0,0 +1,4 @@
MsgGoalAbove=Das Ziel ist oberhalb von hier.
MsgGoalBelow=Das Ziel ist unterhalb von hier.
MsgGoalLeft=Das Ziel ist links von hier.
MsgGoalRight=Das Ziel ist rechts von hier.

View File

@ -0,0 +1,4 @@
MsgGoalAbove=The goal is above this post.
MsgGoalBelow=The goal is below this post.
MsgGoalLeft=The goal is left of this post.
MsgGoalRight=The goal is right of this post.

View File

@ -0,0 +1,16 @@
/*--
Override screenshot functionality
--*/
global func PlayerControl(int plr, int ctrl)
{
if (ctrl == CON_TryScreenshot)
{
CustomMessage(Format("$MsgCheater$", GetTaggedPlayerName(plr)));
Sound("Error", true);
var crew = GetCursor(plr);
if (crew) crew->Punch(crew, 50);
return true;
}
return _inherited(plr, ctrl, ...);
}

View File

@ -0,0 +1,14 @@
[ControlDefs]
[ControlDef]
Identifier=TryScreenshot
[ControlSets]
[ControlSet]
Name=*
[Assignment]
Key=Ctrl+F9
Priority=999
Control=TryScreenshot

View File

@ -0,0 +1 @@
MsgCheater=%s wollte cheaten!

View File

@ -0,0 +1 @@
MsgCheater=%s tried to cheat!

View File

@ -0,0 +1,2 @@
DE:Labyrinth
US:Maze