openclonk/planet/Tutorials.ocf/Tutorial01.ocs/Script.c

528 lines
16 KiB
C

/**
Tutorial 01: Wandering Wipf
Author: Maikel
First introduction to the world of OpenClonk: explains basic movement controls.
Following controls and concepts are explained:
* Clonk HUD
* Walking and jumping with WASD
* Scaling, wall jump and hangling
* Swimming, diving and breath
* Liquids: water
*/
static guide; // guide object
protected func Initialize()
{
// Tutorial goal.
var goal = CreateObject(Goal_Tutorial);
goal.Name = "$MsgGoalName$";
goal.Description = "$MsgGoalDescription$";
CreateObject(Rule_NoPowerNeed);
GUI_Controller->ShowWealth();
// Place objects in different sections.
InitStartLake();
InitCaveEntrance();
InitHangleSection();
InitCaveCenter();
InitGoldMine();
InitCaveExit();
InitVegetation();
InitAnimals();
// Show wealth in HUD.
GUI_Controller->ShowWealth();
// Environment.
var time = CreateObject(Time);
time->SetTime(18 * 60 + 30);
time->SetCycleSpeed(0);
// Dialogue options -> repeat round.
SetNextMission("Tutorials.ocf\\Tutorial01.ocs", "$MsgRepeatRound$", "$MsgRepeatRoundDesc$");
return;
}
// Gamecall from goals, set next mission.
protected func OnGoalsFulfilled()
{
// Achievement: Tutorial completed.
GainScenarioAchievement("TutorialCompleted", 3);
// Dialogue options -> next round.
SetNextMission("Tutorials.ocf\\Tutorial02.ocs", "$MsgNextTutorial$", "$MsgNextTutorialDesc$");
// Normal scenario ending by goal library.
return false;
}
// Lake and start position.
private func InitStartLake()
{
// Seaweed, fishes and shells in the lakes.
Seaweed->Place(5, Rectangle(0, 600, 50, 100));
Seaweed->Place(5, Rectangle(120, 600, 120, 120));
Seaweed->Place(5, Rectangle(220, 400, 140, 50));
// A shipwreck on the beach.
// A few coconut trees.
CreateObjectAbove(Tree_Coconut, 52, 620);
CreateObjectAbove(Tree_Coconut, 79, 620);
CreateObjectAbove(Tree_Coconut, 105, 620);
// Ropebridge over the lake.
Ropebridge->Create(144, 616, 232, 616);
// Small sink hole for the water to flow through, cover by a trunk.
DrawMaterialQuad("Tunnel", 231, 631, 231, 633, 251, 615, 249, 615, DMQ_Sub);
var trunk = CreateObjectAbove(Trunk, 241, 644);
trunk->SetR(45); trunk.Plane = 510; trunk->DoCon(-42);
trunk.MeshTransformation = [-1000, 0, -17, 0, 0, 1000, 0, 0, 17, 0, -1000, 0];
return;
}
// Cave entrance with waterfall.
private func InitCaveEntrance()
{
// Waterfall from a leaking trunk.
var trunk = CreateObjectAbove(Trunk, 324, 465);
trunk->SetR(150); trunk.Plane = 510;
trunk.MeshTransformation = [-70, 0, 998, 0, 0, 1000, 0, 0, -998, 0, -70, 0];
var waterfall;
waterfall = CreateWaterfall(325, 448, 2, "Water");
waterfall->SetDirection(3, 3, 1, 3);
waterfall = CreateWaterfall(338, 450, 8, "Water");
waterfall->SetDirection(3, 4, 2, 4);
CreateLiquidDrain(160, 648, 10);
CreateLiquidDrain(184, 648, 10);
CreateLiquidDrain(208, 648, 10);
// Some vegetation below the waterfall.
Fern->Place(2 + Random(2), Rectangle(300, 600, 60, 40));
Mushroom->Place(4 + Random(2), Rectangle(300, 600, 60, 40));
// Make the wipf hole a bit wider.
DrawMaterialQuad("Tunnel", 360, 600, 410, 600, 410, 589, 360, 589, DMQ_Sub);
// Vegetation in wall jump hole.
var rock = CreateObjectAbove(BigRock, 313, 525);
rock.MeshTransformation = [-314, -543, 671, 0, 0, 1085, 878, 0, -974, 222, -275, 0];
rock.Plane = 510;
// Torches to show the path.
var torch = CreateObjectAbove(Torch, 288, 608);
torch->AttachToWall(true);
var torch = CreateObjectAbove(Torch, 388, 488);
torch->AttachToWall(true);
return;
}
// Hangle section with large mushrooms and gap to cross.
private func InitHangleSection()
{
// Some large mushrooms.
var mushroom = CreateConstruction(LargeCaveMushroom, 489, 557, NO_OWNER, 100, false, false);
mushroom.MeshTransformation = Trans_Scale(500, 500, 500); mushroom->SetR(240);
mushroom = CreateConstruction(LargeCaveMushroom, 517, 606, NO_OWNER, 100, false, false);
mushroom.MeshTransformation = Trans_Scale(550, 550, 550);
mushroom = CreateConstruction(LargeCaveMushroom, 597, 670, NO_OWNER, 100, false, false);
mushroom.MeshTransformation = Trans_Scale(600, 600, 600);
mushroom = CreateConstruction(LargeCaveMushroom, 642, 686, NO_OWNER, 100, false, false);
mushroom.MeshTransformation = Trans_Scale(700, 700, 700);
// Some ferns and mushrooms.
Fern->Place(5 + Random(3), Rectangle(480, 560, 220, 140));
Mushroom->Place(9 + Random(4), Rectangle(480, 560, 220, 140));
// Make the diagonal wipf hole a bit wider.
DrawMaterialQuad("Tunnel", 690, 671, 754, 607, 769, 607, 690, 686, DMQ_Sub);
// Wipf hole covered by trunk.
var trunk = CreateObjectAbove(Trunk, 822, 621);
trunk.MeshTransformation = [821, 0, 795, 0, 0, 1145, 0, 0, -795, 0, 821, 0];
trunk.Plane = 510; trunk->SetR(90);
DrawMaterialQuad("Tunnel", 796, 591, 836, 591, 836, 602, 796, 602, DMQ_Sub);
return;
}
// The cave center has two lakes with seaweed.
private func InitCaveCenter()
{
// Seaweed in the two lakes.
Seaweed->Place(5, Rectangle(540, 480, 140, 100));
Seaweed->Place(12, Rectangle(520, 330, 380, 100));
// Some vegetation between the two bodies of water.
var ferns = Fern->Place(5 + Random(2), Rectangle(420, 370, 110, 90));
for (var fern in ferns)
fern->SetR(40 + Random(10));
var mushrooms = Mushroom->Place(7 + Random(3), Rectangle(420, 370, 110, 90));
for (var mushroom in mushrooms)
mushroom->SetR(40 + Random(10));
return;
}
// Gold mine with dynamite and lorry.
private func InitGoldMine()
{
// Foundry with a lorry and some metal.
CreateObjectAbove(Foundry, 980, 486)->MakeInvincible();
CreateObjectAbove(Metal, 950, 474);
// Pickaxe + ore near ore field.
CreateObjectAbove(Lorry, 824, 460);
CreateObjectAbove(Pickaxe, 808, 446)->SetR(RandomX(300, 330));
CreateObjectAbove(Ore, 818, 460);
CreateObjectAbove(Ore, 839, 460);
// Shovel + coal chunks near coal field.
CreateObjectAbove(Shovel, 966, 534)->SetR(RandomX(90, 135));
CreateObjectAbove(Coal, 959, 532);
CreateObjectAbove(Coal, 936, 532);
// Dynamite box to blast through mine.
var dyn1 = CreateObjectAbove(Dynamite, 959, 585, NO_OWNER); dyn1->SetR(-135);
var dyn2 = CreateObjectAbove(Dynamite, 974, 597, NO_OWNER); dyn2->SetR(-105);
var dyn3 = CreateObjectAbove(Dynamite, 970, 618, NO_OWNER); dyn3->SetR(-60);
var dyn4 = CreateObjectAbove(Dynamite, 955, 633, NO_OWNER); dyn4->SetR(-30);
var dyn5 = CreateObjectAbove(Dynamite, 941, 637, NO_OWNER); dyn5->SetR(-15);
CreateObjectAbove(Fuse, 959, 585, NO_OWNER)->Connect(dyn1, dyn2);
CreateObjectAbove(Fuse, 974, 597, NO_OWNER)->Connect(dyn2, dyn3);
CreateObjectAbove(Fuse, 970, 618, NO_OWNER)->Connect(dyn3, dyn4);
CreateObjectAbove(Fuse, 955, 633, NO_OWNER)->Connect(dyn4, dyn5);
var igniter = CreateObjectAbove(DynamiteBox, 909, 637, NO_OWNER);
igniter->ChangeToIgniter();
CreateObjectAbove(Fuse, 941, 637, NO_OWNER)->Connect(dyn5, igniter);
return;
}
// Cave exit: Elevator, cabin and flag.
private func InitCaveExit()
{
// A small settlement.
var elevator = CreateObjectAbove(Elevator, 872, 328);
elevator->CreateShaft(248);
elevator->MakeInvincible();
CreateObjectAbove(WoodenCabin, 968, 328)->MakeInvincible();
// Some vegetation / rocks on top of the cave.
SproutBerryBush->Place(1 + Random(2), Rectangle(420, 200, 160, 70));
SproutBerryBush->Place(1, Rectangle(560, 200, 100, 70));
Cotton->Place(2, Rectangle(560, 200, 100, 70));
Flower->Place(10, Rectangle(420, 300, 160, 70));
var bigrock = CreateObjectAbove(BigRock, 218, 395);
bigrock.MeshTransformation = [-103, 561, -337, 0, 0, 730, 1216, 0, 555, 74, -44, 0];
return;
}
// Vegetation throughout the scenario.
private func InitVegetation()
{
PlaceGrass(85);
PlaceObjects(Firestone, 25 + Random(5), "Earth");
PlaceObjects(Loam, 15 + Random(5), "Earth");
Branch->Place(32, nil, {underground = true});
Branch->Place(10, nil, {underground = false});
return;
}
private func InitAnimals()
{
// The wipf as your friend, controlled by AI.
var wipf = CreateObjectAbove(Wipf, 76, 616);
wipf->EnableTutorialControl();
wipf->SetMeshMaterial("WipfSkin");
wipf.Name = "$WipfName$";
wipf.Description = "$WipfDescription$";
// Some butterflies as atmosphere.
for (var i = 0; i < 25; i++)
PlaceAnimal(Butterfly);
// Some fish in the big lake.
Fish->Place(5, Rectangle(500, 300, 300, 150));
return;
}
/*-- Player Handling --*/
protected func InitializePlayer(int plr)
{
// Position player's clonk.
var clonk = GetCrew(plr, 0);
clonk->SetPosition(60, 606);
var fx = AddEffect("ClonkRestore", clonk, 100, 10);
fx.to_x = 60;
fx.to_y = 606;
// Some wealth for the guide message.
SetWealth(plr, 25);
// Player controls disabled at the start.
DisablePlrControls(plr);
// Add an effect to the clonk to track the goal.
var track_goal = AddEffect("TrackGoal", nil, 100, 2);
track_goal.plr = plr;
// Standard player zoom for tutorials, player is not allowed to zoom in/out.
SetPlayerViewLock(plr, true);
SetPlayerZoomByViewRange(plr, 400, nil, PLRZOOM_Direct | PLRZOOM_LimitMin | PLRZOOM_LimitMax);
// Determine player movement keys.
var left = GetPlayerControlAssignment(plr, CON_Left, true, true);
var right = GetPlayerControlAssignment(plr, CON_Right, true, true);
var up = GetPlayerControlAssignment(plr, CON_Up, true, true);
var down = GetPlayerControlAssignment(plr, CON_Down, true, true);
var jump = GetPlayerControlAssignment(plr, CON_Jump, true, true);
var control_keys = Format("[%s] [%s] [%s] [%s]", up, left, down, right);
// Create tutorial guide, add messages, show first.
guide = CreateObjectAbove(TutorialGuide, 0, 0, plr);
guide->AddGuideMessage(Format("$MsgTutorialWelcome$", control_keys));
guide->AddGuideMessage("$MsgTutorialHUD$");
guide->AddGuideMessage(Format("$MsgTutorialFollowFriend$", left, right, jump));
guide->ShowGuideMessage(0);
AddEffect("TutorialScale", nil, 100, 2);
return;
}
/*-- Intro, Tutorial Goal & Outro --*/
private func OnFinishedTutorialIntro(int plr)
{
// enable crew
EnablePlrControls(plr);
// start the wipf
FindObject(Find_ID(Wipf))->StartMoving();
}
global func FxTrackGoalTimer(object target, proplist effect, int time)
{
var crew = GetCrew(effect.plr);
if (Inside(crew->GetX(), 980, 996) && Inside(crew->GetY(), 314, 324))
{
if (FindObject(Find_ID(Wipf), Find_Distance(15, crew->GetX(), crew->GetY())))
{
var outro = AddEffect("GoalOutro", crew, 100, 5);
outro.plr = effect.plr;
return FX_Execute_Kill;
}
}
return FX_OK;
}
global func FxGoalOutroStart(object target, proplist effect, int temp)
{
if (temp)
return;
// Show guide message congratulating.
guide->AddGuideMessage("$MsgTutorialCompleted$");
guide->ShowGuideMessage();
// Halt clonk and disable player controls.
DisablePlrControls(effect.plr);
target->SetComDir(COMD_Stop);
// Find wipf and enter it into the clonk.
var wipf = FindObject(Find_ID(Wipf), Find_Distance(15, target->GetX(), target->GetY()));
if (wipf)
wipf->Enter(target);
return FX_OK;
}
global func FxGoalOutroTimer(object target, proplist effect, int time)
{
if (time >= 60)
{
var goal = FindObject(Find_ID(Goal_Tutorial));
goal->Fulfill();
return FX_Execute_Kill;
}
return FX_OK;
}
global func FxGoalOutroStop(object target, proplist effect, int reason, bool temp)
{
if (temp)
return FX_OK;
// Enable player controls again.
EnablePlrControls(effect.plr);
// Enable wipf activity.
var wipf = FindObject(Find_ID(Wipf));
if (wipf)
wipf->DisableTutorialControl();
return FX_OK;
}
/*-- Guide Messages --*/
// Finds when the Clonk has done 'X', and changes the message.
global func FxTutorialScaleTimer(object target, proplist effect, int timer)
{
var clonk = FindObject(Find_ID(Clonk), Find_InRect(264, 568, 80, 48));
if (clonk)
{
var plr = clonk->GetOwner();
while (GetEffect("TutorialIntro*"))
RemoveEffect("TutorialIntro*");
var up = GetPlayerControlAssignment(plr, CON_Up, true, true);
var down = GetPlayerControlAssignment(plr, CON_Down, true, true);
guide->AddGuideMessage(Format("$MsgTutorialScaleWall$", up, down));
guide->ShowGuideMessage();
AddEffect("TutorialHangle", nil, 100, 2);
return FX_Execute_Kill;
}
return FX_OK;
}
global func FxTutorialHangleTimer(object target, proplist effect, int timer)
{
var clonk = FindObject(Find_ID(Clonk), Find_InRect(480, 560, 80, 48));
if (clonk)
{
var plr = clonk->GetOwner();
var down = GetPlayerControlAssignment(plr, CON_Down, true, true);
guide->AddGuideMessage(Format("$MsgTutorialHangle$", down));
guide->ShowGuideMessage();
AddEffect("TutorialJump", nil, 100, 2);
return FX_Execute_Kill;
}
return FX_OK;
}
global func FxTutorialJumpTimer(object target, proplist effect, int timer)
{
if (FindObject(Find_ID(Clonk), Find_InRect(744, 568, 56, 40)))
{
guide->AddGuideMessage("$MsgTutorialJump$");
guide->ShowGuideMessage();
AddEffect("TutorialSwimming", nil, 100, 2);
return FX_Execute_Kill;
}
return FX_OK;
}
global func FxTutorialSwimmingTimer(object target, proplist effect, int timer)
{
var clonk = FindObject(Find_ID(Clonk), Find_InRect(672, 440, 80, 72));
if (clonk)
{
var plr = clonk->GetOwner();
var left = GetPlayerControlAssignment(plr, CON_Left, true, true);
var right = GetPlayerControlAssignment(plr, CON_Right, true, true);
var up = GetPlayerControlAssignment(plr, CON_Up, true, true);
var down = GetPlayerControlAssignment(plr, CON_Down, true, true);
var control_keys = Format("[%s] [%s] [%s] [%s]", up, left, down, right);
guide->AddGuideMessage(Format("$MsgTutorialSwimming$", control_keys));
guide->ShowGuideMessage();
AddEffect("TutorialDiving", nil, 100, 2);
return FX_Execute_Kill;
}
return FX_OK;
}
global func FxTutorialDivingTimer(object target, proplist effect, int timer)
{
if (FindObject(Find_ID(Clonk), Find_InRect(472, 280, 48, 64)))
{
guide->AddGuideMessage("$MsgTutorialDiving$");
guide->ShowGuideMessage();
return FX_Execute_Kill;
}
return FX_OK;
}
protected func OnGuideMessageShown(int plr, int index)
{
// Show the player his clonk and the guide.
if (index == 0)
{
TutArrowShowTarget(GetCrew(GetPlayerByIndex()), 225, 24);
}
// Show the player HUD.
if (index == 1)
{
TutArrowShowGUITarget(FindObject(Find_ID(GUI_Controller_CrewBar)), 0);
TutArrowShowGUITarget(FindObject(Find_ID(GUI_Controller_Goal)), 0);
}
// Show the clonks friend: the wipf.
if (index == 2)
{
OnFinishedTutorialIntro(plr);
TutArrowShowTarget(FindObject(Find_ID(Wipf)));
}
// Show wall to climb.
if (index == 3)
TutArrowShowPos(366, 570, 45);
// Show ceiling on which to hangle.
if (index == 4)
TutArrowShowPos(592, 564, 45);
// Show wall to jump to.
if (index == 5)
{
TutArrowShowPos(780, 560, 0);
TutArrowShowPos(756, 544, 315);
}
// Show sea to swim in.
if (index == 6)
TutArrowShowPos(652, 478, 225);
// Show breath indicator.
if (index == 7)
TutArrowShowGUITarget(FindObject(Find_ID(GUI_Controller_CrewBar)), 0);
return;
}
protected func OnGuideMessageRemoved(int plr, int index)
{
TutArrowClear();
return;
}
/*-- Clonk restoring --*/
global func FxClonkRestoreTimer(object target, proplist effect, int time)
{
// Respawn clonk to new location if reached certain position.
if (FindObject(Find_OCF(OCF_CrewMember), Find_InRect(656, 432, 40, 48)))
{
effect.to_x = 680;
effect.to_y = 470;
}
if (FindObject(Find_OCF(OCF_CrewMember), Find_InRect(488, 280, 32, 48)))
{
effect.to_x = 500;
effect.to_y = 318;
}
return FX_OK;
}
// Relaunches the clonk, from death or removal.
global func FxClonkRestoreStop(object target, effect, int reason, bool temporary)
{
if (reason == 3 || reason == 4)
{
var restorer = CreateObject(ObjectRestorer, 0, 0, NO_OWNER);
var x = BoundBy(target->GetX(), 0, LandscapeWidth());
var y = BoundBy(target->GetY(), 0, LandscapeHeight());
restorer->SetPosition(x, y);
var to_x = effect.to_x;
var to_y = effect.to_y;
// Respawn new clonk.
var plr = target->GetOwner();
var clonk = CreateObject(Clonk, 0, 0, plr);
clonk->GrabObjectInfo(target);
Rule_Relaunch->TransferInventory(target, clonk);
SetCursor(plr, clonk);
clonk->DoEnergy(100000);
restorer->SetRestoreObject(clonk, nil, to_x, to_y, 0, "ClonkRestore");
}
return FX_OK;
}