forked from Mirrors/openclonk
425 lines
12 KiB
C
425 lines
12 KiB
C
/**
|
|
Tutorial 06: Power Problems
|
|
Author: Maikel
|
|
|
|
The player has reached the friendly village high up in the skies, they don't have power as there is no wind.
|
|
Help them setting up a back-up power supply and construct an airplane so they will fight against the evil faction.
|
|
|
|
Following controls and concepts are explained:
|
|
* Power system and overview
|
|
* Buying at the flagpole
|
|
* Construction of steam engine and compensator
|
|
* Catapult (shooting objects and oneself)
|
|
* Production of airplane
|
|
|
|
TODO:
|
|
* Outro with airplane who takes the player to the next round.
|
|
* Intro with airship from bottom left (also add to tutorial 5).
|
|
* NPC clonks.
|
|
* Children NPCs playing with the catapults.
|
|
* Make a script player who is owner of the village.
|
|
*/
|
|
|
|
static guide; // guide object.
|
|
|
|
protected func Initialize()
|
|
{
|
|
// Tutorial goal.
|
|
var goal = CreateObject(Goal_Tutorial);
|
|
goal.Name = "$MsgGoalName$";
|
|
goal.Description = "$MsgGoalDescription$";
|
|
|
|
// Enable buying at the flagpole
|
|
CreateObject(Rule_BuyAtFlagpole);
|
|
|
|
// Place objects in different sections.
|
|
InitBottomIsland();
|
|
InitMiddleIsland();
|
|
InitTopIsland();
|
|
InitVegetation();
|
|
InitAnimals();
|
|
InitAI();
|
|
|
|
// Dialogue options -> repeat round.
|
|
SetNextScenario("Tutorials.ocf\\Tutorial06.ocs", "$MsgRepeatRound$", "$MsgRepeatRoundDesc$");
|
|
return;
|
|
}
|
|
|
|
// Gamecall from goals, set next mission.
|
|
protected func OnGoalsFulfilled()
|
|
{
|
|
// Achievement: Tutorial completed.
|
|
GainScenarioAchievement("TutorialCompleted", 3);
|
|
// Dialogue options -> next round.
|
|
SetNextScenario("Tutorials.ocf\\Tutorial07.ocs", "$MsgNextTutorial$", "$MsgNextTutorialDesc$");
|
|
// Normal scenario ending by goal library.
|
|
return false;
|
|
}
|
|
|
|
private func InitBottomIsland()
|
|
{
|
|
var shipyard = CreateObjectAbove(Shipyard, 250, 568);
|
|
shipyard->MakeInvincible();
|
|
shipyard->CreateContents(Wood, 4);
|
|
shipyard->CreateContents(Metal, 6);
|
|
|
|
CreateObjectAbove(Flagpole, 440, 544)->MakeInvincible();
|
|
CreateObjectAbove(WindGenerator, 400, 544)->MakeInvincible();
|
|
return;
|
|
}
|
|
|
|
private func InitMiddleIsland()
|
|
{
|
|
// A catapult to get to the bottom island.
|
|
var catapult = CreateObjectAbove(Catapult, 760, 408);
|
|
catapult->MakeInvincible();
|
|
catapult->TurnLeft();
|
|
|
|
var chemical_lab = CreateObjectAbove(ChemicalLab, 850, 368);
|
|
chemical_lab->MakeInvincible();
|
|
chemical_lab->CreateContents(Dynamite, 5);
|
|
|
|
CreateObjectAbove(WindGenerator, 890, 368)->MakeInvincible();
|
|
|
|
var sawmill = CreateObjectAbove(Sawmill, 996, 360);
|
|
sawmill->MakeInvincible();
|
|
sawmill->SetDir(DIR_Right);
|
|
|
|
// A chest with a windbag on the small island (this is the easter egg).
|
|
var chest = CreateObjectAbove(Chest, 1004, 176);
|
|
chest->MakeInvincible();
|
|
chest->CreateContents(WindBag);
|
|
return;
|
|
}
|
|
|
|
private func InitTopIsland()
|
|
{
|
|
// A catapult to get to the middle island.
|
|
var catapult = CreateObjectAbove(Catapult, 432, 248);
|
|
catapult->MakeInvincible();
|
|
|
|
var elevator = CreateObjectAbove(Elevator, 312, 240);
|
|
elevator->SetDir(DIR_Right);
|
|
elevator->MakeInvincible();
|
|
elevator->CreateShaft(200);
|
|
var case = elevator->GetCase();
|
|
case->SetPosition(case->GetX(), 496);
|
|
case->MakeInvincible();
|
|
|
|
var workshop = CreateObjectAbove(ToolsWorkshop, 220, 240);
|
|
workshop->MakeInvincible();
|
|
workshop->CreateContents(Hammer, 2);
|
|
workshop->CreateContents(Shovel, 2);
|
|
workshop->CreateContents(Pickaxe, 2);
|
|
|
|
CreateObjectAbove(Flagpole, 356, 240)->MakeInvincible();
|
|
CreateObjectAbove(WindGenerator, 270, 240)->MakeInvincible();
|
|
return;
|
|
}
|
|
|
|
private func InitVegetation()
|
|
{
|
|
Grass->Place(85);
|
|
PlaceObjects(Rock, 20, "Earth");
|
|
PlaceObjects(Firestone, 8, "Earth");
|
|
Branch->Place(10);
|
|
Mushroom->Place(12);
|
|
Flower->Place(10);
|
|
Trunk->Place(3);
|
|
Tree_Deciduous->Place(12);
|
|
Tree_Coniferous2->Place(2);
|
|
Tree_Coniferous3->Place(2);
|
|
return;
|
|
}
|
|
|
|
private func InitAnimals()
|
|
{
|
|
// Some animals as atmosphere.
|
|
Butterfly->Place(20);
|
|
Zaphive->Place(2);
|
|
Mosquito->Place(2);
|
|
return;
|
|
}
|
|
|
|
// Initializes the AI: which is all defined in System.ocg
|
|
private func InitAI()
|
|
{
|
|
// Village head.
|
|
var npc_head = CreateObjectAbove(Clonk, 250, 558);
|
|
npc_head->SetName("Archibald");
|
|
npc_head->SetObjectLayer(npc_head);
|
|
npc_head->SetDir(DIR_Left);
|
|
npc_head->SetDialogue("VillageHead");
|
|
npc_head->SetAlternativeSkin("Sage");
|
|
|
|
// Skyville head.
|
|
var npc_head = CreateObjectAbove(Clonk, 260, 558);
|
|
npc_head->SetName("Ludwig");
|
|
npc_head->SetObjectLayer(npc_head);
|
|
npc_head->SetDir(DIR_Left);
|
|
npc_head->SetDialogue("SkyvilleHead");
|
|
npc_head->SetAlternativeSkin("MaleBlackHair");
|
|
return;
|
|
}
|
|
|
|
/*-- Player Handling --*/
|
|
|
|
protected func InitializePlayer(int plr)
|
|
{
|
|
// Position player's clonk.
|
|
var clonk = GetCrew(plr);
|
|
clonk->SetPosition(220, 558);
|
|
clonk->CreateContents(Shovel);
|
|
var effect = AddEffect("ClonkRestore", clonk, 100, 10);
|
|
effect.to_x = 220;
|
|
effect.to_y = 558;
|
|
|
|
// Standard player zoom for tutorials.
|
|
SetPlayerViewLock(plr, true);
|
|
SetPlayerZoomByViewRange(plr, 300, nil, PLRZOOM_Direct | PLRZOOM_Set | PLRZOOM_LimitMax);
|
|
SetPlayerZoomByViewRange(plr, LandscapeWidth(), nil, PLRZOOM_LimitMax);
|
|
|
|
// Claim ownership of structures.
|
|
for (var structure in FindObjects(Find_Or(Find_Category(C4D_Structure), Find_Func("IsFlagpole"))))
|
|
structure->SetOwner(plr);
|
|
|
|
// Knowledge and base material for this round.
|
|
SetPlrKnowledge(plr, Compensator);
|
|
SetPlrKnowledge(plr, SteamEngine);
|
|
SetPlrKnowledge(plr, Airplane);
|
|
SetBaseMaterial(plr, Metal, 20);
|
|
|
|
// Set wealth to buy items.
|
|
SetWealth(plr, 400);
|
|
|
|
// Add an effect to the clonk to track the goal.
|
|
var track_goal = AddEffect("TrackGoal", nil, 100, 2);
|
|
track_goal.plr = plr;
|
|
|
|
// Start the intro sequence.
|
|
StartSequence("Intro", 0, plr);
|
|
|
|
// Create tutorial guide and control effect.
|
|
guide = CreateObject(TutorialGuide, 0, 0, plr);
|
|
guide->HideGuide();
|
|
var effect = AddEffect("TutorialReachedSkyIsland", nil, 100, 5);
|
|
effect.plr = plr;
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Intro, Tutorial Goal & Outro --*/
|
|
|
|
global func FxTrackGoalTimer(object target, proplist effect, int time)
|
|
{
|
|
if (FindObject(Find_ID(Airplane)))
|
|
{
|
|
StartSequence("Outro", 0, effect.plr);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
public func ShowLastGuideMessage()
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialCompleted$");
|
|
guide->ShowGuideMessage();
|
|
guide->ShowGuide();
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Guide Messages --*/
|
|
|
|
public func OnIntroSequenceFinished()
|
|
{
|
|
var interact = GetPlayerControlAssignment(guide->GetOwner(), CON_Interact, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialTakeElevator$", interact));
|
|
guide->ShowGuide();
|
|
guide->ShowGuideMessage();
|
|
return;
|
|
}
|
|
|
|
global func FxTutorialReachedSkyIslandTimer(object target, proplist effect)
|
|
{
|
|
var crew = GetCrew(effect.plr);
|
|
if (crew && FindObject(Find_ID(Hammer), Find_Container(crew)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialBuildCompensator$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialPlacedCompensatorSite", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialPlacedCompensatorSiteTimer(object target, proplist effect)
|
|
{
|
|
var site = FindObject(Find_ID(ConstructionSite), Find_AtRect(160, 260, 80, 40));
|
|
var compensator = FindObject(Find_ID(Compensator), Find_AtRect(160, 260, 80, 40));
|
|
if ((site && site.definition == Compensator) || compensator)
|
|
{
|
|
var interaction_menu = GetPlayerControlAssignment(effect.plr, CON_Contents, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialBuyMetal$", interaction_menu));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialFinishedCompensator", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFinishedCompensatorTimer(object target, proplist effect)
|
|
{
|
|
if (FindObject(Find_ID(Compensator), Find_AtRect(160, 260, 80, 40)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialPowerOverview$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialLookedAtPowerOverview", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialLookedAtPowerOverviewTimer(object target, proplist effect)
|
|
{
|
|
// TODO: fix me (should be triggered when the E menu is openend).
|
|
if (effect.looked_at || true)
|
|
{
|
|
var interact = GetPlayerControlAssignment(effect.plr, CON_Interact, true, true);
|
|
var interact_cycle = GetPlayerControlAssignment(effect.plr, CON_InteractNext_CycleObject, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialEnterCatapult$", interact, interact_cycle, interact));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialEnteredCatapult", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialEnteredCatapultTimer(object target, proplist effect)
|
|
{
|
|
var catapult = FindObject(Find_ID(Catapult), Find_Distance(80, 432, 248));
|
|
if (catapult && GetCrew(effect.plr) == FindObject(Find_OCF(OCF_CrewMember), Find_Container(catapult)))
|
|
{
|
|
var zoom_in = GetPlayerControlAssignment(effect.plr, CON_WheelZoomIn, true, true);
|
|
var zoom_out = GetPlayerControlAssignment(effect.plr, CON_WheelZoomOut, true, true);
|
|
var use = GetPlayerControlAssignment(effect.plr, CON_Use, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialLaunchCatapult$", zoom_in, zoom_out, use));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialReachedMiddleIsland", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialReachedMiddleIslandTimer(object target, proplist effect)
|
|
{
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr), Find_InRect(744, 336, 300, 280)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialBuildSteamEngine$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialPlacedSteamEngineSite", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialPlacedSteamEngineSiteTimer(object target, proplist effect)
|
|
{
|
|
var site = FindObject(Find_ID(ConstructionSite), Find_AtRect(832, 400, 72, 56));
|
|
if (site && site.definition == SteamEngine)
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialTransportMaterials$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialReachedTopIsland", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialReachedTopIslandTimer(object target, proplist effect)
|
|
{
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr), Find_InRect(200, 200, 200, 40)))
|
|
{
|
|
var interact = GetPlayerControlAssignment(effect.plr, CON_Interact, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialShootCoalAndMetal$", interact));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialReachedMiddleIslandSecond", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialReachedMiddleIslandSecondTimer(object target, proplist effect)
|
|
{
|
|
if (FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr), Find_InRect(744, 336, 300, 280)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFinishSteamEngine$");
|
|
guide->ShowGuideMessage();
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
protected func OnGuideMessageShown(int plr, int index)
|
|
{
|
|
// Show the location for the compensator.
|
|
if (index == 1)
|
|
{
|
|
TutArrowShowPos(200, 300, 135);
|
|
}
|
|
|
|
// Show the location for the steam engine.
|
|
if (index == 6)
|
|
{
|
|
TutArrowShowPos(872, 456, 135);
|
|
}
|
|
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.
|
|
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");
|
|
GameCall("OnClonkRestore", clonk);
|
|
}
|
|
return FX_OK;
|
|
}
|