forked from Mirrors/openclonk
494 lines
13 KiB
C
494 lines
13 KiB
C
/**
|
|
Tutorial 02: Bombing Barriers
|
|
Author: Maikel
|
|
|
|
Again your wipf is lost: explains items, collection, inventory management, using them and throwing them.
|
|
|
|
Following controls and concepts are explained:
|
|
* Collection of objects
|
|
* Inventory control
|
|
* Throwing items: firestones
|
|
* Dropping items: mushroom/berries
|
|
* Using items: shovel, loam
|
|
* Liquids: acid
|
|
*/
|
|
|
|
|
|
static guide; // guide object.
|
|
|
|
protected func Initialize()
|
|
{
|
|
// Tutorial goal.
|
|
var goal = CreateObject(Goal_Tutorial);
|
|
goal.Name = "$MsgGoalName$";
|
|
goal.Description = "$MsgGoalDescription$";
|
|
|
|
// Set the mood.
|
|
SetSkyParallax(0, 20, 20);
|
|
|
|
// Place objects in different sections.
|
|
InitCaveEntrance();
|
|
InitCaveMiddle();
|
|
InitCaveBottom();
|
|
InitCliffTop();
|
|
InitAcidLake();
|
|
InitVegetation();
|
|
InitAnimals();
|
|
|
|
// Dialogue options -> repeat round.
|
|
SetNextMission("Tutorials.ocf\\Tutorial02.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\\Tutorial03.ocs", "$MsgNextTutorial$", "$MsgNextTutorialDesc$");
|
|
// Normal scenario ending by goal library.
|
|
return false;
|
|
}
|
|
|
|
private func InitCaveEntrance()
|
|
{
|
|
var windgenerator = CreateObjectAbove(WindGenerator, 40, 328);
|
|
windgenerator->SetObjectLayer(windgenerator);
|
|
var armory =CreateObjectAbove(Armory, 112, 328);
|
|
armory->SetObjectLayer(armory);
|
|
var workshop = CreateObjectAbove(ToolsWorkshop, 192, 328);
|
|
workshop->SetObjectLayer(workshop);
|
|
|
|
// Place a shovel for digging.
|
|
var shovel = CreateObjectAbove(Shovel, 200, 328);
|
|
shovel->SetR(Random(360));
|
|
return;
|
|
}
|
|
|
|
private func InitCaveMiddle()
|
|
{
|
|
// Make sure the brick overlaps the rock to blast.
|
|
DrawMaterialQuad("Brick", 432, 536, 472, 536, 472, 542, 432, 542, true);
|
|
|
|
// Widen and cover the exit for the wipf.
|
|
DrawMaterialQuad("Tunnel", 550, 526, 570, 526, 570, 536, 550, 536, DMQ_Sub);
|
|
var trunk = CreateObjectAbove(Trunk, 570, 558);
|
|
trunk.MeshTransformation = [821, 0, 795, 0, 0, 1145, 0, 0, -795, 0, 821, 0];
|
|
trunk.Plane = 510; trunk->SetR(90);
|
|
|
|
// A source of light drawing attention to the wipf.
|
|
var torch = CreateObjectAbove(Torch, 484, 528);
|
|
torch->AttachToWall(true);
|
|
|
|
// Some mushrooms and ferns in the middle: left cave.
|
|
Fern->Place(2 + Random(2), Rectangle(0, 480, 56, 40));
|
|
Mushroom->Place(4 + Random(2), Rectangle(0, 480, 56, 40));
|
|
|
|
// Some mushrooms and ferns in the middle: middle cave.
|
|
Fern->Place(3 + Random(2), Rectangle(128, 464, 104, 80));
|
|
Mushroom->Place(6 + Random(2), Rectangle(128, 464, 104, 80));
|
|
|
|
// Some mushrooms and ferns in the middle: bottom cave.
|
|
Fern->Place(3 + Random(2), Rectangle(400, 580, 100, 64));
|
|
Mushroom->Place(6 + Random(2), Rectangle(400, 580, 100, 64));
|
|
|
|
// Seaweed in the two lakes.
|
|
Seaweed->Place(5, Rectangle(48, 400, 96, 32));
|
|
return;
|
|
}
|
|
|
|
private func InitCaveBottom()
|
|
{
|
|
PlaceObjects(Loam, 2 + Random(2), "Earth", 496, 592, 40, 24);
|
|
|
|
// Seaweed in the two lakes.
|
|
Seaweed->Place(5, Rectangle(128, 688, 88, 48));
|
|
Seaweed->Place(5, Rectangle(568, 688, 80, 48));
|
|
return;
|
|
}
|
|
|
|
private func InitCliffTop()
|
|
{
|
|
// Some trees and bushes on top of the cliff.
|
|
SproutBerryBush->Place(3 + Random(3), Rectangle(240, 160, 400, 120));
|
|
Flower->Place(10, Rectangle(240, 160, 400, 120));
|
|
|
|
// Berry bush on the right side of the cliff.
|
|
SproutBerryBush->Place(1, Rectangle(580, 310, 60, 60));
|
|
var bush = FindObject(Find_ID(SproutBerryBush), Sort_Distance(610, 340));
|
|
if (bush)
|
|
bush->CreateObject(Sproutberry);
|
|
return;
|
|
}
|
|
|
|
private func InitAcidLake()
|
|
{
|
|
// Ropebridge over the acid lake.
|
|
Ropebridge->Create(816, 528, 928, 528);
|
|
|
|
// Make the acid lake bubbling a bit.
|
|
BoilingAcid->Place();
|
|
|
|
// An acid power plant on the right with some dead trees.
|
|
CreateObjectAbove(Tree_Coniferous_Burned, 754, 532)->DoCon(-30);
|
|
CreateObjectAbove(Tree_Coniferous_Burned, 792, 532)->DoCon(-10);
|
|
CreateObjectAbove(Tree_Coniferous_Burned, 942, 532)->DoCon(-35);
|
|
var engine = CreateObjectAbove(SteamEngine, 980, 528); // TODO: replace with the acid machine
|
|
engine->SetObjectLayer(engine);
|
|
return;
|
|
}
|
|
|
|
// Vegetation throughout the scenario.
|
|
private func InitVegetation()
|
|
{
|
|
PlaceGrass(85);
|
|
PlaceObjects(Firestone, 25 + Random(5), "Earth");
|
|
PlaceObjects(Loam, 15 + Random(5), "Earth");
|
|
return;
|
|
}
|
|
|
|
private func InitAnimals()
|
|
{
|
|
// The wipf as your friend, controlled by AI.
|
|
var wipf = CreateObjectAbove(Wipf, 500, 536);
|
|
wipf->EnableTutorialControl();
|
|
wipf->SetMeshMaterial("WipfSkin");
|
|
wipf.Name = "$WipfName$";
|
|
wipf.Description = "$WipfDescription$";
|
|
|
|
// Some butterflies as atmosphere.
|
|
for (var i = 0; i < 25; i++)
|
|
PlaceAnimal(Butterfly);
|
|
return;
|
|
}
|
|
|
|
/*-- Player Handling --*/
|
|
|
|
protected func InitializePlayer(int plr)
|
|
{
|
|
// Position player's clonk.
|
|
var clonk = GetCrew(plr, 0);
|
|
clonk->SetPosition(40, 318);
|
|
var effect = AddEffect("ClonkRestore", clonk, 100, 10);
|
|
effect.to_x = 40;
|
|
effect.to_y = 318;
|
|
|
|
// 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.
|
|
SetPlayerViewLock(plr, true);
|
|
SetPlayerZoomByViewRange(plr, 400, nil, PLRZOOM_Direct | PLRZOOM_Set);
|
|
|
|
// Create tutorial guide, add messages, show first.
|
|
guide = CreateObject(TutorialGuide, 0, 0, plr);
|
|
guide->AddGuideMessage("$MsgTutorialIntro$");
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialShovel", nil, 100, 5);
|
|
return;
|
|
}
|
|
|
|
/*-- Intro, Tutorial Goal & Outro --*/
|
|
|
|
global func FxTrackGoalTimer(object target, proplist effect, int time)
|
|
{
|
|
var crew = GetCrew(effect.plr);
|
|
if (Inside(crew->GetX(), 982, 1002) && Inside(crew->GetY(), 504, 528))
|
|
{
|
|
if (FindObject(Find_ID(Wipf), Find_Distance(15, crew->GetX(), crew->GetY())))
|
|
{
|
|
AddEffect("GoalOutro", crew, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxGoalOutroStart(object target, proplist effect, int temp)
|
|
{
|
|
if (temp)
|
|
return FX_OK;
|
|
|
|
// Show guide message congratulating.
|
|
guide->AddGuideMessage("$MsgTutorialCompleted$");
|
|
guide->ShowGuideMessage();
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxGoalOutroTimer(object target, proplist effect, int time)
|
|
{
|
|
if (time >= 54)
|
|
{
|
|
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 wipf activity.
|
|
var wipf = FindObject(Find_ID(Wipf));
|
|
if (wipf)
|
|
wipf->DisableTutorialControl();
|
|
return FX_OK;
|
|
}
|
|
|
|
/*-- Guide Messages --*/
|
|
|
|
global func FxTutorialShovelTimer()
|
|
{
|
|
var clonk = FindObject(Find_ID(Clonk), Find_InRect(120, 264, 64, 64));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var pick_up = GetPlayerControlAssignment(plr, CON_PickUp, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialShovel$", pick_up));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialInventory", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialInventoryTimer()
|
|
{
|
|
var clonk = FindObject(Find_ID(Clonk), Find_InRect(120, 264, 192, 64));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var shovel = FindObject(Find_ID(Shovel), Find_Container(clonk));
|
|
if (shovel)
|
|
{
|
|
var inv_hotkey_ids = [CON_Hotkey1, CON_Hotkey2, CON_Hotkey3, CON_Hotkey4, CON_Hotkey5];
|
|
var inv_hotkeys = "";
|
|
for (var inv_hotkey in inv_hotkey_ids)
|
|
inv_hotkeys = Format("%s[%s]", inv_hotkeys, GetPlayerControlAssignment(plr, inv_hotkey, true, true));
|
|
var inv_inv_scroll_up = GetPlayerControlAssignment(plr, CON_InventoryShiftForward, true, true);
|
|
var inv_inv_scroll_down = GetPlayerControlAssignment(plr, CON_InventoryShiftBackward, true, true);
|
|
var inv_scroll = Format("[%s][%s]", inv_inv_scroll_up, inv_inv_scroll_down);
|
|
guide->AddGuideMessage(Format("$MsgTutorialInventory$", inv_hotkeys, inv_scroll));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialDigging", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialDiggingTimer()
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_InRect(328, 304, 96, 64));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var shovel = FindObject(Find_ID(Shovel), Find_Container(clonk));
|
|
if (shovel)
|
|
{
|
|
var use = GetPlayerControlAssignment(plr, CON_Use, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialDigging$", use));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialFirestones", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFirestonesTimer()
|
|
{
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_InRect(288, 384, 24, 96)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFirestones$");
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialWipfHole", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialWipfHoleTimer()
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_InRect(368, 496, 72, 56));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var throw = GetPlayerControlAssignment(plr, CON_Throw, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialFoundWipf$", throw));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialBlastedRock", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialBlastedRockTimer()
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_InRect(468, 496, 72, 48));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var drop = GetPlayerControlAssignment(plr, CON_DropHotkey1, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialBlastedRock$", drop));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialFedWipf", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFedWipfTimer()
|
|
{
|
|
var wipf = FindObject(Find_ID(Wipf));
|
|
if (wipf->HadFood())
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFedWipf$");
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialDigOutLoam", nil, 100, 5);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialDigOutLoamTimer()
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_InRect(424, 600, 32, 64));
|
|
if (clonk)
|
|
{
|
|
var plr = clonk->GetOwner();
|
|
var use = GetPlayerControlAssignment(plr, CON_Use, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialDigOutLoam$", use));
|
|
guide->ShowGuideMessage();
|
|
AddEffect("TutorialFragileBridge", nil, 100, 5);
|
|
var bridge = FindObject(Find_ID(Ropebridge));
|
|
bridge->SetFragile();
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFragileBridgeTimer()
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_InRect(744, 480, 80, 48));
|
|
if (clonk)
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFragileBridge$");
|
|
guide->ShowGuideMessage();
|
|
// Stop the controls of the clonk for a few seconds.
|
|
DisablePlrControls(clonk->GetOwner());
|
|
clonk->SetComDir(COMD_Stop);
|
|
var effect = AddEffect("TutorialWaitForBridge", nil, 100, 5);
|
|
effect.plr = clonk->GetOwner();
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialWaitForBridgeTimer(object target, proplist effect, int time)
|
|
{
|
|
if (time > 2 * 36)
|
|
{
|
|
var use = GetPlayerControlAssignment(effect.plr, CON_Use, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialMakeLoamBridge$", use));
|
|
guide->ShowGuideMessage();
|
|
// Start the controls of the clonk again.
|
|
EnablePlrControls(effect.plr);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
protected func OnGuideMessageShown(int plr, int index)
|
|
{
|
|
// Show the direction to move.
|
|
if (index == 0)
|
|
TutArrowShowPos(104, 312, 90);
|
|
// Show the shovel.
|
|
if (index == 1)
|
|
{
|
|
var shovel = FindObject(Find_ID(Shovel), Find_NoContainer());
|
|
if (shovel)
|
|
TutArrowShowTarget(shovel);
|
|
}
|
|
// Show the direction to dig.
|
|
if (index == 3)
|
|
TutArrowShowPos(366, 396, 225);
|
|
// Show the firestone material.
|
|
if (index == 4)
|
|
TutArrowShowPos(268, 456);
|
|
// Show the rock to blow up.
|
|
if (index == 5)
|
|
TutArrowShowPos(436, 520, 90);
|
|
// Show the clonks friend: the wipf.
|
|
if (index == 6)
|
|
TutArrowShowTarget(FindObject(Find_ID(Wipf)));
|
|
// Show to the way down to the settlement.
|
|
if (index == 7)
|
|
TutArrowShowPos(368, 592);
|
|
// Show the loam pieces to collect.
|
|
if (index == 8)
|
|
TutArrowShowPos(504, 614, 90);
|
|
// Show the fragile bridge.
|
|
if (index == 9)
|
|
TutArrowShowPos(848, 520);
|
|
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(266, 382, 80, 100)))
|
|
{
|
|
effect.to_x = 304;
|
|
effect.to_y = 444;
|
|
}
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_InRect(408, 574, 60, 58)))
|
|
{
|
|
effect.to_x = 428;
|
|
effect.to_y = 624;
|
|
}
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_InRect(744, 464, 72, 64)))
|
|
{
|
|
effect.to_x = 776;
|
|
effect.to_y = 518;
|
|
}
|
|
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;
|
|
} |