From fb400456ce89da6f62c6d41884192a7dd85d2696 Mon Sep 17 00:00:00 2001 From: Sven Eberhardt Date: Sun, 20 Nov 2016 12:53:46 -0500 Subject: [PATCH] Add MovingBrick editorprops and graph movement --- .../Bricks.ocd/MovingBrick.ocd/Script.c | 384 +++++++++++++++++- .../MovingBrick.ocd/StringTblDE.txt | 35 ++ .../MovingBrick.ocd/StringTblUS.txt | 35 ++ 3 files changed, 448 insertions(+), 6 deletions(-) create mode 100644 planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblDE.txt create mode 100644 planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblUS.txt diff --git a/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/Script.c b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/Script.c index bbfcf87f9..e49dd2338 100644 --- a/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/Script.c +++ b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/Script.c @@ -1,19 +1,44 @@ /*-- Moving Bricks - --*/ local size; // Length of the brick. +// Constants +local MovementType = { // enum + None = nil, + Horizontal = 1, + Vertical = 2, + Graph = 3 +}; +local EdgeSpeed_Infinite = -1; +local EdgeAdvancement = { // enum: How to select the next edge when advancing on the graph + Random = nil, + Clockwise = 1, + Counterclockwise = 2 +}; + +// Movement properties (for editor) +local movement; +local movement_speed = 100; +// Movement on graph +local current_edge_index, current_vertex_index, target_vertex_index; +local vertex_wait_time; +local last_vx, last_vy, target_pos_x, target_pos_y; + + protected func Initialize() { + // No movement by default + movement = { Type=MovementType.None }; // Size defaults to four. SetSize(4); // Allow for dynamically changing speeds. ActMap = { Prototype = this.Prototype.ActMap }; ActMap.Moving = { Prototype = ActMap.Moving }; + movement_speed = ActMap.Moving.Speed; // Set floating action. SetAction("Moving"); @@ -36,21 +61,35 @@ public func SetSize(int to_size) public func SetMoveSpeed(int speed) { - ActMap.Moving.Speed = Max(0, speed); + movement_speed = ActMap.Moving.Speed = Max(0, speed); return; } +public func ClearMovement() +{ + RemoveEffect("MoveHorizontal", this); + RemoveEffect("MoveVertical", this); + RemoveEffect("MoveOnGraph", this); + movement = { Type=MovementType.None }; + current_edge_index = current_vertex_index = -1; + SetComDir(COMD_None); + SetXDir(); SetYDir(); + return true; +} + /*-- Horizontal movement --*/ public func MoveHorizontal(int left, int right, int speed) { - RemoveEffect("MoveHorizontal", this); RemoveEffect("MoveVertical", this); + ClearMovement(); var effect = AddEffect("MoveHorizontal", this, 100, 1, this); effect.Left = left; effect.Right = right; if (speed != nil) SetMoveSpeed(10 * speed); if (GetComDir() != COMD_Right) SetComDir(COMD_Left); + // Props for editor display of movement range + movement = { Type = MovementType.Horizontal, Graph = [{X=left, Y=GetY()}, {X=right, Y=GetY()}] }; return; } @@ -71,13 +110,15 @@ private func FxMoveHorizontalTimer(object target, proplist effect) public func MoveVertical(int top, int bottom, int speed) { - RemoveEffect("MoveHorizontal", this); RemoveEffect("MoveVertical", this); + ClearMovement(); var effect = AddEffect("MoveVertical", this, 100, 1, this); effect.Top = top; effect.Bottom = bottom; if (speed != nil) SetMoveSpeed(10 * speed); if (GetComDir() != COMD_Down) SetComDir(COMD_Up); + // Props for editor display of movement range + movement = { Type=MovementType.Vertical, Graph = [{X=GetX(), Y=top}, {X=GetX(), Y=bottom}] }; return; } @@ -89,18 +130,140 @@ private func FxMoveVerticalTimer(object target, proplist effect) if (target->GetY() < effect.Top + 7) if (target->GetComDir() == COMD_Up) - SetComDir(COMD_Down); + SetComDir(COMD_Down); return 1; } + +/*-- Movement on graph --*/ + +public func MoveOnGraph(proplist graph) +{ + ClearMovement(); + movement = { Type = MovementType.Graph, Graph = graph }; + OnGraphUpdate(movement.Graph); + var effect = AddEffect("MoveOnGraph", this, 100, 1, this); + return; +} + +private func FxMoveOnGraphTimer(object target, proplist effect) +{ + // Vertex wait time? + if (vertex_wait_time) + { + if (!--vertex_wait_time) + { + OnVertexReached(current_vertex_index, true); + } + return; + } + // Check if the target vertex has been reached + var dx = target_pos_x - GetX(); + var dy = target_pos_y - GetY(); + if (dx * last_vx + dy * last_vy <= 0) + { + OnVertexReached(target_vertex_index); + } + return 1; +} + +private func StartEdgeMovement(int edge_index, int edge_direction) +{ + // Initiate movement towards target vertex + //Log("%v Start edge movement %v (%v)", this, edge_index, edge_direction); + current_edge_index = edge_index; + current_vertex_index = -1; + var edge = movement.Graph.Edges[edge_index]; + target_vertex_index = edge.Vertices[edge_direction]; + var target_vertex = movement.Graph.Vertices[target_vertex_index]; + target_pos_x = target_vertex.X; + target_pos_y = target_vertex.Y; + var speed = edge.Speed ?? movement_speed; + if (speed == EdgeSpeed_Infinite) + { + SetPosition(target_pos_x, target_pos_y); + speed = 100; // For trigger + } + else + { + var dx = target_pos_x - GetX(); + var dy = target_pos_y - GetY(); + var d = Max(1, Distance(dx, dy)); + SetXDir((last_vx = speed * dx / d), 100); + SetYDir((last_vy = speed * dy / d), 100); + ActMap.Moving.Speed = Max(0, speed); + } +} + +private func OnVertexReached(int vertex_index, bool after_wait) +{ + //Log("Vertex reached %v (%v)", vertex_index, after_wait); + // Safety + if (vertex_index < 0 || vertex_index >= GetLength(movement.Graph.Vertices)) + { + vertex_index = 0; + } + // Called when a vertex has been reached. Perform vertex action & continue movement + current_vertex_index = vertex_index; + target_vertex_index = -1; + var vertex = movement.Graph.Vertices[vertex_index]; + if (!after_wait) + { + // Stop for now (befure UserAction because UserAction may do movement) + SetXDir(); SetYDir(); + // Vertex action + if (vertex.ArrivalAction) + { + UserAction->EvaluateAction(vertex.ArrivalAction, this); + } + // Vertex wait time? Finish for now; function Will be called again later. + if (vertex.WaitTime) + { + vertex_wait_time = vertex.WaitTime; + return; + } + } + // Find next vertex to continue to + var vertex = movement.Graph.Vertices[vertex_index]; + var n_edges = GetLength(vertex._edges); + if (!n_edges) return; // Stuck on an unconnected vertex + // First figure out where we came from + var last_edge_index = -1; // index in vertex._edges array + if (current_edge_index >= 0) + { + last_edge_index = GetIndexOf(vertex._edges, movement.Graph.Edges[current_edge_index]); + } + // Advance in specified order + var advancement = vertex.Advancement; + var next_edge_index = 0; // index in vertex._edges array + if (advancement == EdgeAdvancement.Random) + { + var exclude_current = (last_edge_index >= 0 && n_edges > 1); + next_edge_index = Random(n_edges - exclude_current); + if (next_edge_index == last_edge_index) next_edge_index += exclude_current; + } + else if (advancement == EdgeAdvancement.Clockwise) + { + next_edge_index = (last_edge_index + 1) % n_edges; + } + else if (advancement == EdgeAdvancement.Counterclockwise) + { + next_edge_index = last_edge_index - 1; + if (next_edge_index < 0) next_edge_index = n_edges - 1; + } + var next_edge = vertex._edges[next_edge_index]; + StartEdgeMovement(next_edge._index, next_edge.Vertices[0] == current_vertex_index); +} + + /* Scenario saving */ func SaveScenarioObject(props) { if (!inherited(props, ...)) return false; if (size != 4) props->AddCall("Size", this, "SetSize", size); - if (ActMap.Moving.Speed != GetID().ActMap.Moving.Speed) props->AddCall("MoveSpeed", this, "SetMoveSpeed", ActMap.Moving.Speed); + if (movement_speed != GetID().ActMap.Moving.Speed) props->AddCall("MoveSpeed", this, "SetMoveSpeed", movement_speed); if (GetComDir() == COMD_None) props->Remove("ComDir"); return true; } @@ -117,6 +280,215 @@ func FxMoveVerticalSaveScen(obj, fx, props) return true; } +func FxMoveGraphSaveScen(obj, fx, props) +{ + props->AddCall("Move", obj, "MoveOnGraph", obj.movement_graph); + return true; +} + + +/* Editor */ + +public func SetMoveType(new_movement) +{ + var movement_type = new_movement.Type; + // Set movement in editor: Set default graphs + if (movement_type == MovementType.Horizontal) + { + MoveHorizontal(Max(GetX()-50, 10), Min(GetX()+50, LandscapeWidth()-10)); + } + else if (movement_type == MovementType.Vertical) + { + MoveVertical(Max(GetY()-50, 10), Min(GetY()+50, LandscapeHeight()-10)); + } + else if (movement_type == MovementType.Graph) + { + var x = BoundBy(GetX(), 30, LandscapeWidth()-30), y = BoundBy(GetY(), 50, LandscapeHeight()-50); + MoveOnGraph({ Vertices=[{X=x, Y=y}, {X=x-20, Y=y-40}, {X=x+20, Y=y-40}, {X=x, Y=y+40}], Edges=[{Vertices=[0, 1]}, {Vertices=[0, 2]}, {Vertices=[0, 3]}] }); + } + else + { + ClearMovement(); + } + return true; +} + +public func Definition(def) +{ + if (!def.EditorProps) def.EditorProps = {}; + def.EditorProps.movement_speed = { Name="$Speed$", EditorHelp="$SpeedHelp$", Type="int", Min=5, Set="SetMoveSpeed" }; + def.EditorProps.movement = { Name="$Movement$", EditorHelp="$MovementHelp$", Type="enum", Set="SetMoveType", OptionKey="Type", ValueKey="Graph", Options=[ + { Name="$NoMovement$", EditorHelp="$NoMovementHelp$", Value={Type=MovementType.None} }, + { Name="$Horizontal$", EditorHelp="$HorizontalHelp$", Value={Type=MovementType.Horizontal}, Delegate={ Type="polyline", VerticalFix=true, StructureFix=true, OnUpdate="OnHorizontalGraphUpdate", Relative=false, Color=0xff2010 } }, + { Name="$Vertical$", EditorHelp="$VerticalHelp$", Value={Type=MovementType.Vertical}, Delegate={ Type="polyline", HorizontalFix=true, StructureFix=true, OnUpdate="OnVerticalGraphUpdate", Relative=false, Color=0x20ff10 } }, + { Name="$Graph$", EditorHelp="$GraphHelp$", Value={Type=MovementType.Graph}, Delegate={ Type="graph", OnUpdate="OnGraphUpdate", Relative=false, Color=0xef8000, + EdgeDelegate = { Name="$Edge$", EditorHelp="$EdgeHelp$", EditorProps={ + Speed = { Name="$Speed$", EditorHelp="$EdgeSpeedHelp$", Type="enum", Options=[ + {Name="$Default$", EditorHelp="$DefaultHelp$"}, + {Name="$Instant$", EditorHelp="$InstantSpeedHelp$", Value=EdgeSpeed_Infinite }, + {Name="$CustomSpeed$", EditorHelp="$CustomSpeedHelp$", Type=C4V_Int, Delegate={Type="int", Min=5} } + ] }, + } }, + VertexDelegate = { Name="$Vertex$", EditorHelp="$VertexHelp$", EditorProps={ + ArrivalAction = new UserAction.Prop { Name="$ArrivalAction$", EditorHelp="$ArrivalActionHelp$" }, + WaitTime = {Name="$VertexWaitTime$", EditorHelp="$VertexWaitTimeHelp$", Type="int", Min=0}, + Advancement = { Name="$Advancement$", EditorHelp="$AdvancementHelp$", Type="enum", Options=[ + { Name="$Random$", EditorHelp="$RandomAdvancementHelp$", Value=EdgeAdvancement.Random }, + { Name="$Clockwise$", EditorHelp="$CWAdvancementHelp$", Value=EdgeAdvancement.Clockwise }, + { Name="$Counterclockwise$", EditorHelp="$CCWAdvancementHelp$", Value=EdgeAdvancement.Counterclockwise } + ] } + } }, + } }, + ]}; + // TODO: Button to flip direction +} + +public func EditCursorMoved(int old_x, int old_y, bool movement_finished) +{ + // Move horizontal/vertical graph elements + if (movement.Type == MovementType.Horizontal) + { + movement.Graph = [{X=movement.Graph[0].X, Y=GetY()}, {X=movement.Graph[1].X, Y=GetY()}]; + } + else if (movement.Type == MovementType.Vertical) + { + movement.Graph = [{Y=movement.Graph[0].Y, X=GetX()}, {Y=movement.Graph[1].Y, X=GetX()}]; + } + else if (movement.Type == MovementType.Graph) + { + // Snap to nearest edge if mouse is let go + if (movement_finished) + { + SetPositionToNearestEdge(); + } + else + { + // Stop movment during drag (will be reactivated in SetPositionToNearestEdge) + ActMap.Moving.Speed = 0; + } + } + // return true to signal engine to update shapes + return true; +} + +private func OnHorizontalGraphUpdate(array vertices) +{ + var fx = GetEffect("MoveHorizontal", this); + if (fx) + { + fx.Left = Min(vertices[0].X, vertices[1].X); + fx.Right = Max(vertices[0].X, vertices[1].X); + } +} + +private func OnVerticalGraphUpdate(array vertices) +{ + var fx = GetEffect("MoveVertical", this); + if (fx) + { + fx.Top = Min(vertices[0].Y, vertices[1].Y); + fx.Bottom = Max(vertices[0].Y, vertices[1].Y); + } +} + +private func OnGraphUpdate(proplist graph) +{ + // Graph update + // Update vertex indices + var vertex, ivertex = 0, n, edge, iedge = 0; + for (vertex in graph.Vertices) + { + vertex._index = ivertex++; + } + for (edge in graph.Edges) + { + edge._index = iedge++; + } + // Update all connectivities + for (vertex in graph.Vertices) + { + // Order connected edges by angle + n = 0; + vertex._edges = []; + for (var edge in graph.Edges) + { + var evtxidx = GetIndexOf(edge.Vertices, vertex._index); + if (evtxidx > -1) + { + var connected_vertex = graph.Vertices[edge.Vertices[1 - evtxidx]]; + vertex._edges[n++] = edge; + edge._angle = Angle(vertex.X, vertex.Y, connected_vertex.X, connected_vertex.Y); + } + } + // Order them by angle to allow clockwise/counter-clockwise graph traversal + SortArrayByProperty(vertex._edges, "_angle"); + } + // Reset position to closest edge + SetPositionToNearestEdge(); + // Done + return true; +} + +private func SetPositionToNearestEdge() +{ + // First check if we're close to a current vertex + var x = GetX(), y = GetY(); + //Log("x=%d, y=%d", x, y); + if (current_vertex_index >= 0 && current_vertex_index < GetLength(movement.Graph.Vertices)) + { + var current_vertex = movement.Graph.Vertices[current_vertex_index]; + if (Distance(current_vertex.X, current_vertex.Y, x, y) < 8) + { + // no reset necessery. But do restart edge movement if e.g. stuck on an unconnected vertex that just got connected. + if (!vertex_wait_time) OnVertexReached(current_vertex_index, true); + } + } + current_vertex_index = target_vertex_index = -1; + vertex_wait_time = 0; + // Reset position to closest edge + var best_distance, edge_index = 0, best_edge; + var proj_x, proj_y, edge_direction; + for (var edge in movement.Graph.Edges) + { + var v0 = movement.Graph.Vertices[edge.Vertices[0]], v1 = movement.Graph.Vertices[edge.Vertices[1]]; + var dx0 = x - v0.X, dy0 = y - v0.Y; + var dx1 = v1.X - v0.X, dy1 = v1.Y - v0.Y; + var d = dx1*dx1 + dy1*dy1; + // Perpendicular distance + var dp = Abs(dx0 * dy1 - dx1 * dy0); + // Penalty if outside edge along the edge axis + var da = dx0 * dx1 + dy0 * dy1; + // Total distance (multiplied by d1 length) + var distance = dp + Max(-da) + Max(da - d); + //Log("%d (%v, %v) dist = %d", edge_index, {X=v0.X, Y=v0.Y, idx=v0._index}, {X=v1.X, Y=v1.Y, idx=v1._index}, distance); + //Log("dx0=%d, dx1=%d, dy0=%d, dy1=%d, d=%d, dp=%d, da=%d, distance=%d", dx0, dx1, dy0, dy1, d, dp, da, distance); + if (!edge_index || distance < best_distance) + { + best_edge = edge_index; + best_distance = distance; + edge_direction = (last_vx * dx1 + last_vy * dy1 >= 0); + if (best_edge == current_edge_index && best_distance/Distance(dx1, dy1) < 6) + { + // Stay on current edge + StartEdgeMovement(best_edge, edge_direction); + return; + } + da = BoundBy(da, 0, d); // Da da da! Stay inside edge range! + proj_x = x - dx0 + dx1 * da / d; + proj_y = y - dy0 + dy1 * da / d; + } + ++edge_index; + } + current_edge_index = -1; + if (!edge_index) return; // No edges. Nothing to do. + //Log("Snap to edge %d", best_edge); + // Project position onto closest edge + SetPosition(proj_x, proj_y); + // Start movement along this edge + StartEdgeMovement(best_edge, edge_direction); +} + + /* Properties */ local ActMap = { diff --git a/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblDE.txt b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblDE.txt new file mode 100644 index 000000000..bce47541d --- /dev/null +++ b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblDE.txt @@ -0,0 +1,35 @@ +Speed=Geschwindigkeit +SpeedHelp=Bewegungsgeschwindigkeit in 1/100px pro Frame +Movement=Bewegung +MovementHelp=In welchem Muster sich dieser Stein bewegem soll. +NoMovement=Keine +NoMovementHelp=Der Stein schwebt still in der Luft, wie es Steine so tun. +Horizontal=Horizontal +HorizontalHelp=Horizontal nach links und rechts zwischen zwei Punkten. +Vertical=Vertikal +VerticalHelp=Vertikal nach oben und unten zwischen zwei Punkten. +Graph=Graph +GraphHelp=Bewegung auf einem frei definierbaren Graphen. Punkte hinzufuegen/entfernen mit Umschalt+Steuerung. +Edge=Kante +EdgeHelp=Kanteneigenschaften +EdgeSpeedHelp=Bewegungsgeschwindigkeit in 1/100px pro Frame auf dieser Kante. +Default=Standard +DefaultHelp=Geschwindigkeit wie im Objekt eingestellt. +CustomSpeed=Benutzerdefiniert +CustomSpeedHelp=Diese Kante hat eine eigene Geschwindigkeit. +Vertex=Eckpunkt +VertexHelp=Eigenschaften fuer diesen Knotenpunkt. +ArrivalAction=Aktion +ArrivalActionHelp=Aktion, die bei Ankunft des Steins an diesem Eckpunkt ausgefuehrt wird. +VertexWaitTime=Wartezeit +VertexWaitTimeHelp=Wie lange der Stein an diesem Eckpunkt warten soll, ehe er sich weiter bewegt. +Advancement=Bewegungsrichtung +AdvancementHelp=In welche Richtung sich der Stein nach Ankunft an diesem Eckpunkt weiter bewegen soll. +Random=Zufaellig +RandomAdvancementHelp=Waehlt eine zufaellige Richtung (ausser der Ankunftsrichtung). +Clockwise=Im Uhrzeigersinn +CWAdvancementHelp=Der Stein bewegt sich im Uhrzeigersinn von der Ankunftsrichtung weiter. +Counterclockwise=Gegen den Uhrzeigersinn +CCWAdvancementHelp=Der Stein bewegt sich gegen den Uhrzeigersinn von der Ankunftsrichtung weiter. +Instant=Instantan +InstantSpeedHelp=Der Stein wird sofort zur naechsten Ecke bewegt. diff --git a/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblUS.txt b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblUS.txt new file mode 100644 index 000000000..da799d6c7 --- /dev/null +++ b/planet/Objects.ocd/Environment.ocd/Bricks.ocd/MovingBrick.ocd/StringTblUS.txt @@ -0,0 +1,35 @@ +Speed=Speed +SpeedHelp=Movement speed in 1/100px per frame. +Movement=Movement +MovementHelp=In which pattern this brick shall be moved. +NoMovement=None +NoMovementHelp=The brick does not move. +Horizontal=Horizontal +HorizontalHelp=Horizontally from left to right between two points. +Vertical=Vertical +VerticalHelp=Vertically from top to bottom between two points. +Graph=Graph +GraphHelp=Movement on a freely definable graph. Add/remove points with ctrl/shift. +Edge=Edge +EdgeHelp=Edge properties +EdgeSpeedHelp=Movement speed in 1/100px per frame on this edge only. +Default=Default +DefaultHelp=Speed as specified in the object. +CustomSpeed=Custom +CustomSpeedHelp=This edge has a custom speed. +Vertex=Vertex +VertexHelp=Properties for this vertex. +ArrivalAction=Action +ArrivalActionHelp=Action when the brick reaches this vertex. +VertexWaitTime=Wait time +VertexWaitTimeHelp=How long to wait at this location before movement is continued. +Advancement=Movement direction +AdvancementHelp=In which direction to continue movement after arrival at this edge point. +Random=Random +RandomAdvancementHelp=Picks a random direction (excluding where it just came from). +Clockwise=Clockwise +CWAdvancementHelp=The brick moves clockwise relative to where it came from. +Counterclockwise=Counter-clockwise +CCWAdvancementHelp=The brick moves counter-clockwise relative to where it came from. +Instant=Instantanuous +InstantSpeedHelp=The brick is beamed directly to the next vertex.