protected StateMachine ParseStateMachine(Actor actor)
        {
            string[] lines = actor.StatesText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);

            var states = new List<State>();
            var labels = new Dictionary<string, State>();

            // non-root actors inheirit state machine from their parents
            if (actor.Parent != null)
            {
                StateMachine parentMachine = actor.Parent.StateMachine.CloneForChild(actor.Parent.Name);
                states = parentMachine.States.ToList();
                labels = parentMachine.Labels.ToDictionary(p => p.Key, p => p.Value);
            }

            var openGoTos = new Dictionary<GoToState, KeyValuePair<string, int>>();

            List<string> openLabels = new List<string>();

            string lastLabel = string.Empty;

            foreach (string line in lines)
            {
                string trimmed = line.Trim();

                if (trimmed == string.Empty)
                {
                    continue;
                }

                string framePattern = @"
                    ([A-Za-z0-9_\-""#]{4,6})    # sprite name
                    \s+
                    ([A-Za-z\[\]\\""#]+)         # frame list
                    \s+
                    (-?\d+)                     # duration
                    (\s+bright\s+)              # bright yes/no
                    ?(.*)                       # code pointer
                ";

                Match labelMatch = Regex.Match(line, "([A-Za-z0-9_.]+):", RegexOptions.IgnoreCase);
                Match gotoMatch = Regex.Match(line, @"goto\s+([A-Za-z0-9_:.]+)?(\s*\+\s*(\d+))?", RegexOptions.IgnoreCase);
                Match frameMatch = Regex.Match(line, framePattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

                List<State> currentLineStates = new List<State>();

                if (trimmed.Equals("fail", StringComparison.OrdinalIgnoreCase))
                {
                    currentLineStates.Add(new FailState());
                }
                else if (trimmed.Equals("stop", StringComparison.OrdinalIgnoreCase))
                {
                    currentLineStates.Add(new StopState());
                }
                else if (trimmed.Equals("loop", StringComparison.OrdinalIgnoreCase))
                {
                    GoToState state = new GoToState();
                    openGoTos.Add(state, new KeyValuePair<string, int>(lastLabel, 0));
                    currentLineStates.Add(state);
                }
                else if (trimmed.Equals("wait", StringComparison.OrdinalIgnoreCase))
                {
                    GoToState state = new GoToState();
                    state.Target = states.Last();
                    currentLineStates.Add(state);
                }
                else if (gotoMatch.Success)
                {
                    int offset = 0;

                    int.TryParse(gotoMatch.Groups[3].Value, out offset);

                    State state = new GoToState();
                    openGoTos.Add(state as GoToState, new KeyValuePair<string, int>(gotoMatch.Groups[1].Value.ToLower(), offset));
                    currentLineStates.Add(state);
                }
                else if (labelMatch.Success)
                {
                    openLabels.Add(labelMatch.Groups[1].Value.ToLower());
                    lastLabel = labelMatch.Groups[1].Value.ToLower();
                }
                else if (frameMatch.Success)
                {
                    foreach (char frame in frameMatch.Groups[2].Value.TrimQuotes())
                    {
                        FrameState newState = new FrameState()
                        {
                            Sprite = frameMatch.Groups[1].Value,
                            Frame = frame,
                            Duration = int.Parse(frameMatch.Groups[3].Value),
                            IsBright = frameMatch.Groups[4].Value != string.Empty,
                            CodePointer = frameMatch.Groups[5].Value
                        };

                        currentLineStates.Add(newState);
                    }
                }
                else
                {
                    throw new Exception("Could not parse state line \"" + trimmed + "\" in actor \"" + actor.Name + "\" in file \"" + actor.File.FullName + "\".");
                }

                if (currentLineStates.Count > 0)
                {
                    foreach (string label in openLabels)
                    {
                        labels[label] = currentLineStates[0];
                    }

                    openLabels.Clear();
                }

                states.AddRange(currentLineStates);
            }

            foreach (var openGoTo in openGoTos)
            {
                GoToState state = openGoTo.Key;
                string targetLabel = openGoTo.Value.Key;
                int offset = openGoTo.Value.Value;

                State targetState = null;

                if (targetLabel == string.Empty)
                {
                    targetState = state;
                }
                else
                {
                    if (!labels.ContainsKey(targetLabel))
                    {
                        throw new Exception("Jump target \"" + targetLabel + "\" not found in actor \"" + actor.Name + "\" in file \"" + actor.File.FullName + "\".");
                    }

                    targetState = labels[targetLabel];
                }

                int stateIndex = states.IndexOf(targetState);

                Debug.Assert(stateIndex >= 0, "Goto target state not found!");

                state.Target = states[stateIndex + offset];

                // only FrameStates count towards the offset
                int numStatesToSkip = offset;
                int i = stateIndex;
                state.Target = states[i];
                while (numStatesToSkip >= 1)
                {
                    i++;
                    state.Target = states[i];

                    if (state.Target.CountsAsState)
                    {
                        numStatesToSkip--;
                    }
                }
            }

            return new StateMachine(states, labels);
        }
        protected StateMachine ParseStateMachine(Actor actor)
        {
            string[] lines = actor.StatesText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);

            var states = new List <State>();
            var labels = new Dictionary <string, State>();

            // non-root actors inheirit state machine from their parents
            if (actor.Parent != null)
            {
                StateMachine parentMachine = actor.Parent.StateMachine.CloneForChild(actor.Parent.Name);
                states = parentMachine.States.ToList();
                labels = parentMachine.Labels.ToDictionary(p => p.Key, p => p.Value);
            }

            var openGoTos = new Dictionary <GoToState, KeyValuePair <string, int> >();

            List <string> openLabels = new List <string>();

            string lastLabel = string.Empty;

            foreach (string line in lines)
            {
                string trimmed = line.Trim();

                if (trimmed == string.Empty)
                {
                    continue;
                }

                string framePattern = @"
                    ([A-Za-z0-9_\-""#]{4,6})    # sprite name
                    \s+
                    ([A-Za-z\[\]\\""#]+)         # frame list
                    \s+                         
                    (-?\d+)                     # duration
                    (\s+bright\s+)              # bright yes/no
                    ?(.*)                       # code pointer
                ";

                Match labelMatch = Regex.Match(line, "([A-Za-z0-9_.]+):", RegexOptions.IgnoreCase);
                Match gotoMatch  = Regex.Match(line, @"goto\s+([A-Za-z0-9_:.]+)?(\s*\+\s*(\d+))?", RegexOptions.IgnoreCase);
                Match frameMatch = Regex.Match(line, framePattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

                List <State> currentLineStates = new List <State>();

                if (trimmed.Equals("fail", StringComparison.OrdinalIgnoreCase))
                {
                    currentLineStates.Add(new FailState());
                }
                else if (trimmed.Equals("stop", StringComparison.OrdinalIgnoreCase))
                {
                    currentLineStates.Add(new StopState());
                }
                else if (trimmed.Equals("loop", StringComparison.OrdinalIgnoreCase))
                {
                    GoToState state = new GoToState();
                    openGoTos.Add(state, new KeyValuePair <string, int>(lastLabel, 0));
                    currentLineStates.Add(state);
                }
                else if (trimmed.Equals("wait", StringComparison.OrdinalIgnoreCase))
                {
                    GoToState state = new GoToState();
                    state.Target = states.Last();
                    currentLineStates.Add(state);
                }
                else if (gotoMatch.Success)
                {
                    int offset = 0;

                    int.TryParse(gotoMatch.Groups[3].Value, out offset);

                    State state = new GoToState();
                    openGoTos.Add(state as GoToState, new KeyValuePair <string, int>(gotoMatch.Groups[1].Value.ToLower(), offset));
                    currentLineStates.Add(state);
                }
                else if (labelMatch.Success)
                {
                    openLabels.Add(labelMatch.Groups[1].Value.ToLower());
                    lastLabel = labelMatch.Groups[1].Value.ToLower();
                }
                else if (frameMatch.Success)
                {
                    foreach (char frame in frameMatch.Groups[2].Value.TrimQuotes())
                    {
                        FrameState newState = new FrameState()
                        {
                            Sprite      = frameMatch.Groups[1].Value,
                            Frame       = frame,
                            Duration    = int.Parse(frameMatch.Groups[3].Value),
                            IsBright    = frameMatch.Groups[4].Value != string.Empty,
                            CodePointer = frameMatch.Groups[5].Value
                        };

                        currentLineStates.Add(newState);
                    }
                }
                else
                {
                    throw new Exception("Could not parse state line \"" + trimmed + "\" in actor \"" + actor.Name + "\" in file \"" + actor.File.FullName + "\".");
                }

                if (currentLineStates.Count > 0)
                {
                    foreach (string label in openLabels)
                    {
                        labels[label] = currentLineStates[0];
                    }

                    openLabels.Clear();
                }

                states.AddRange(currentLineStates);
            }

            foreach (var openGoTo in openGoTos)
            {
                GoToState state       = openGoTo.Key;
                string    targetLabel = openGoTo.Value.Key;
                int       offset      = openGoTo.Value.Value;

                State targetState = null;

                if (targetLabel == string.Empty)
                {
                    targetState = state;
                }
                else
                {
                    if (!labels.ContainsKey(targetLabel))
                    {
                        throw new Exception("Jump target \"" + targetLabel + "\" not found in actor \"" + actor.Name + "\" in file \"" + actor.File.FullName + "\".");
                    }

                    targetState = labels[targetLabel];
                }

                int stateIndex = states.IndexOf(targetState);

                Debug.Assert(stateIndex >= 0, "Goto target state not found!");

                state.Target = states[stateIndex + offset];

                // only FrameStates count towards the offset
                int numStatesToSkip = offset;
                int i = stateIndex;
                state.Target = states[i];
                while (numStatesToSkip >= 1)
                {
                    i++;
                    state.Target = states[i];

                    if (state.Target.CountsAsState)
                    {
                        numStatesToSkip--;
                    }
                }
            }

            return(new StateMachine(states, labels));
        }