31 Commits 66d0763194 ... 05523e1ede

Autor SHA1 Mensagem Data
  Reddo 05523e1ede Actually working descriptions. 5 anos atrás
  Reddo 4cae92da6c Don't mention the contents of carried Coin Pouches 5 anos atrás
  Reddo bf18a05518 A lot about combat, some text is not working and can't figure out why 5 anos atrás
  Reddo efbaa6a59f We getting combat now bois 5 anos atrás
  Reddo 21b8a86208 Save damage 5 anos atrás
  Reddo be79b814b2 Simplify 5 anos atrás
  Reddo 0299c940ba Simplify 5 anos atrás
  Reddo 17cb62e307 Add Follow Action 5 anos atrás
  Reddo 50904dbf8f ... 5 anos atrás
  Reddo b315cccea5 Hopefully final version of this list. 5 anos atrás
  Reddo 1b77131347 Support playing songs from youtube 5 anos atrás
  Reddo 0a0a2a6b01 A lot was done: 5 anos atrás
  Reddo 8934ac600b Tips that only show up 5 fives 5 anos atrás
  Reddo e41bf8f7d4 Transition animation for when animations show up 5 anos atrás
  Reddo c4e18b2ff7 I don't know why I was trying to make a shadow system, I mean it'd be cool and stuff but I'm far too sleep deprived to figure out the math because of how the scaling affects it and I no longer care abgout it 5 anos atrás
  Reddo 8f0367d815 Only do scaling once on animations, allowing for pixel-perfect precision in the odd case the space available fits exactly the size of the sprite (scale(1)). Might also do scaling in spaces later (like 0.5, 0.6, 0.7 etc, to make for less blurring). 5 anos atrás
  Reddo 7ab6f83544 Also allows sprites to be whatever size and scales each of them up appropriately. If we get a better artist we can slowly replace everything rather than do it all in one go. 5 anos atrás
  Reddo 65d5d20c35 Utilize scaling to make sprites fit on whatever space they have available. 5 anos atrás
  Reddo 026d09cab7 Fix ancient error that resulted in only half of old passages being marked as "old". 5 anos atrás
  Reddo 8732afb3d6 Move animations to bottom of the screen 5 anos atrás
  Reddo 5d04cb4c9d Fix awful bugs from awful update 5 anos atrás
  Reddo f5e72c3f15 Shoddy, terrible work to make textboxes show all the text all the time. 5 anos atrás
  Reddo 085d1f1dc6 Separate Animation code from Elements for tidyness 5 anos atrás
  Reddo 46f7ff03f0 Make animation thingy optional because there's no way in hell it's gestting done on the first version 5 anos atrás
  Reddo f055b17573 Exits need to shade differently again... 5 anos atrás
  Reddo bad1c1ee31 Make inventory display play nice with non-infinite space. 5 anos atrás
  Reddo 587ad67c9e Move animations on top of the Appearance tab and also make it so they aren't blurred by the turn effect since they'd be blurred during sex due to those happening in turn. 5 anos atrás
  Reddo 6daef2f7f7 Having a maximum FPS does not seem to increase processing time, probably because the animations themselves are low fps anyhow. 5 anos atrás
  Reddo 6848a3e08d Animation Support done... 5 anos atrás
  Reddo 70192f0119 How did that stay here for so long!? 5 anos atrás
  Reddo bea8113684 Modularize Stylesheet reader 5 anos atrás
54 arquivos alterados com 1802 adições e 681 exclusões
  1. 7 7
      The Obelisk.haml
  2. 31 0
      app/Controls/Controls.ts
  3. 116 0
      app/Elements/Classes/AnimationLayer.ts
  4. 1 14
      app/Elements/Classes/Say/SayImage.ts
  5. 20 0
      app/Elements/Classes/Say/SayProtip.ts
  6. 17 1
      app/Elements/Elements.ts
  7. 130 0
      app/Elements/Modules/AnimationHandler.ts
  8. 57 0
      app/Elements/Modules/BGMHandler.ts
  9. 2 2
      app/Elements/Modules/CurrentTurnHandler.ts
  10. 9 7
      app/Elements/Modules/HyperlinkHandler.ts
  11. 20 5
      app/World/Classes/AI.ts
  12. 21 0
      app/World/Classes/AI/AIGrudge.ts
  13. 17 0
      app/World/Classes/AI/AIHitBack.ts
  14. 57 0
      app/World/Classes/Action.ts
  15. 243 0
      app/World/Classes/Action/ActionAttack.ts
  16. 53 0
      app/World/Classes/Action/ActionDropDown.ts
  17. 62 0
      app/World/Classes/Action/ActionFollow.ts
  18. 54 0
      app/World/Classes/Action/ActionStand.ts
  19. 58 0
      app/World/Classes/ContentPicker/AdaptiveDifferential.ts
  20. 12 0
      app/World/Classes/ContentPicker/Combat/CombatDescription.ts
  21. 9 15
      app/World/Classes/ContentPicker/Combat/CombatMarker.ts
  22. 35 0
      app/World/Classes/ContentPicker/Combat/CombatPokeDescription.ts
  23. 46 0
      app/World/Classes/ContentPicker/Combat/CombatPokeUnit.ts
  24. 21 5
      app/World/Classes/ContentPicker/Combat/CombatUnit.ts
  25. 10 0
      app/World/Classes/ContentPicker/ContentDescription.ts
  26. 15 3
      app/World/Classes/ContentPicker/ContentDifferential.ts
  27. 15 0
      app/World/Classes/ContentPicker/ContentGroup.ts
  28. 29 5
      app/World/Classes/ContentPicker/Fucking/FuckingMarker.ts
  29. 32 0
      app/World/Classes/Thing.ts
  30. 9 0
      app/World/Classes/Things/Clothing.ts
  31. 1 0
      app/World/Classes/Things/CoinPouch.ts
  32. 28 5
      app/World/Classes/Things/Humanoid/Humanoid.ts
  33. 27 5
      app/World/Classes/Things/Person.ts
  34. 47 0
      app/World/Classes/Things/Weapon/Weapon.ts
  35. 12 10
      app/World/EveryTurn.ts
  36. 1 0
      app/World/TurnSequence.ts
  37. 1 0
      config.rb
  38. 77 12
      content/main.ts
  39. 0 0
      dist/The Obelisk.html
  40. 1 1
      sass/__imagesLinks.scss
  41. 22 1
      sass/_animations.scss
  42. 5 0
      sass/_animationsBG.scss
  43. 8 0
      sass/_modal.scss
  44. 7 20
      sass/_page.scss
  45. 0 6
      sass/screen.scss
  46. 51 22
      stylesheets/images.css
  47. 14 172
      stylesheets/screen.css
  48. 0 1
      stylesheets/screenInline.css
  49. 19 164
      stylesheets/screenLink.css
  50. 65 190
      stylesheets/screenRelative.css
  51. 24 0
      tools/FuckingDescription.html
  52. 17 5
      tools/dialogger/dialogger.js
  53. 16 2
      tools/dialogger/index.css
  54. 151 1
      tools/dialogger/lib/joint.min.css

+ 7 - 7
The Obelisk.haml

@@ -45,19 +45,21 @@
       -#    .noshrinkFlex.statusColumnDivider
       #gameContainer
         #windowContainer
+          #sceneAnimation
           #leftWindow
             .topBottomFlex
-              #appearanceTab.growingFlex.scrollbar
+              #appearanceTab.noshrinkFlex.scrollbar
                 %p.appearanceHeader="Appearance:"
                 #appearanceTarget
               .growingFlex
-              .noshrinkFlex.scrollbar
+              #inventoryTab.noshrinkFlex.scrollbar
                 -#inventoryTab.noshrinkFlex
                 #inventoryTarget
           #centerWindow
+            %audio#bgmPlayer{:controls=>"true", :loop=>"true", :style=>"position: absolute; display: none; z-index: 1;"}
             .topBottomFlex
               #currentTurn.growingFlex.scrollbar
-                .topBottomFlex
+                #turnContainer.topBottomFlex
                   #forceTurnToBottom.growingFlex
                   #currentTurnTab.noshrinkFlex
                   #forceTurnToTop.growingFlex
@@ -75,17 +77,15 @@
             .topBottomFlex
               #mapTab.growingFlex.scrollbar
                 #mapTarget
-              #sceneAnimation
-                %a.sceneAnimation#orcAnim
               #roomExitsHolder
                 %p.roomExitsHeader="Exits:"
                 #roomExits
     -###########
     -# Relative File for Debugging
-    -#%script{:type => "text/javascript", :charset=>"utf8", :src => "../js/Application.js"}
+    %script{:type => "text/javascript", :charset=>"utf8", :src => "../js/Application.js"}
     -###########
     -# Optimized No Debug Data URL
-    = "<script id=\"ApplicationJS\" type=\"text/javascript\", charset=\"utf8\", src=\"data:application/javascript," + (URI.encode("(function(){\n" + (File.read "js/Application.js").encode!('UTF-8', 'UTF-8', :invalid => :replace).gsub("console.debug(", "//")) + "\n})()") + "\"></script>";
+    -#= "<script id=\"ApplicationJS\" type=\"text/javascript\", charset=\"utf8\", src=\"data:application/javascript," + (URI.encode("(function(){\n" + (File.read "js/Application.js").encode!('UTF-8', 'UTF-8', :invalid => :replace).gsub("console.debug(", "//")) + "\n})()") + "\"></script>";
     -###########
     %style{:media=>"screen", :type=>"text/css"}
       = File.read "stylesheets/fonts.css"

+ 31 - 0
app/Controls/Controls.ts

@@ -1,3 +1,34 @@
 module Controls {
+    export function createBigButton (text : string, resolve : (t : string) => void) {
+        let p = document.createElement("p");
+        p.classList.add("choice");
+        p.appendChild(document.createTextNode(text));
 
+        p.addEventListener("click", () => {
+            resolve(text);
+        });
+
+        Controls.KeyHandler.applyCode(p, Controls.KeyHandler.getFirstKeyCode());
+
+        return p;
+    }
+
+    export async function giveChoices (big? : boolean, ...choices : Array<string>) {
+        let buttons;
+        let chosenPromise = <Promise<string>> new Promise((async (resolve) => {
+            Controls.KeyHandler.reset();
+            let say = new Say();
+
+            choices.forEach(choice => {
+                say.add(createBigButton(choice, resolve))
+            });
+
+            buttons = await say.getHTMLContent();
+            Elements.CurrentTurnHandler.print(...(buttons));
+        }));
+
+        let chosen = await chosenPromise;
+        Elements.CurrentTurnHandler.unprint(...buttons);
+        return [chosen, choices.indexOf(chosen)];
+    }
 }

+ 116 - 0
app/Elements/Classes/AnimationLayer.ts

@@ -0,0 +1,116 @@
+/// <reference path="../Elements.ts" />
+// parseInt(Elements.getStyle(".anims-orcNotStand").style.width.slice(0, -2))
+class AnimationLayer {
+    private name = "unknown";
+    private layer = 0;
+    private cStep = 0;
+    private totalSteps = 9;
+    private frameSize = 200;
+    private heightMult = 1; // used to create shadows
+    private feetHeight = 0; // used to move shadows up and down
+
+    private filters = ["hue-rotate(0deg)", "brightness(1)"];
+
+    private el = document.createElement("a");
+
+    constructor (filename : string, layer = 1) {
+        this.name = filename;
+        this.layer = layer;
+        this.el.classList.add("sceneAnimation");
+        this.el.style.zIndex = layer.toString();
+
+        let style = Elements.getStyle(".anims-" + filename);
+        if (style == undefined || style.width == undefined) {
+            console.error("Animation " + filename + " not found.");
+        } else {
+            let height = (parseInt(style.height.slice(0,-2)));
+            this.frameSize = height;
+            this.totalSteps = (parseInt(style.width.slice(0,-2)) / height);
+        }
+        this.el.classList.add("anims-" + filename);
+        this.updateSize();
+    }
+
+    public updateSize () {
+        this.el.style.transform = "scale(" + (Elements.AnimationHandler.spriteSize / this.frameSize) + "," + this.heightMult * (Elements.AnimationHandler.spriteSize / this.frameSize) + ")";
+        this.el.style.width = this.frameSize + "px";
+        this.el.style.height = this.frameSize + "px";
+        this.el.style.marginLeft = "-" + this.frameSize/2 + "px";
+        this.el.style.marginTop = "-" + ((this.frameSize / 2)) + "px";
+        if (this.heightMult != 1) {
+            //let realHeight = this.heightMult * (Elements.AnimationHandler.spriteSize / this.frameSize) * this.frameSize;
+            this.el.style.top = "100%";
+            this.el.style.marginTop = (- (this.feetHeight - (this.feetHeight * this.heightMult))) + "px";
+            //this.el.style.top = "100%";
+            //this.el.style.marginTop = "-" + (realHeight + this.feetHeight);
+            //console.log("finalHeight: ", realHeight, "feetHeight:", this.feetHeight, "target:" , 130, this.el.style.marginTop);
+            // Originally top: 50%;
+        }
+    }
+
+    public addShadow (feetHeight : number) {
+        let shadow = Elements.AnimationHandler.addAnimation(this.name, this.layer - 1);
+        shadow.heightMult = 0.15;
+        shadow.feetHeight = feetHeight;
+        shadow.updateSize();
+        shadow.brightify(0);
+    }
+
+    public updateSprite (percentage : number) {
+        this.cStep = Math.floor(percentage * this.totalSteps);
+        this.el.style.backgroundPositionX = "-" + (this.cStep * this.frameSize) + "px";
+    }
+
+    /**
+     * Turn refers to how much we turn the hue wheel, in degres.
+     * Anything that's colorizable begins at red, so:
+     * RED = 0
+     * YELLOW = 60
+     * GEREN = 120
+     * BLUE = 240
+     * RED = 360
+     *
+     * So pink is between RED and BLUE. 300 can be pink.
+     * @param degrees
+     */
+    public colorize (degrees : number) {
+        this.filters[0] = "hue-rotate(" + degrees + "deg)";
+        this.updateFilter();
+    }
+
+    public brightify (percent : number) {
+        this.filters[1] = "brightness(" + percent + ")";
+        this.updateFilter();
+    }
+
+    public updateFilter () {
+        this.el.style.filter = this.filters.join(" ");
+    }
+
+    public getElement () {
+        return this.el;
+    }
+}
+
+/**
+    Planning...
+ Precise positioning is all out, so let's forget about all that.
+ Due to animation limitations, the situations for fucking must be different:
+ - Speed according to if someone fucking is going to cum or not. Highest speed is used.
+ - Player has two stances only: Standing and On All Fours. Standing is used for combat grabs, generally, and the player is better able to fight back. On All Fours the player is weaker. Either way, doesn't matter here.
+ - The complete list of possible fucking positions for enemies:
+        * Holding Standing From the Front
+        * Holding Standing from the back
+        * Fucking Crotch standing from the front
+        * Fucking Crotch standing from the back
+        * Fucking Mouth standing
+        * Fucking Crotch On All Fours
+        * Fucking Mouth on All Fours
+ - Obviously, enemies only need to implement the things they do. Cockbats, for instance, will only fuck mouth if standing or Mouth/Crotch if on all fours.
+
+
+ - The player character is moving according to percentages and has a few states.
+        * At 25%, the character will slightly move forward as if compensating for fucking
+        * At 75%, the character will slightly move backward as if compensating for fucking
+ - These should be used as marks for thrusts.
+ **/

+ 1 - 14
app/Elements/Classes/Say/SayImage.ts

@@ -8,20 +8,7 @@ class SayImage implements PrintableElement {
     private imgName : string;
 
     private isImageDefined () {
-        try {
-            for(var i = 0; i < document.styleSheets.length; i++) {
-                var rules = document.styleSheets[i]['rules'] || document.styleSheets[i]['cssRules'];
-                for(var x in rules) {
-                    if(typeof rules[x].selectorText == 'string' && rules[x].selectorText == "." + this.imgName) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        } catch (e) {
-            console.warn("Can't check for undefined images.");
-            return true;
-        }
+        return Elements.getStyle("." + this.imgName) != undefined;
     }
 
     public constructor (imgName : string) {

+ 20 - 0
app/Elements/Classes/Say/SayProtip.ts

@@ -0,0 +1,20 @@
+/// <reference path="../Say.ts" />
+class SayProtip extends Say {
+    private static tipCounter = {};
+
+    public async getPureElements () : Promise<Array<Element | Text>> {
+        let paragraphs = await this.getParagraphs();
+        let elements = paragraphs.length == 1 ? paragraphs[0] : Array.prototype.concat.apply([], paragraphs);
+        let span = document.createElement("span");
+        span.classList.add("protip");
+        elements.forEach((element) => {
+            span.appendChild(element);
+        });
+        SayProtip.tipCounter[span.innerText] = SayProtip.tipCounter[span.innerText] == undefined ? 1 : SayProtip.tipCounter[span.innerText] + 1;
+        if (SayProtip.tipCounter[span.innerText] <= 5) {
+            return [span];
+        } else {
+            return [];
+        }
+    }
+}

+ 17 - 1
app/Elements/Elements.ts

@@ -112,9 +112,25 @@ module Elements {
     }
 
     export function printObeliskLogo () {
-        let say = new Say(Say.CENTERED, new SayImage("introLogo"), Say.LINE_BREAK, new SayItalic("The Obelisk is an adult interactive fiction game set in a post-apocalyptic world ravaged by a magical structure."));
+        let say = new Say(Say.CENTERED, new SayImage("introLogo"), Say.LINE_BREAK, new SayItalic("The Obelisk is a pornographic interactive fiction game set in a post-apocalyptic world ravaged by a magical structure."));
         Elements.CurrentTurnHandler.printAsContent(say);
     }
+
+    export function getStyle (name : string) {
+        try {
+            for (var i = 0; i < document.styleSheets.length; i++) {
+                var rules = document.styleSheets[i]['rules'] || document.styleSheets[i]['cssRules'];
+                for (var x in rules) {
+                    if (typeof rules[x].selectorText == 'string' && rules[x].selectorText == name) {
+                        return rules[x].style;
+                    }
+                }
+            }
+        } catch (e) {
+            console.warn("Can't read CSS styles", e);
+            return {};
+        }
+    }
 }
 
 if (Elements.isMobile) {

+ 130 - 0
app/Elements/Modules/AnimationHandler.ts

@@ -0,0 +1,130 @@
+/// <reference path="../Elements.ts" />
+/// <reference path="../Classes/AnimationLayer.ts" />
+
+// Animations must have 200x200 frames, placed in a spritesheet with a single row
+// Use https://github.com/williammalone/HTML5-Photoshop-Sprite-Sheet-Creator
+// To make animations of varying Frames Per Second fit, we will have breakpoints in percentage that delimitate what is to be expected.
+// Example: at 25% animation length, that's the Back-Thrust moment, so all the animations should expect and behave as if that was happening right then.
+// 75% would be front-thrust, etc.
+// The idea is to have the base animation look "ok" without anything going on, but to also have things going on just fit mathmagically.
+// Support to animations that contain the WHOLE of the events should be possible as to make it so we can make special animations for special circumstances. e.g. only encounter with the ogre
+enum AnimationLayerLevel {
+    BACKGROUND = 1,
+    MIDDLE = 150,
+    FRONT = 300
+}
+
+interface AnimationInstruction {
+    name: string;
+    layer?: AnimationLayerLevel | number;
+}
+
+module Elements.AnimationHandler {
+    var container = document.getElementById("sceneAnimation");
+    var background = document.createElement("a");
+    var stop = true;
+    export var spriteSize = 200;
+    export var animations : Array<AnimationLayer> = [];
+    export var animationSpeed = 1500; // how long to complete in ms
+    export var lastTime = 0;
+    export var percentage;
+
+    export function clearAnimations () {
+        while (container.firstChild != undefined) {
+            container.removeChild(container.firstChild);
+        }
+        animations = [];
+    }
+
+    export function addAnimations (...names : Array<AnimationInstruction>) {
+        fixVerticalPosition();
+        for (let i = 0; i < names.length; i++) {
+            addAnimation(names[i].name);
+        }
+    }
+
+    export function addAnimation (name : string, layer = 1) {
+        let newAnim = new AnimationLayer(name, layer);
+        container.appendChild(newAnim.getElement());
+        animations.push(newAnim);
+        return newAnim;
+    }
+
+    export function setBackground (name : string) {
+        background.className = "";
+
+        background.classList.add("bg-" + name);
+        background.classList.add("sceneAnimation");
+        background.style.zIndex = "0";
+
+        container.appendChild(background);
+    }
+
+    export function beginAnimate (targetAnimationSpeed? : number) {
+        animationSpeed = targetAnimationSpeed == undefined ? 1500 : targetAnimationSpeed;
+        lastTime = (new Date()).getTime();
+        percentage = 0;
+        container.style.display = "";
+
+        fixVerticalPosition();
+        stop = false;
+        animate();
+    }
+
+    export function animate () {
+        let time = (new Date()).getTime();
+        let percGain = (time - lastTime) / animationSpeed;
+        percentage += percGain;
+        while (percentage > 1) {
+            percentage -= 1;
+        }
+        for (let i = 0; i < animations.length; i++) {
+            animations[i].updateSprite(percentage);
+        }
+        lastTime = time;
+        if (!stop) {
+            window.requestAnimationFrame(Elements.AnimationHandler.animate);
+        }
+    }
+
+    export function fixVerticalPosition () {
+        spriteSize = getSize(true);
+        //container.style.marginBottom = (availHeight - 200) + "px";
+        container.style.width = spriteSize + "px";
+        container.style.marginLeft = "-" + (spriteSize / 2) + "px";
+        container.style.height = spriteSize + "px";
+
+        animations.forEach((anim) => {
+            anim.updateSize();
+        });
+
+        if (background.parentElement == container) {
+            background.style.transform = "";
+            let height = background.offsetHeight;
+            let width = background.offsetWidth;
+            if (height != 0 && width != 0) {
+                background.style.transform = "scale(" + (spriteSize / height) + ")";
+                background.style.marginLeft = (-width / 2) + "px";
+                background.style.marginTop = (-height / 2) + "px";
+            }
+        }
+    }
+
+    export function getSize (fixMin = true) {
+        let tab = document.getElementById("currentTurn");
+        //let tab1 = document.getElementById("roomName");
+        //tab1.style.marginTop = "";
+        let height = window.innerHeight - tab.clientHeight;
+        return height;
+    }
+
+    export function stopAnimate () {
+        stop = true;
+        container.style.display = "none";
+        clearAnimations();
+        getSize(false);
+    }
+    stopAnimate();
+}
+
+window.addEventListener("resize", () => { Elements.AnimationHandler.fixVerticalPosition(); });

+ 57 - 0
app/Elements/Modules/BGMHandler.ts

@@ -0,0 +1,57 @@
+/// <reference path="../Elements.ts" />
+module Elements.BGMHandler {
+    var player = <HTMLAudioElement> document.getElementById("bgmPlayer");
+    player.volume = 0.1;
+    player.addEventListener("canplay", function () {
+        this.play();
+        this.style.display = "";
+    });
+    player.addEventListener("error", function () {
+        this.style.display = "none";
+        console.warn("Could not play the BGM ", this.src);
+    });
+    player.addEventListener("pause", function () {
+        this.style.display = "none";
+    });
+
+    function getJsonFromUrl(url) : any {
+        if(!url) url = location.search;
+        var query = url.substr(1);
+        var result = {};
+        query.split("&").forEach(function(part) {
+            var item = part.split("=");
+            result[item[0]] = decodeURIComponent(item[1]);
+        });
+        return result;
+    }
+
+    export function playYT (vidID : string) {
+        let proxyURL = "https://images" + (Math.floor(Math.random() * 33)) + "-focus-opensocial.googleusercontent.com/gadgets/proxy?container=none&url=";
+        let vidInfo = encodeURIComponent("https://www.youtube.com/get_video_info?video_id=") + encodeURIComponent(vidID);
+        fetch(proxyURL + vidInfo).then(response => {
+            if (response.ok) {
+                response.text().then((data : any) => {
+                    data = getJsonFromUrl(data);
+                    let streams = (data.url_encoded_fmt_stream_map + ',' + data.adaptive_fmts).split(',');
+
+                    let valid = [];
+
+                    for (let i = 0; i < streams.length; i++) {
+                        let stream = getJsonFromUrl(streams[i]);
+                        if (stream.type && stream.type.indexOf("audio") == 0) {
+                            valid.push(stream.url);
+                        }
+                    }
+
+                    // Last one has highest quality, if none found results "undefined" which just doesn't play
+                    player.src = valid.pop();
+                });
+            }
+        });
+    }
+
+    /** TODO: Allow players to create a playlist of youtube URLs that vary with certain situations, then play one of those randomly every time situation arises
+              e.g.: A list of songs for Combat, Exploration in the Forest, Talking with the Witch, etc.
+
+     */
+}

+ 2 - 2
app/Elements/Modules/CurrentTurnHandler.ts

@@ -13,8 +13,8 @@ module Elements.CurrentTurnHandler {
      */
     export function startTurn (action? : Action) {
 
-        let oldContent = currentTurnTab.getElementsByClassName("content");
-        for (let i = 0; i < oldContent.length; i++) {
+        let oldContent = new Array(...currentTurnTab.getElementsByClassName("content"));
+        for (let i = oldContent.length - 1; i >= 0; i--) {
             oldContent[i].classList.add("contentOld");
             oldContent[i].classList.remove("content");
         }

+ 9 - 7
app/Elements/Modules/HyperlinkHandler.ts

@@ -46,16 +46,18 @@ module Elements.HyperlinkHandler {
 
     export async function hyperlinkObject (thing? : any) {
         resetAvailableActions();
-        if (thing instanceof Thing && thing != WorldState.player && thing.isVisibleTo(WorldState.player)) {
+        if (thing instanceof Thing && thing != WorldState.player) {
             await HyperlinkingRulebook.execute({noun: thing});
 
-            currentActionTarget.nodeValue = thing.getPrintedName() + ": ";
+            if (availableActions.length > 0) {
+                currentActionTarget.nodeValue = thing.getPrintedName() + ": ";
 
-            for (let i = 0, value = availableActions[i]; value != undefined; value = availableActions[++i]) {
-                let link = createLink(value);
-                link.classList.add("columnLink");
-                Controls.KeyHandler.applyCode(link, Controls.KeyHandler.getSecondKeyCode());
-                linkedActionsTab.appendChild(link);
+                for (let i = 0, value = availableActions[i]; value != undefined; value = availableActions[++i]) {
+                    let link = createLink(value);
+                    link.classList.add("columnLink");
+                    Controls.KeyHandler.applyCode(link, Controls.KeyHandler.getSecondKeyCode());
+                    linkedActionsTab.appendChild(link);
+                }
             }
         }
     }

+ 20 - 5
app/World/Classes/AI.ts

@@ -4,16 +4,21 @@
 /// <reference path="../../Functions.ts" />
 
 interface AIOptions {
+    actor : Thing,
     wanderer? : boolean,
     wandersOn? : Region,
     picksShinies? : boolean
 }
 
 class AI {
+    public actor : Thing;
     public wanderer = true;
     public wandersOn : Region;
     public wanderChance = 50;
     public picksShinies = true;
+    public hostileTo : Array<Thing> = [];
+    public anger = 0;
+    public grudgeRate = 10;
 
     public constructor (options : AIOptions) {
         for (let key in options) {
@@ -25,16 +30,26 @@ class AI {
      * Executing an AI returns an Action. DOESN'T execute the action, just finds it!
      * @returns {Promise<Action>}
      */
-    public async execute (actor : Thing) : Promise<Action> {
+    public async execute () : Promise<Action> {
         let promise : Promise<Action>;
-        // TODO: if actor.isInCombat();
-        if (promise != undefined) {
+        let inCombat = false;
+
+        if (this.hostileTo.length > 0) {
+            for (let i = this.hostileTo.length - 1; i >= 0; i--) {
+                if (this.actor.getRoom() == this.hostileTo[i].getRoom()) {
+                    inCombat = true;
+                    break;
+                }
+            }
+        }
+
+        if (inCombat) {
             promise = AI.combatRules.execute({
-                noun : actor
+                noun : this.actor
             }, ...this.extraCombatRules);
         } else {
             promise = AI.rules.execute({
-                noun : actor
+                noun : this.actor
             }, ...this.extraRules);
         }
 

+ 21 - 0
app/World/Classes/AI/AIGrudge.ts

@@ -0,0 +1,21 @@
+/// <reference path="../AI.ts" />
+module AIRules {
+    export var Grudge = AI.rules.createAndAddRule({
+        name : "Grudge",
+        firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
+        conditions : (runner : RulebookRunner<Person>) => {
+            let person = runner.noun;
+            return person.AI.hostileTo.length > 0 && (Math.random() * 100) <= person.AI.grudgeRate;
+        },
+        code : (runner : RulebookRunner<Person>) => {
+            let person = runner.noun;
+            let hostileTo = person.AI.hostileTo;
+
+            for (let i = 0; i < hostileTo.length; i++) {
+                if (ActionFollow.isCloseEnough(person, hostileTo[i])) {
+                    return new ActionFollow(person, hostileTo[i]);
+                }
+            }
+        }
+    });
+}

+ 17 - 0
app/World/Classes/AI/AIHitBack.ts

@@ -0,0 +1,17 @@
+/// <reference path="../AI.ts" />
+module AIRules {
+    export var attackInRange = AI.combatRules.createAndAddRule({
+        name : "Attack in range",
+        firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
+        code : (runner : RulebookRunner<Person>) => {
+            let person = runner.noun;
+            let hostileTo = person.AI.hostileTo;
+
+            for (let i = 0; i < hostileTo.length; i++) {
+                if (hostileTo[i].isVisibleTo(person)) {
+                    return new ActionAttack(person, hostileTo[i]);
+                }
+            }
+        }
+    });
+}

+ 57 - 0
app/World/Classes/Action.ts

@@ -1,5 +1,6 @@
 /// <reference path="Rulebook.ts" />
 /// <reference path="Rule.ts" />
+/// <reference path="Things/Person.ts" />
 /// <reference path="../../Elements/Classes/Say.ts" />
 class Action {
     public static check = new Rulebook<Action>("Check any Action");
@@ -13,10 +14,12 @@ class Action {
     public say : Say = new Say();
 
     public actingAgressively = false;
+    public aggressivenessRating = 1;
     public actingSubmissively = false;
     public requiresTurn = true;
     public requiresNoun = true;
     public requiresVisibility = true; // First noun must be visible and in the same room
+    public allowedStances = [PersonStance.ALLFOURS, PersonStance.STANDING];
 
 
     public constructor (actor : Thing, ...nouns : Array<any>) {
@@ -55,9 +58,12 @@ class Action {
             return;
         } else if(result instanceof Action) {
             console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
+            let originalNouns = this.nouns;
             await result.execute();
             this.say.add(result.say);
             this.nouns = result.nouns;
+            // Reset to initial state
+            this.nouns = originalNouns;
             return;
         }
 
@@ -139,6 +145,26 @@ Action.check.addRule(
     })
 );
 
+Action.check.addRule(
+    new Rule({
+        name : "Check any Action - Stance",
+        firstPriority : Rule.PRIORITY_HIGHEST,
+        code : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            let actor = action.actor;
+            if (actor instanceof Person) {
+                if (action.allowedStances.indexOf(actor.stance) == -1) {
+                    if (action.actor == WorldState.player) {
+                        action.say = new Say("You can't do that while ", PersonStanceNames[actor.stance], ", you need to be ", PersonStanceNames[actor.stance], ".");
+                    }
+                    return false;
+                    action.stop();
+                }
+            }
+        }
+    })
+);
+
 Action.check.addRule(
     new Rule({
         name : "Check any Action - Requires Visibility",
@@ -153,4 +179,35 @@ Action.check.addRule(
             return runner.noun.requiresVisibility;
         }
     })
+);
+
+// TODO: Pass everything on here directly to the AI so that IT can handle this.
+Action.carry.addRule(
+    new Rule({
+        name : "Check any Action - Angery",
+        firstPriority : Rule.PRIORITY_LOWEST,
+        priority: Rule.PRIORITY_LOWEST,
+        code : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            let target = action.getNoun(0);
+            let tai = (<Person> target).AI;
+            tai.anger += action.aggressivenessRating * tai.grudgeRate;
+            let cu = new CombatPokeUnit().setTarget(target);
+            if (tai.anger < 10) {
+                cu.addMarker(CombatPokeResult.NOHEAT);
+            } else if (tai.anger < 100) {
+                cu.addMarker(CombatPokeResult.ANNOYED);
+            } else {
+                cu.addMarker(CombatPokeResult.AGGROED);
+                (<Person>target).AI.hostileTo.push(action.actor);
+            }
+
+
+            action.say.add(Say.PARAGRAPH_BREAK, ...CombatPokeDescription.getDescription(new ContentGroup(cu)));
+        },
+        conditions : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            return action.actor == WorldState.player && action.actingAgressively && action.getNoun(0) instanceof Person && !action.getNoun(0).isHostileTo(action.actor) && action.getNoun(0).getHealthOnScale() > 0;
+        }
+    })
 );

+ 243 - 0
app/World/Classes/Action/ActionAttack.ts

@@ -0,0 +1,243 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+/// <reference path="../Things/Weapon/Weapon.ts" />
+enum ActionAttackReaction {
+    DODGE, RESIST, COUNTERATTACK
+}
+
+class ActionAttack extends Action {
+    public static check = new Rulebook<ActionAttack>("Check Attack");
+    public static carry = new Rulebook<ActionAttack>("Carry out Attack");
+    public actingAgressively = true;
+    public aggressivenessRating = 1;
+    private targetReaction : ActionAttackReaction;
+    public allowedStances = [PersonStance.STANDING];
+
+    public static randomness = 2;
+    public static dodgeCost = 1;
+    public static fistCost = 1;
+
+    public static gettingAttacked = new OneOf(OneOf.PURELY_AT_RANDOM,
+        "moves on to attack you!",
+        "attacks you!",
+        "tries to attack you!",
+        "decides to attack you!",
+        "initiates an attack!",
+        "is attacking!",
+        "approaches with malicious intent!",
+        "aims at you with malicious intent!",
+        "aims at you!",
+        "goes for you!",
+        "comes for you!",
+        "seems hostile!",
+        "seems to want to fight!",
+        "wants to fight!",
+        "is coming right for you!",
+        "is coming right at you!",
+        "is not friendly at all!"
+        );
+    // TODO: Add infinitely more of these...
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = true;
+        this.requiresVisibility = false;
+    }
+
+    public getTarget () : Thing {
+        return this.getNoun(0);
+    }
+
+    public getWeapons () : Array<Weapon> {
+        let weaponNoun = this.getNoun(1);
+
+        if (weaponNoun == undefined) {
+            let weapons = <Array<Weapon>> this.actor.getWorns(Weapon);
+            return weapons;
+        }
+
+        return [weaponNoun];
+    }
+
+    public getCommandText () {
+        let weaponName;
+        let weapons = this.getWeapons();
+        if (weapons.length == 0) {
+            weaponName = "fists";
+        } else {
+            weaponName = [];
+            weapons.forEach((weapon => {
+                weaponName.push(weapon.getPrintedName());
+            }));
+            weaponName = weaponName.join(" and ");
+        }
+        return "attack " + this.getNoun(0).getPrintedName() + " with " + weaponName;
+    }
+
+    public generateDescription (group : ContentGroup) {
+        this.say.add(...(CombatDescription.getDescription(group)));
+    }
+
+    public static checkAttackable = ActionAttack.check.createAndAddRule({
+        name : "Attack - Is it attackable?",
+        firstPriority : Rule.PRIORITY_HIGHEST,
+        code : async (runner : RulebookRunner<ActionAttack>) => {
+            let action = runner.noun;
+            let target = runner.noun.getTarget();
+            if (!(target instanceof Person) && !target.breakable) {
+                if (action.actor == WorldState.player) {
+                    action.say.add("You can't possibly damage that.");
+                }
+                action.stop();
+                return false;
+            }
+        }
+    });
+
+    public static checkAccuracyRule = ActionAttack.check.createAndAddRule({
+        name : "Attack - Is it a hit?",
+        code : async (runner : RulebookRunner<ActionAttack>) => {
+            let action = runner.noun;
+            let actorDex = (<Person> runner.noun.actor).getStat(Attributes.Agility);
+            let targetDex = 0;
+            let target = runner.noun.getTarget();
+            if (target instanceof Person) {
+                targetDex = target.getStat(Attributes.Agility);
+            }
+
+
+            let reaction = ActionAttackReaction.DODGE;
+            if (target == WorldState.player) {
+                Elements.CurrentTurnHandler.printAsContent(new Say(action.actor, " ", ActionAttack.gettingAttacked.getOne()));
+                let choices = ["Counter-Attack", "Dodge", "Resist"];
+                let choice = await Controls.giveChoices(false, ...choices);
+                if (choice[1] == 0) {
+                    reaction = ActionAttackReaction.COUNTERATTACK;
+                } else if (choice[1] == 2) {
+                    reaction = ActionAttackReaction.DODGE;
+                } else {
+                    reaction = ActionAttackReaction.RESIST;
+                }
+            }
+
+            let weapons = action.getWeapons();
+            for (let i = 0; i < weapons.length; i++) {
+                (<Person> action.actor).changeStamina(- weapons[i].attackCost);
+            }
+            if (weapons.length == 0) {
+                (<Person> action.actor).changeStamina(- ActionAttack.fistCost)
+            }
+
+            action.targetReaction = reaction;
+
+            if (reaction == ActionAttackReaction.DODGE) {
+                (<Person> target).changeStamina(- ActionAttack.dodgeCost);
+                let attack =  Math.floor(Math.random() * (ActionAttack.randomness + ActionAttack.randomness + 1)) - ActionAttack.randomness;
+                let defense = Math.floor(Math.random() * (ActionAttack.randomness + ActionAttack.randomness + 1)) - ActionAttack.randomness;
+                attack += actorDex;
+                defense += targetDex;
+                if (attack < defense) {
+                    action.generateDescription(
+                        (new ContentGroup())
+                            .addUnit(
+                                (new CombatUnit())
+                                    .setActor(action.actor)
+                                    .setTarget(target)
+                                    .setWeapon(...weapons)
+                                    .addMarker(CombatHit.MISS)
+                            )
+                    );
+                    return false;
+                }
+            }
+        }
+    });
+
+    public static carryOutAttack = ActionAttack.carry.createAndAddRule({
+        name: "Attack - Go for the throat!",
+        code: async (runner: RulebookRunner<ActionAttack>) => {
+            let action = runner.noun;
+            let target = runner.noun.getTarget();
+            let damage = 0;
+            let weapons = action.getWeapons();
+            for (let i = 0; i < weapons.length; i++) {
+                damage += weapons[i].getDamage();
+            }
+            if (weapons.length == 0) {
+                damage += (<Person> action.actor).getStat(Attributes.Strength);
+            }
+
+            if (!(target instanceof Person)) {
+                if (target.breakable && target.breakableOn <= damage) {
+                    target.break();
+                    action.generateDescription(
+                        (new ContentGroup())
+                            .addUnit(
+                                (new CombatUnit())
+                                    .setActor(action.actor)
+                                    .setTarget(target)
+                                    .setWeapon(...weapons)
+                                    .addMarker(CombatHit.HIT, CombatResult.KILLED)
+                            )
+                    );
+                }
+            } else {
+                let damageReduction = target.getStat(Attributes.Strength);
+                if (action.targetReaction == ActionAttackReaction.RESIST) {
+                    damageReduction = Math.floor(damageReduction * 1.5)
+                }
+                let finalDamage = damage - damageReduction;
+                if (finalDamage < 0) {
+                    finalDamage = 0;
+                }
+                action.aggressivenessRating = 1 + finalDamage;
+                let torso = <HumanoidTorso> (<Person> target).getPart(HumanoidTorso);
+                if (torso != undefined) {
+                    torso.changeSoreness(finalDamage);
+                }
+
+                let hitType = (finalDamage * torso.getWeightedSoreness()) > (target.getMaxHealth() / 3) ? CombatHit.CRITICAL : CombatHit.HIT;
+                let targetHealth = target.getHealthOnScale();
+                let knockedOff = hitType == CombatHit.CRITICAL;
+
+                if (knockedOff) {
+                    target.stance = PersonStance.ALLFOURS;
+                }
+
+                let result = targetHealth < -9 ? CombatResult.KILLED : targetHealth <= 0 ? CombatResult.KNOCKED_OFF :
+                                knockedOff ? CombatResult.KNOCKED : undefined;
+
+                if (targetHealth < -9) {
+                    target.die();
+                }
+
+
+                action.generateDescription(
+                    (new ContentGroup())
+                        .addUnit(
+                            (new CombatUnit())
+                                .setActor(action.actor)
+                                .setTarget(target)
+                                .setWeapon(...weapons)
+                                .addMarker(hitType, result)
+                        )
+                );
+            }
+        }
+    });
+}
+
+Elements.HyperlinkHandler.HyperlinkingRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Attack",
+        firstPriority : Rule.PRIORITY_HIGHEST,
+        code : (rulebook : RulebookRunner<Thing>) => {
+            let thing = <Thing> rulebook.noun;
+
+            if ((thing instanceof Person || thing.breakable) && thing.isVisibleTo(WorldState.player)) {
+                Elements.HyperlinkHandler.addAvailableAction("Attack", new ActionAttack(WorldState.player, thing));
+            }
+        }
+    }
+));

+ 53 - 0
app/World/Classes/Action/ActionDropDown.ts

@@ -0,0 +1,53 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+/// <reference path="../Things/Person.ts" />
+class ActionDropDown extends Action {
+    public static check = new Rulebook<ActionDropDown>("Check Stand");
+    public static carry = new Rulebook<ActionDropDown>("Carry out Stand");
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = false;
+        this.requiresVisibility = false;
+    }
+
+    public getCommandText () {
+        return "stand up"
+    }
+
+    public checkStand = ActionDropDown.check.createAndAddRule({
+        name : "Stand - Can get down?",
+        code : (rulebook) => {
+            // TODO: Check if being held up
+        }
+    });
+
+    public carryStand = ActionDropDown.carry.createAndAddRule({
+        name : "Stand - Rise up!",
+        code : (runner : RulebookRunner<ActionDropDown>) => {
+            let actor = runner.noun.actor;
+            if (actor instanceof Person) {
+                actor.stance = PersonStance.ALLFOURS;
+
+                if (actor == WorldState.player) {
+                    runner.noun.say.add("You get down on all fours.");
+                } else {
+                    runner.noun.say.add(runner.noun.actor, " gets down.");
+                }
+            }
+        }
+    });
+}
+
+Elements.HyperlinkHandler.CommonActionsRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Stand up!",
+        firstPriority : Rule.PRIORITY_MEDIUM,
+        code : (rulebook : RulebookRunner<void>) => {
+            if (WorldState.player.stance != PersonStance.ALLFOURS) {
+                Elements.HyperlinkHandler.addCommonAction("Get down", new ActionDropDown(WorldState.player));
+            }
+        }
+    }
+));

+ 62 - 0
app/World/Classes/Action/ActionFollow.ts

@@ -0,0 +1,62 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+class ActionFollow extends Action {
+    public static check = new Rulebook<ActionFollow>("Check Follow");
+    public static carry = new Rulebook<ActionFollow>("Carry out Follow");
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = true;
+        this.requiresVisibility = false;
+    }
+
+    public getCommandText () {
+        return "follow " + this.getNoun(0).getPrintedName();
+    }
+
+    public static isCloseEnough (actor : Person, stalked : Thing) {
+        let cRoom = <RoomRandom> actor.getRoom();
+        let tRoom = <RoomRandom> stalked.getRoom();
+        if (cRoom instanceof RoomRandom && tRoom instanceof RoomRandom) {
+            let distance = tRoom.getDistanceTo(cRoom);
+
+            if (distance == 1 || distance < (actor.getStat(Attributes.Intelligence)/2)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public checkFollowClose = ActionFollow.check.createAndAddRule({
+        name : "Follow - Is the stalked close enough?",
+        code : (rulebook) => {
+            let action = <ActionFollow> rulebook.noun;
+            let actor = <Person> action.actor;
+            let stalked = action.getNoun(0);
+            let cRoom = <RoomRandom> actor.getRoom();
+            let tRoom = <RoomRandom> stalked.getRoom();
+
+            if (ActionFollow.isCloseEnough(actor, stalked)) {
+                let direction = cRoom.getAStarBestDirectionTo(tRoom);
+                return new ActionGo(actor, direction);
+            } else {
+                action.stop();
+            }
+        }
+    })
+}
+
+Elements.HyperlinkHandler.HyperlinkingRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Follow",
+        firstPriority : Rule.PRIORITY_HIGHEST,
+        code : (rulebook : RulebookRunner<Thing>) => {
+            let thing = <Thing> rulebook.noun;
+
+            if (thing instanceof Person && thing.getRoom() != WorldState.player.getRoom() && ActionFollow.isCloseEnough(WorldState.player, thing)) {
+                Elements.HyperlinkHandler.addAvailableAction("Follow", new ActionFollow(WorldState.player, thing));
+            }
+        }
+    }
+));

+ 54 - 0
app/World/Classes/Action/ActionStand.ts

@@ -0,0 +1,54 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+/// <reference path="../Things/Person.ts" />
+class ActionStand extends Action {
+    public static check = new Rulebook<ActionStand>("Check Stand");
+    public static carry = new Rulebook<ActionStand>("Carry out Stand");
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = false;
+        this.requiresVisibility = false;
+    }
+
+    public getCommandText () {
+        return "stand up"
+    }
+
+    public checkStand = ActionStand.check.createAndAddRule({
+        name : "Stand - Can stand?",
+        code : (rulebook) => {
+            // TODO: Check if being held down
+            // TODO: Check if incapacitated
+        }
+    });
+
+    public carryStand = ActionStand.carry.createAndAddRule({
+        name : "Stand - Rise up!",
+        code : (runner : RulebookRunner<ActionStand>) => {
+            let actor = runner.noun.actor;
+            if (actor instanceof Person) {
+                actor.stance = PersonStance.STANDING;
+
+                if (actor == WorldState.player) {
+                    runner.noun.say.add("You get up.");
+                } else {
+                    runner.noun.say.add(runner.noun.actor, " rises up.");
+                }
+            }
+        }
+    });
+}
+
+Elements.HyperlinkHandler.CommonActionsRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Stand up!",
+        firstPriority : Rule.PRIORITY_MEDIUM,
+        code : (rulebook : RulebookRunner<void>) => {
+            if (WorldState.player.stance == PersonStance.ALLFOURS) {
+                Elements.HyperlinkHandler.addCommonAction("Get up", new ActionStand(WorldState.player));
+            }
+        }
+    }
+));

+ 58 - 0
app/World/Classes/ContentPicker/AdaptiveDifferential.ts

@@ -0,0 +1,58 @@
+class AdaptiveDifferential {
+    public compareFunction : (noun : any) => boolean = () => false;
+    public countsAs = 0;
+
+    constructor (replacer : (noun : any) => boolean) {
+        this.compareFunction = replacer;
+    }
+
+    public isMatch (noun : any) {
+        return this.compareFunction(noun);
+    }
+
+    public static ANYOF (...acceptableValues) {
+        return new AdaptiveDifferential((noun : any) => {
+            //console.log(acceptableValues, noun);
+            for (let i = 0; i < acceptableValues.length; i++) {
+                if (ContentDifferential.compareNouns(acceptableValues[i], noun)) {
+                    return true;
+                }
+            }
+            return false;
+        });
+    }
+
+    public static FULLYADAPTIVE (...acceptableValues) {
+        let ad = AdaptiveDifferential.ANYOF(...acceptableValues);
+        ad.countsAs = -1;
+        return ad;
+    }
+
+    public static MALE = new AdaptiveDifferential((noun : any) => {
+        if (noun instanceof Humanoid) {
+            return noun.isMale();
+        }
+        return false;
+    });
+
+    public static FEMALE = new AdaptiveDifferential((noun : any) => {
+        if (noun instanceof Humanoid) {
+            return noun.isMale();
+        }
+        return false;
+    });
+
+    public static MASCULINE = new AdaptiveDifferential((noun : any) => {
+        if (noun instanceof Humanoid) {
+            return noun.getGenderValue().genderValueCorrected <= 50;
+        }
+        return false;
+    });
+
+    public static FEMININE = new AdaptiveDifferential((noun : any) => {
+        if (noun instanceof Humanoid) {
+            return noun.getGenderValue().genderValueCorrected >= 50;
+        }
+        return false;
+    });
+}

+ 12 - 0
app/World/Classes/ContentPicker/Combat/CombatDescription.ts

@@ -1,4 +1,6 @@
 /// <reference path="../ContentDescription.ts" />
+/// <reference path="CombatPokeUnit.ts" />
+/// <reference path="CombatUnit.ts" />
 /**
  * Quick Cheat Sheet of markers!
  * When making a description take these markers into account while describing the action! If a marker describes something
@@ -28,6 +30,16 @@ class CombatDescription extends ContentDescription {
         CombatDescription.DESCRIPTIONS.push(this);
     }
 
+    public setDescriptionFunction (descriptionFor : (actor : any, target : any, weapons : Array<any>, markers : Array<any>) => Say) {
+        let finalFunction = (description : CombatDescription, group : ContentGroup) => {
+            // Combat only has one unit
+            let unit = <CombatUnit> group.getUnit(0);
+            return descriptionFor (unit.getActor().nouns[0], unit.getTarget().nouns[0], unit.getWeapons(), unit.getMarkers());
+        }
+        this.description = finalFunction;
+        return this;
+    }
+
     public addUnit () {
         let unit = new CombatUnit();
         (<ContentGroup> this.group).addUnit(unit);

+ 9 - 15
app/World/Classes/ContentPicker/Combat/CombatMarker.ts

@@ -4,21 +4,9 @@
  * If making a new description, you should clone it for each CombatResult variant.
  */
 class CombatHit extends ContentMarker {
-    public static FULL_DODGE = new CombatHit("Full Dodge", true);
-    public static PARTIAL_DODGE = new CombatHit("Partial Dodge", true);
-    public static FULL_HIT = new CombatHit("Full Hit", true);
-}
-
-/**
- * These are markers that explain how much damage was caused by the attack.
- * Low/High is relative to a percentage of overall HP. Low < 25%, Medium < 50%, High >= 50% of max health.
- * These are non-mandatory, but if describing the amount of damage caused one of them should be included.
- * Note: while a description without any of these will still work, a description with more than one will never be picked.
- */
-class CombatDamage extends ContentMarker {
-    public static LOW_DAMAGE = new CombatDamage("Low Damage");
-    public static MEDIUM_DAMAGE = new CombatDamage("Medium Damage");
-    public static HIGH_DAMAGE = new CombatDamage("High Damage");
+    public static MISS = new CombatHit("Miss", true);
+    public static HIT = new CombatHit("Hit", true);
+    public static CRITICAL = new CombatHit("Strong Hit", true);
 }
 
 /**
@@ -30,4 +18,10 @@ class CombatResult extends ContentMarker {
     public static KNOCKED = new CombatResult("Target was knocked down by the attack", true);
     public static KNOCKED_OFF = new CombatResult("Target was knocked off by the attack, becoming unconscious", true);
     public static KILLED = new CombatResult("Target was killed by this attack", true);
+}
+
+class CombatPokeResult extends ContentMarker {
+    public static AGGROED = new CombatResult("Target is now hostile", true);
+    public static ANNOYED = new CombatResult("Target is almost hostile", true);
+    public static NOHEAT = new CombatResult("Target didn't mind", true);
 }

+ 35 - 0
app/World/Classes/ContentPicker/Combat/CombatPokeDescription.ts

@@ -0,0 +1,35 @@
+/// <reference path="../ContentDescription.ts" />
+/// <reference path="CombatPokeUnit.ts" />
+/// <reference path="CombatUnit.ts" />
+/**
+ * Quick Cheat Sheet of markers!
+
+ */
+class CombatPokeDescription extends ContentDescription {
+    public static DESCRIPTIONS = [];
+
+    public constructor (name : string) {
+        super(name, new ContentGroup());
+        CombatPokeDescription.DESCRIPTIONS.push(this);
+    }
+
+    public setDescriptionFunction (descriptionFor : (target : any, markers : Array<any>) => Say) {
+        let finalFunction = (description : CombatPokeDescription, group : ContentGroup) => {
+            // Combat only has one unit
+            let unit = <CombatPokeUnit> group.getUnit(0);
+            return descriptionFor (unit.getTarget().nouns[0], unit.getMarkers());
+        }
+        this.description = finalFunction;
+        return this;
+    }
+
+    public addUnit () {
+        let unit = new CombatPokeUnit();
+        (<ContentGroup> this.group).addUnit(unit);
+        return unit;
+    }
+
+    public static getDescription (target : ContentGroup) {
+        return ContentDescription.pickDescriptions(CombatPokeDescription.DESCRIPTIONS, target);
+    }
+}

+ 46 - 0
app/World/Classes/ContentPicker/Combat/CombatPokeUnit.ts

@@ -0,0 +1,46 @@
+/// <reference path="../ContentUnit.ts" />
+/// <reference path="../../Things/Person.ts" />
+/// <reference path="../../Things/Bodypart/SexHole.ts" />
+/// <reference path="../../Things/Bodypart/SexStick.ts" />
+class CombatPokeUnit extends ContentUnit {
+    private target : ContentDifferential = new ContentDifferential(Person);
+    private markers : ContentDifferential = new ContentDifferential();
+
+    public constructor () {
+        super();
+    }
+
+
+    public setTarget (it : Thing | typeof Thing) {
+        this.target = new ContentDifferential(it);
+        return this;
+    }
+
+    public getTarget () {
+        return this.target;
+    }
+
+
+    public addMarker (...marker : Array<ContentMarker | AdaptiveDifferential>) {
+        this.markers.addNoun(...marker);
+        return this;
+    }
+
+    public getMarkers () {
+        return [...this.markers.nouns];
+    }
+
+    public getScore () {
+        return this.target.getScore() + this.markers.getScore();
+    }
+
+    public isMatch (cu : CombatPokeUnit) {
+        console.warn("Chegking :)");
+        if (cu instanceof CombatPokeUnit) {
+            console.log(this.markers.nouns, cu.markers.nouns);
+            return this.target.isMatch(cu.target) &&
+                this.markers.isMatch(cu.markers);
+        }
+        return false;
+    }
+}

+ 21 - 5
app/World/Classes/ContentPicker/Combat/CombatUnit.ts

@@ -5,7 +5,7 @@
 class CombatUnit extends ContentUnit {
 	private actor : ContentDifferential = new ContentDifferential(Person);
 	private target : ContentDifferential = new ContentDifferential(Person);
-	private weapon : ContentDifferential = new ContentDifferential(Thing);
+	private weapon : ContentDifferential = new ContentDifferential();
 	private markers : ContentDifferential = new ContentDifferential();
 
 	public constructor () {
@@ -17,21 +17,37 @@ class CombatUnit extends ContentUnit {
 		return this;
 	}
 
+	public getActor () {
+		return this.actor;
+	}
+
 	public setTarget (it : Thing | typeof Thing) {
 		this.target = new ContentDifferential(it);
 		return this;
 	}
 
-	public setWeapon (it : Thing | typeof Thing) {
-		this.weapon = new ContentDifferential(it);
+	public getTarget () {
+		return this.target;
+	}
+
+	public setWeapon (...it : Array<Thing | typeof Thing>) {
+		this.weapon = new ContentDifferential(...it);
 		return this;
 	}
 
-	public addMarker (marker : ContentMarker) {
-		this.markers.addNoun(marker);
+	public getWeapons () {
+		return [...this.weapon.nouns];
+	}
+
+	public addMarker (...marker : Array<ContentMarker | AdaptiveDifferential>) {
+		this.markers.addNoun(...marker);
 		return this;
 	}
 
+	public getMarkers () {
+		return [...this.markers.nouns];
+	}
+
 	public getScore () {
 		return this.actor.getScore() + this.target.getScore() + this.weapon.getScore() + this.markers.getScore();
 	}

+ 10 - 0
app/World/Classes/ContentPicker/ContentDescription.ts

@@ -9,6 +9,16 @@ class ContentDescription {
         this.group = group;
     }
 
+    public addMarker (marker : any) {
+        if (this.group instanceof ContentGroup) {
+            let markerUnit = new ContentUnit();
+            markerUnit.addCategory(new ContentDifferential(marker))
+            this.group.addUnit(markerUnit);
+        } else if (this.group instanceof ContentUnit) {
+            this.group.addCategory(marker);
+        }
+    }
+
     public getScore () {
         return (this.group).getScore();
     }

+ 15 - 3
app/World/Classes/ContentPicker/ContentDifferential.ts

@@ -13,7 +13,9 @@ class ContentDifferential {
 
     public addNoun (...nouns : Array<any>) {
         nouns.forEach(noun => {
-            this.nouns.push(noun);
+            if (noun != undefined) {
+                this.nouns.push(noun);
+            }
         });
         this.score = this.getScore();
         return this;
@@ -30,8 +32,15 @@ class ContentDifferential {
 
     public isMatch (cd : ContentDifferential, allowPartial = false) {
         let check = this.getUnmatched(cd);
-        if ((allowPartial || check.unmatched.length == 0) && check.matching.length == 0) {
+        if ((allowPartial || (check.unmatched.length) <= 0) && check.matching.length == 0) {
             return true;
+        } else if ((allowPartial || (check.unmatched.length) <= 0) && check.matching.length > 0) {
+            for (let i = 0; i < check.matching.length; i++) {
+                if (!(check.matching[i] instanceof AdaptiveDifferential && (<AdaptiveDifferential> <unknown> check.matching[i]).countsAs == -1)) {
+                    return false;
+                }
+                return true;
+            }
         } else if (check.matching.length == 0) {
             for (let i = 0; i < check.unmatched.length; i++) {
                 if (!(check.unmatched[i] instanceof ContentMarker)) {
@@ -107,7 +116,10 @@ class ContentDifferential {
         if (a == undefined || a == null) {
             return true;
         }
-        if (typeof a == "function") {
+        if (a instanceof AdaptiveDifferential) {
+            //console.log(a, b, a.isMatch(b));
+            return a.isMatch(b);
+        } else if (typeof a == "function") {
             // b must inherit a or be a
             return b == a || b instanceof a || (typeof b == "function" && (<any>b).prototype instanceof a)
         } else if (a instanceof Thing) {

+ 15 - 0
app/World/Classes/ContentPicker/ContentGroup.ts

@@ -24,6 +24,13 @@ class ContentGroup {
         return this;
     }
 
+    public addDifferentials (...differentials : Array<any>) {
+        let unit = new ContentUnit();
+        unit.addCategory(...differentials);
+        this.addUnit(unit);
+        return this;
+    }
+
     public reset () {
         this.matching = this.units.slice();
     }
@@ -82,4 +89,12 @@ class ContentGroup {
         }
         return undefined;
     }
+
+    public getLength () {
+        return this.units.length;
+    }
+
+    public getUnit (n : number) {
+        return this.units[n];
+    }
 }

+ 29 - 5
app/World/Classes/ContentPicker/Fucking/FuckingMarker.ts

@@ -2,32 +2,56 @@
 /**
  * CHEAT SHEET FOR FUCKING MARKERS
  *
- * Mandatory if present - These may all appear at once or separatedly! Multiples is always rarer, so you can get away with less descriptions
+ * Mandatory if present - These may all appear at once or separatedly! Separated is always rarer, so you can get away with less descriptions (Most enemies will start and finish in a single turn)
  * FuckingState.CUM_START
  * FuckingState.CUMMING
  * FuckingState.CUM_END
  *
- * Mandatory if present - these always accompany the ones above, they never appear without at least one of the ones above. They might be both at once, but very few enemies do that so always check the enemy first
+ * Mandatory if present - these always accompany the ones above, they never appear without at least one of the ones above. They might be both at once, but very few enemies do that so always check the enemy first (both at once = messy)
  * FuckingState.CUM_INSIDE
  * FuckingState.CUM_OUTSIDE
  *
- * Mandatory if present - These may all appear at once or separatedly! Multiples is always rarer, so you can get away with less descriptions
+ * Mandatory if present - These may all appear at once or separatedly! Multiples is always rarer, so you can get away with less descriptions. (Only a special enemy would both insert and finish and remove in the same turn)
  * FuckingState.PENETRATING
  * FuckingState.REMOVING
+ *
+ * Mandatory if present - Only one of these is ever active
+ * FuckeePosition.STANDING
+ * FuckeePosition.ALLFOURS
+ *
+ * Optional - Only one of these is ever active
+ * FuckingStyle.GENTLE
+ * FuckingStyle.ROUGH
+ *
+ *
+ * TO MATCH ANIMATIONS:
+ * - Anything fucking the crotch does so from BEHIND. Only ignore this if you know for a fact that there are special animations, and then all the other descriptions must follow.
+ *
+ * Examples:
+ * {
+ *     Fucking Unit: Orc is Fucker, Player is Fuckee, Orc Penis is Stick, Player Vagina is Hole, Markers: FuckingStyle.GENTLE - FuckeePosition.STANDING - FuckerPosition.STANDINGFRONT
+ * }
  */
 
 class FuckingStyle extends ContentMarker {
-    public static GENTLE = new FuckingStyle("Gentle");
-    public static ROUGH = new FuckingStyle("Rough");
+    public static GENTLE = new FuckingStyle("Gentle", false);
+    public static ROUGH = new FuckingStyle("Rough", false);
 }
 
 class FuckingState extends ContentMarker {
     public static PENETRATING = new FuckingState("Penetration Start", true);
     public static REMOVING = new FuckingState("Removing Stick", true);
     public static SPEEDING_UP = new FuckingState("Speeding up",true);
+
     public static CUM_START = new FuckingState("Started Cumming", true);
     public static CUMMING = new FuckingState("Cumming", true);
     public static CUM_END = new FuckingState("Finished Cumming", true);
+
     public static CUM_INSIDE = new FuckingState("Cum Inside", true);
     public static CUM_OUTSIDE = new FuckingState("Cum Outside", true);
+}
+
+class FuckeePosition extends ContentMarker {
+    public static STANDING = new FuckingStyle("Standing");
+    public static ALLFOURS = new FuckingStyle("On All Fours");
 }

+ 32 - 0
app/World/Classes/Thing.ts

@@ -9,6 +9,9 @@ interface ThingOptions {
     unique? : boolean;
     image? : SayImage | string;
     shiny? : boolean;
+    breakable? : boolean;
+    resistance? : number;
+    breaksTo? : string;
 }
 
 // A thing is something that exists in the World
@@ -23,6 +26,9 @@ class Thing implements Printable {
     public unique : boolean = false;
     public image : SayImage;
     protected shiny : boolean = false;
+    public breakable = false;
+    public breakableOn = 5;
+    public breaksTo;
 
     protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
     protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = [];
@@ -50,6 +56,10 @@ class Thing implements Printable {
     public constructor (options? : ThingOptions) {
         options = options == undefined ? {} : options;
 
+        this.breakable = options.breakable === true;
+        this.breakableOn = options.resistance == undefined ? 5 : options.resistance;
+        this.breaksTo = options.breaksTo;
+
         if (options.properName != undefined) {
             this.name = options.properName;
             this.properlyNamed = true;
@@ -284,6 +294,13 @@ class Thing implements Printable {
         }
     }
 
+    public getWorns (wornType? : any) {
+        if (wornType != undefined) {
+            return Thing.WearRelation.getRightType(this, wornType);
+        }
+        return Thing.WearRelation.getRight(this);
+    }
+
     public getParts (partType? : any) {
         if (partType != undefined) {
             return Thing.PartRelation.getRightType(this, partType);
@@ -376,6 +393,21 @@ class Thing implements Printable {
         }
     }
 
+    public break () {
+        if (this.breaksTo != undefined) {
+            let unique = Thing.getUnique(this.breaksTo);
+            if (unique != undefined) {
+                this.getRoom().place(unique);
+            } else {
+                let nonunique = Thing.getOneThing(this.breaksTo);
+                if (nonunique != undefined) {
+                    this.getRoom().place(nonunique.clone())
+                }
+            }
+        }
+        this.destroy();
+    }
+
     /**
      * So long as a thing is in a relation, it can't be garbage cleaned.
      * If you're throwing a thing away, do run destroy.

+ 9 - 0
app/World/Classes/Things/Clothing.ts

@@ -3,6 +3,8 @@ interface ClothingOptions extends ThingOptions {
     topDescription? : Say;
     bottomDescription? : Say;
     feetDescription? : Say;
+    slots? : Array<number>;
+    layer? : number;
 }
 
 interface ClothingWearerValue {
@@ -50,6 +52,13 @@ class Clothing extends Thing {
     public tightButtSize : number = -1;
     public looseButtSize : number = 0;
 
+    constructor (t : ClothingOptions) {
+        super(t);
+        this.layer = t.layer == undefined ? Clothing.LAYER_MEDIUM : t.layer;
+        this.slots = t.slots == undefined ? [] : [...t.slots];
+
+    }
+
     /**
      * This function must be called any time anything could change clothing on a person.
      * Ripped a clothing? Update all clothes.

+ 1 - 0
app/World/Classes/Things/CoinPouch.ts

@@ -97,6 +97,7 @@ Say.afterPrinting.addRule(new Rule(
         conditions : (rulebook : RulebookRunner<Say>) => {
             return Elements.RoomHandler.PrintingVisibleThingsRulebook.isRunning()
                 && (<Say> rulebook.noun).currentNoun instanceof CoinPouch
+                && (<CoinPouch> (<Say> rulebook.noun).currentNoun).getEnclosedOne() == WorldState.player.getRoom()
                 && (<CoinPouch> (<Say> rulebook.noun).currentNoun).getCoins() > 0;
         }
     }

+ 28 - 5
app/World/Classes/Things/Humanoid/Humanoid.ts

@@ -314,9 +314,20 @@ class Humanoid extends Person {
             }
         });
 
+        this.addGetAlterations((humanoid : Humanoid) => {
+            // Let's not do this for NPCs. It can break patches.
+            let allParts = this.getParts();
+            let changes = {};
+            for (let i = 0; i < allParts.length; i++) {
+                let part = <Bodypart> allParts[i];
+                changes[part.getName()] = part.getChanges();
+            }
+            return {bodyparts : changes};
+        });
+
         this.addSetAlterations((humanoid : Humanoid, changes) => {
             // Let's not do this for NPCs. It can break patches.
-            if (humanoid.isPlayer()) {
+            if (changes.HumanoidGender != undefined) {
                 humanoid.removeGenderedParts();
                 if (changes.HumanoidGender == Humanoid.SEX_MALE) {
                     humanoid.addMaleParts();
@@ -327,6 +338,19 @@ class Humanoid extends Person {
                     humanoid.addFemaleParts();
                 }
             }
+
+            this.addSetAlterations((humanoid : Humanoid, changes : any) => {
+                if (changes.bodyparts != undefined) {
+                    let bpChanges = <any>changes.bodyparts;
+                    let allParts = this.getParts();
+                    for (let i = 0; i < allParts.length; i++) {
+                        let part = <Bodypart>allParts[i];
+                        if (bpChanges[part.getName()] != undefined) {
+                            part.setChanges(bpChanges[part.getName()]);
+                        }
+                    }
+                }
+            });
         });
     }
 
@@ -578,7 +602,9 @@ class Humanoid extends Person {
     public static SLOT_FEET = 23;
     public static SLOT_FEET_NAILS = 24;
     public static SLOT_BREASTS = 25;
-    public static SLOT_SLOT_COUNT = 26;
+    public static SLOT_LEFTHAND = 26;
+    public static SLOT_RIGHTHAND = 27;
+    public static SLOT_SLOT_COUNT = 28;
 
     public static cacheInvalidationActionRule = new Rule({
         name : "Invalidate humanoid caches",
@@ -644,9 +670,6 @@ class Humanoid extends Person {
                 penis.getSizeText(), (penis.isFlaccid() ? " flaccid " : " erect "),
                 Say.COCK, " between your legs"
             );
-            if (!penis.isBig() && penis.isGrower() && penis.isFlaccid()) {
-                say.add(", which is okay, since you're a grower, not a shower");
-            }
             if (penis.isUncovered()) {
                 let oneOf = new OneOf(OneOf.PURELY_AT_RANDOM, ...[
                     ", it is not covered by any clothing",

+ 27 - 5
app/World/Classes/Things/Person.ts

@@ -10,8 +10,17 @@
  *
  * Fuck your standards.
  */
+enum PersonStance {
+    STANDING, ALLFOURS, KNOCKEDOUT
+}
+
+let PersonStanceNames = {};
+PersonStanceNames[PersonStance.STANDING] = "standing";
+PersonStanceNames[PersonStance.ALLFOURS] = "kneeling";
+PersonStanceNames[PersonStance.KNOCKEDOUT] = "passed out";
+
 class Person extends Thing implements AttributeBearer, SkillBearer {
-    public AI = new AI({});
+    public AI = new AI({actor: this});
     public animated = true;
 
     public soreness = 0;
@@ -23,6 +32,8 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
     public static MAX_STAMINA = 10;
     public static STRENGTH_SORENESS_MULTIPLIER = 4;
 
+    public stance : PersonStance = PersonStance.STANDING;
+
     public constructor (options : ThingOptions) {
         super(options);
 
@@ -36,6 +47,11 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
             }
         });
 
+        // We *should* have the bodypart thing here, but there are issues with Humanoids and that...
+        // this.addGetAlterations((person : Person) => {
+        //
+        // });
+
         this.addSetAlterations((person : Person, changes) => {
             // Let's not do this for NPCs. It can break patches.
             if (person.isPlayer()) {
@@ -59,6 +75,10 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
         });
     }
 
+    public isHostileTo (person : Thing) {
+        return this.AI.hostileTo.indexOf(person) != -1;
+    }
+
     public changeHealth (n : number) {
         let bodyparts = <Array<Bodypart>> this.getParts(Bodypart);
         for (let i = 0; i < bodyparts.length; i++) {
@@ -71,9 +91,7 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
      * Returns health as a number from 0 to 10.
      */
     public getHealthOnScale () {
-        return Math.round(
-            ((this.getHealth() * 10) / (this.getStat(Attributes.Strength) * 2))
-        );
+        return Math.ceil(10 - (this.getHealth() * 10));
     }
 
     /**
@@ -86,7 +104,11 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
         if (important === true || this.lastHealthUpdate != WorldState.getCurrentTurn()) {
             this.updateHealth();
         }
-        return this.soreness / (this.getStat(Attributes.Strength) * Person.STRENGTH_SORENESS_MULTIPLIER)
+        return this.soreness / this.getMaxHealth()
+    }
+
+    public getMaxHealth () {
+        return (this.getStat(Attributes.Strength) * Person.STRENGTH_SORENESS_MULTIPLIER);
     }
 
     /**

+ 47 - 0
app/World/Classes/Things/Weapon/Weapon.ts

@@ -0,0 +1,47 @@
+/// <reference path="../Clothing.ts" />
+/// <reference path="../Person/Attribute.ts" />
+interface WeaponOptions extends ClothingOptions {
+    leftHand : boolean;
+    rightHand : boolean;
+    attribute? : Attribute;
+    baseDamage? : number;
+    attackCost? : number;
+}
+
+class Weapon extends Clothing {
+    private baseDamage : number = 0;
+    private attribute = undefined;
+    public attackCost = 1;
+
+    constructor (t : WeaponOptions) {
+        super(t);
+        if (t.leftHand) {
+            this.slots.push(Humanoid.SLOT_LEFTHAND);
+        }
+        if (t.rightHand) {
+            this.slots.push(Humanoid.SLOT_RIGHTHAND);
+        }
+        this.baseDamage = t.baseDamage == undefined ? 0 : t.baseDamage;
+        this.attackCost = t.attackCost == undefined ? 1 : t.attackCost;
+        this.attribute = t.attribute;
+    }
+
+    public getDamage () {
+        let damage = this.baseDamage;
+        let wielder = <Person> this.getWearOne();
+        let attrValue : number;
+        if (this.attribute != undefined) {
+            attrValue = wielder.getStat(this.attribute);
+        } else {
+            attrValue = 0;
+        }
+        // TODO: Reconsider RNG.
+        let result = attrValue + (1 + Math.floor(Math.random() * damage));
+
+        if (this.slots.length == 2) {
+            return Math.floor(result * 5 / 3); // Twohanded modifier
+        } else {
+            return Math.floor(result * 2 / 3); // One-handed modifier
+        }
+    }
+}

+ 12 - 10
app/World/EveryTurn.ts

@@ -16,19 +16,21 @@ module EveryTurn {
 
             let people = <Array<Person>> Thing.InsideRoomRelation.getAnyRightType(Person).filter(isAIAvailable);
             for (let i = 0; i < people.length; i++) {
-                let action = await people[i].AI.execute(people[i]);
                 let person = people[i];
-                let visible = people[i].isVisibleTo(WorldState.player);
+                if (person.getHealthOnScale() > 0) {
+                    let action = await people[i].AI.execute();
+                    let visible = people[i].isVisibleTo(WorldState.player);
 
-                if (action != undefined) {
-                    let printValue: Say = await action.execute();
+                    if (action != undefined) {
+                        let printValue: Say = await action.execute();
 
-                    if (
-                        (
-                            visible ||
-                            person.isVisibleTo(WorldState.player)
-                        ) && printValue != undefined) {
-                        Elements.CurrentTurnHandler.printAsContent(printValue);
+                        if (
+                            (
+                                visible ||
+                                person.isVisibleTo(WorldState.player)
+                            ) && printValue != undefined) {
+                            Elements.CurrentTurnHandler.printAsContent(printValue);
+                        }
                     }
                 }
             }

+ 1 - 0
app/World/TurnSequence.ts

@@ -99,6 +99,7 @@ module TurnSequence {
             await Elements.RememberedHandler.updateMap();
 
             let playerAction = <Action> rulebook.noun;
+
             if (playerAction) {
                 await Elements.HyperlinkHandler.hyperlinkObject(playerAction.getNoun(0));
             } else {

+ 1 - 0
config.rb

@@ -20,6 +20,7 @@ output_style = :expanded
 
 # To disable debugging comments that display the original location of your selectors. Uncomment:
 line_comments = true
+line_comments = false
 
 
 # If you prefer the indented syntax, you might want to regenerate this

+ 77 - 12
content/main.ts

@@ -241,7 +241,7 @@ for (let i = 0; i < 0; i++) {
 }
 let randomOrc;
 let randomOrc2;
-for (let i = 0; i < 10; i++) {
+for (let i = 0; i < 2; i++) {
     let orc = new OrcDebugger();
     randomOrc = orc;
     if (randomOrc2 == undefined) {
@@ -304,14 +304,79 @@ spitroast.addUnit()
     .setStick(HumanoidPenis);
 
 
-let orcAnim = document.getElementById("orcAnim");
-let step = 1;
-let direction = 1;
-setInterval(() => {
-    for (let i = 1; i <= 3; i++) {
-        orcAnim.classList.remove("anims-Orc-" + i);
-    }
-    orcAnim.classList.add("anims-Orc-" + step);
-    direction = step == 3 ? -1 : step == 1 ? 1 : direction;
-    step += direction;
-}, 500);
+// (new CombatDescription("Poking 2")
+//     .setDescription("Oy cheeky kunt stahp that"))
+//     .addPokeUnit()
+//     .setTarget(Person)
+//     .addMarker(CombatPokeResult.ANNOYED);
+//
+// (new CombatDescription("Poking 1")
+//     .setDescription("Heh whatever"))
+//     .addPokeUnit()
+//     .setTarget(Person)
+//     .addMarker(CombatPokeResult.NOHEAT);
+//
+// (new CombatDescription("Poking 3")
+//     .setDescription("A'IGHT YOU GET FUCKED NOW MATE SWAER ON ME MUM"))
+//     .addPokeUnit()
+//     .setTarget(Person)
+//     .addMarker(CombatPokeResult.AGGROED);
+
+(new CombatPokeDescription("Hitting Orc"))
+    .setDescriptionFunction((target, markers) => {
+        let say = new Say(new SayBold(target), ": ");
+
+        if (markers.includes(CombatPokeResult.NOHEAT)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Heh, ain't that cute?",
+                    "Pathetic."
+                )
+            );
+        } else if (markers.includes(CombatPokeResult.ANNOYED)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Oy, stahp that.",
+                    "Ya' betta' stahp that before I getting mad."
+                )
+            );
+        } else if (markers.includes(CombatPokeResult.AGGROED)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Aight. The axe it is."
+                )
+            );
+        }
+        return say;
+    })
+    .addUnit()
+    .setTarget(OrcDebugger)
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatPokeResult.AGGROED, CombatPokeResult.NOHEAT, CombatPokeResult.ANNOYED));
+
+
+(new CombatDescription("Allranging Fists"))
+    .setDescriptionFunction((actor, target, weapons, markers) => {
+        let say = new Say("You attack ", new SayThe(), target, " with your fists");
+        if (markers.indexOf(CombatHit.MISS) != -1) {
+            say.add(", but you miss");
+        } else if (markers.indexOf(CombatHit.CRITICAL) != -1) {
+            say.add(", it is a strong hit");
+        }
+
+        if (markers.indexOf(CombatResult.KNOCKED) != -1) {
+            say.add(", the strength of your attack knocks ", new SayHimHerIt(target), " on the floor.");
+        } else if (markers.indexOf(CombatResult.KNOCKED_OFF) != -1) {
+            say.add(", the strength of your attack knocks ", new SayHimHerIt(target), " unconscious.");
+        } else if (markers.indexOf(CombatResult.KILLED) != -1) {
+            say.add(", ", new SayHeSheIt(target), " dies.");
+        } else {
+            say.add(".");
+        }
+
+        return say;
+    })
+    .addUnit()
+    .setActor(WorldState.player)
+    .setTarget(Person)
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatHit.HIT, CombatHit.CRITICAL, CombatHit.MISS))
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatResult.KILLED, CombatResult.KNOCKED, CombatResult.KNOCKED_OFF));

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/The Obelisk.html


+ 1 - 1
sass/__imagesLinks.scss

@@ -1,3 +1,3 @@
 .introLogo {
   background-image: inline-image("IntroLogo.svg");
-}
+}

+ 22 - 1
sass/_animations.scss

@@ -8,4 +8,25 @@
 $anims-layout:vertical;
 $anims-sprite-dimensions: true;
 @import "anims/*/**.png";
-@include all-anims-sprites;
+@include all-anims-sprites;
+
+@import "_animationsBG";
+
+
+#sceneAnimation {
+  display: block;
+  position: fixed;
+  bottom: 0px;
+  left: 50%;
+  z-index: 1;
+  pointer-events: none;
+  opacity: 0;
+}
+
+.sceneAnimation {
+  display: block;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  image-rendering: pixelated;
+}

+ 5 - 0
sass/_animationsBG.scss

@@ -0,0 +1,5 @@
+.bg-Forest {
+  background-image: url("images/Forest.png");
+  width: 400px;
+  height: 200px;
+}

+ 8 - 0
sass/_modal.scss

@@ -48,7 +48,15 @@ body.modal {
   transition: filter .3s ease-in-out;
 }
 
+#sceneAnimation {
+  transition: opacity  .3s ease-in-out;
+}
+
 #mainPage.turn {
+  & #sceneAnimation {
+    opacity: 1;
+  }
+
   & #leftWindow , #rightWindow , #currentRoomTab, #hyperlinksTab, #fakeparserTab , #statusLine {
     pointer-events: none;
     filter: blur(2px);

+ 7 - 20
sass/_page.scss

@@ -20,6 +20,7 @@
 }
 
 .scrollbar {
+  overflow: auto;
   &::-webkit-scrollbar-track {
     -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
     background-color: #F5F5F5;
@@ -173,10 +174,13 @@ body {
   overflow: auto;
   //background: linear-gradient(to right, rgba(0,0,0, $dark1) 0%, rgba(0,0,0, $dark2) 50%,rgba(0,0,0, $dark3) 100%);
   padding: 0.8rem;
+  //padding-top: 200px;
 }
 
 #inventoryTab {
-  overflow: hidden;
+  overflow-y: auto;
+  flex-shrink: 1;
+  //overflow: hidden;
   //background: linear-gradient(to left, rgba(0,0,0, $dark1) 0%, rgba(0,0,0, $dark2) 50%,rgba(0,0,0, $dark3) 100%);
 }
 
@@ -222,27 +226,10 @@ body {
   padding: 0.8rem;
 }
 
-#sceneAnimation {
-  @extend .noshrinkFlex;
-  height: 200px;
-  position: relative;
-  background: linear-gradient(to top left, rgba(0,0,0, $dark1) 0%, rgba(0,0,0, $dark2) 50%,rgba(0,0,0, $dark3) 100%);
-}
-
-
-.sceneAnimation {
-  display: block;
-  position: absolute;
-  height: 200px;
-  width: 200px;
-  left: 50%;
-  margin-left: -100px;
-}
-
 // #mainPage.mobile
 #roomExitsHolder {
   @extend .noshrinkFlex;
-  background: linear-gradient(to bottom left, rgba(0,0,0, $dark1) 0%, rgba(0,0,0, $dark2) 50%,rgba(0,0,0, $dark3) 100%);
+  background: linear-gradient(to top left, rgba(0,0,0, $dark1) 0%, rgba(0,0,0, $dark2) 50%,rgba(0,0,0, $dark3) 100%);
   padding: 2ex;
   padding-top: 0px;
 }
@@ -251,4 +238,4 @@ body {
   overflow: hidden;
   background-color: rgba(0,0,0, $dark4);
   padding: 0.8rem;
-}
+}

+ 0 - 6
sass/screen.scss

@@ -1,9 +1,3 @@
-/* Welcome to Compass.
- * In this file you should write your main styles. (or centralize your imports)
- * Import this file using the following HTML or equivalent:
- * <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
-
-// Reset
 p {
   margin: 0px;
   padding: 0px;

Diferenças do arquivo suprimidas por serem muito extensas
+ 51 - 22
stylesheets/images.css


+ 14 - 172
stylesheets/screen.css

@@ -1,26 +1,18 @@
-/* Welcome to Compass.
- * In this file you should write your main styles. (or centralize your imports)
- * Import this file using the following HTML or equivalent:
- * <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
-/* line 7, ../sass/screen.scss */
 p {
   margin: 0px;
   padding: 0px;
 }
 
-/* line 1, ../sass/_page.scss */
 .error {
   padding: 0.5em;
   background-color: #d6a3a7;
   color: #780100;
 }
-/* line 5, ../sass/_page.scss */
 .error:before {
   content: "ERROR: ";
   font-weight: bold;
 }
 
-/* line 11, ../sass/_page.scss */
 .unselectable, .textLink, .lineLink, .columnLink, .ccButton, .roomObject, .roomDirection, .rememberedRoomLink, .inventoryLink, .statusLink, .roundButton, p.choice, .combatChoice {
   -moz-user-select: -moz-none;
   -khtml-user-select: none;
@@ -28,54 +20,47 @@ p {
   -o-user-select: none;
   user-select: none;
 }
-/* line 17, ../sass/_page.scss */
 .unselectable:hover, .textLink:hover, .lineLink:hover, .columnLink:hover, .ccButton:hover, .roomObject:hover, .roomDirection:hover, .rememberedRoomLink:hover, .inventoryLink:hover, .statusLink:hover, .roundButton:hover, p.choice:hover, .combatChoice:hover {
   cursor: default;
 }
 
-/* line 23, ../sass/_page.scss */
+.scrollbar {
+  overflow: auto;
+}
 .scrollbar::-webkit-scrollbar-track {
   -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
   background-color: #F5F5F5;
 }
-/* line 28, ../sass/_page.scss */
 .scrollbar::-webkit-scrollbar {
   width: 6px;
   background-color: #F5F5F5;
 }
-/* line 33, ../sass/_page.scss */
 .scrollbar::-webkit-scrollbar-thumb {
   background-color: #000000;
 }
 
-/* line 38, ../sass/_page.scss */
 .header, .roomName, .roomExitsHeader, .inventoryHeader, .appearanceHeader {
   font-weight: bold;
   font-variant: small-caps;
   font-size: 1.1rem;
 }
 
-/* line 44, ../sass/_page.scss */
 .alignRight {
   text-align: right;
 }
 
-/* line 48, ../sass/_page.scss */
 .alignCenter {
   text-align: center;
 }
 
-/* line 53, ../sass/_page.scss */
 body {
   background-color: #000;
 }
 
-/* line 57, ../sass/_page.scss */
 #mainPage {
   background-color: #fff;
 }
 
-/* line 61, ../sass/_page.scss */
 #mainPage, .topBottomFlex {
   position: absolute;
   left: 0px;
@@ -90,7 +75,6 @@ body {
   align-items: flex-start;
 }
 
-/* line 75, ../sass/_page.scss */
 .leftRightFlex {
   display: flex;
   flex-direction: row;
@@ -100,14 +84,12 @@ body {
   align-items: flex-start;
 }
 
-/* line 84, ../sass/_page.scss */
 #mainPage {
   color: #000;
   font-family: "Source Sans Pro";
 }
 
-/* line 89, ../sass/_page.scss */
-#statusLine, .noshrinkFlex, #sceneAnimation, #roomExitsHolder {
+#statusLine, .noshrinkFlex, #roomExitsHolder {
   flex: 0 0 auto;
   align-self: stretch;
   height: auto;
@@ -115,7 +97,6 @@ body {
   position: relative;
 }
 
-/* line 97, ../sass/_page.scss */
 #statusLine {
   background-color: black;
   color: #FFF;
@@ -124,32 +105,27 @@ body {
   padding: 0.4rem;
 }
 
-/* line 105, ../sass/_page.scss */
 .statusColumnRight {
   margin-left: auto;
 }
 
-/* line 109, ../sass/_page.scss */
 .statusColumnCenter {
   margin-left: auto;
   margin-right: auto;
 }
 
-/* line 114, ../sass/_page.scss */
 .statusColumnDivider {
   border-right: #fff solid 1px;
   margin-left: 0.5rem;
   margin-right: 0.5rem;
 }
 
-/* line 120, ../sass/_page.scss */
 #gameContainer, .growingFlex {
   flex: 1 1 auto;
   align-self: stretch;
   position: relative;
 }
 
-/* line 126, ../sass/_page.scss */
 #leftWindow, #centerWindow, #rightWindow {
   flex: 1 0 auto;
   align-self: auto;
@@ -159,14 +135,12 @@ body {
   background-color: #fff;
 }
 
-/* line 135, ../sass/_page.scss */
 #centerWindow {
   max-width: 60%;
   flex: 1 1 auto;
   flex-grow: 4;
 }
 
-/* line 141, ../sass/_page.scss */
 #windowContainer {
   display: flex;
   flex-direction: row;
@@ -179,7 +153,6 @@ body {
   position: absolute;
 }
 
-/* line 153, ../sass/_page.scss */
 #currentTurn {
   overflow-x: hidden;
   overflow-y: scroll;
@@ -187,7 +160,6 @@ body {
   background: linear-gradient(to bottom, rgba(0, 0, 0, 0.03) 75%, rgba(0, 0, 0, 0.06) 100%);
 }
 
-/* line 160, ../sass/_page.scss */
 #currentTurn > * {
   animation: fadein 1s;
 }
@@ -200,58 +172,49 @@ body {
     opacity: 1;
   }
 }
-/* line 168, ../sass/_page.scss */
 #leftWindow {
   background: linear-gradient(to right, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0.09) 50%, rgba(0, 0, 0, 0.06) 100%);
 }
 
-/* line 172, ../sass/_page.scss */
 #appearanceTab {
   overflow: auto;
   padding: 0.8rem;
 }
 
-/* line 178, ../sass/_page.scss */
 #inventoryTab {
-  overflow: hidden;
+  overflow-y: auto;
+  flex-shrink: 1;
 }
 
-/* line 183, ../sass/_page.scss */
 #currentRoomTab {
   overflow: hidden;
   background: linear-gradient(to bottom, rgba(0, 0, 0, 0.06) 75%, rgba(0, 0, 0, 0.03) 100%);
   padding: 0.8rem;
 }
 
-/* line 189, ../sass/_page.scss */
 #hyperlinksTab {
   background: rgba(0, 0, 0, 0.03);
   text-indent: 1em;
 }
 
-/* line 194, ../sass/_page.scss */
 #mainPage.mobile #fakeparserTab {
   display: none;
 }
 
-/* line 198, ../sass/_page.scss */
 #fakeparserTab {
   overflow: visible;
   background: rgba(0, 0, 0, 0.03);
   padding: 0.8rem;
   font-family: "Cousine";
 }
-/* line 204, ../sass/_page.scss */
 #fakeparserTab:before {
   content: ">";
 }
 
-/* line 209, ../sass/_page.scss */
 #currentCommand, #fakeParserThingy {
   font-weight: bold;
 }
 
-/* line 213, ../sass/_page.scss */
 #fakeParserThingy {
   animation: blinker 0.9s cubic-bezier(0.5, 0, 1, 1) infinite alternate;
 }
@@ -261,45 +224,24 @@ body {
     opacity: 0;
   }
 }
-/* line 219, ../sass/_page.scss */
 #rememberedRoomsTab, #mapTab {
   overflow-y: auto;
   background: linear-gradient(to bottom left, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0.09) 50%, rgba(0, 0, 0, 0.06) 100%);
   padding: 0.8rem;
 }
 
-/* line 225, ../sass/_page.scss */
-#sceneAnimation {
-  height: 200px;
-  position: relative;
-  background: linear-gradient(to top left, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0.09) 50%, rgba(0, 0, 0, 0.06) 100%);
-}
-
-/* line 233, ../sass/_page.scss */
-.sceneAnimation {
-  display: block;
-  position: absolute;
-  height: 200px;
-  width: 200px;
-  left: 50%;
-  margin-left: -100px;
-}
-
-/* line 243, ../sass/_page.scss */
 #roomExitsHolder {
-  background: linear-gradient(to bottom left, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0.09) 50%, rgba(0, 0, 0, 0.06) 100%);
+  background: linear-gradient(to top left, rgba(0, 0, 0, 0.12) 0%, rgba(0, 0, 0, 0.09) 50%, rgba(0, 0, 0, 0.06) 100%);
   padding: 2ex;
   padding-top: 0px;
 }
 
-/* line 250, ../sass/_page.scss */
 #exitsTab {
   overflow: hidden;
   background-color: rgba(0, 0, 0, 0.03);
   padding: 0.8rem;
 }
 
-/* line 1, ../sass/_modal.scss */
 #modalWindow {
   opacity: 0;
   pointer-events: none;
@@ -316,22 +258,18 @@ body {
   overflow: hidden;
 }
 
-/* line 17, ../sass/_modal.scss */
 *:not(#modalWindow) {
   transition: filter .3s ease-in-out;
 }
 
-/* line 22, ../sass/_modal.scss */
 body.modal > *:not(#modalWindow) {
   filter: blur(3px);
 }
-/* line 26, ../sass/_modal.scss */
 body.modal #modalWindow {
   opacity: 1;
   pointer-events: auto;
 }
 
-/* line 32, ../sass/_modal.scss */
 #modalContent {
   width: auto;
   height: auto;
@@ -347,27 +285,29 @@ body.modal #modalWindow {
   padding-top: 0px;
 }
 
-/* line 47, ../sass/_modal.scss */
 #leftWindow, #rightWindow, #currentRoomTab, #hyperlinksTab, #fakeparserTab, #statusLine {
   transition: filter .3s ease-in-out;
 }
 
-/* line 52, ../sass/_modal.scss */
+#sceneAnimation {
+  transition: opacity  .3s ease-in-out;
+}
+
+#mainPage.turn #sceneAnimation {
+  opacity: 1;
+}
 #mainPage.turn #leftWindow, #mainPage.turn #rightWindow, #mainPage.turn #currentRoomTab, #mainPage.turn #hyperlinksTab, #mainPage.turn #fakeparserTab, #mainPage.turn #statusLine {
   pointer-events: none;
   filter: blur(2px);
 }
 
-/* line 59, ../sass/_modal.scss */
 #mainPage.intro #leftWindow, #mainPage.intro #rightWindow, #mainPage.intro #statusLine {
   display: none;
 }
-/* line 63, ../sass/_modal.scss */
 #mainPage.intro #centerWindow {
   max-width: 100%;
 }
 
-/* line 68, ../sass/_modal.scss */
 #loadingScreen {
   position: absolute;
   left: 0px;
@@ -379,21 +319,18 @@ body.modal #modalWindow {
   z-index: 99999;
 }
 
-/* line 12, ../sass/_linktab.scss */
 .lineLink {
   line-height: 2em;
   padding-left: 0.35em;
   padding-right: 0.35em;
 }
 
-/* line 23, ../sass/_linktab.scss */
 .columnLink {
   line-height: 2em;
   padding-left: 0.35em;
   padding-right: 0.35em;
 }
 
-/* line 30, ../sass/_linktab.scss */
 #linkTarget {
   font-weight: bold;
   display: inline;
@@ -401,13 +338,11 @@ body.modal #modalWindow {
   margin-left: 2ex;
 }
 
-/* line 37, ../sass/_linktab.scss */
 #linkActions {
   display: inline;
   white-space: nowrap;
 }
 
-/* line 1, ../sass/_map.scss */
 .mapRow {
   padding: 0;
   margin: 0;
@@ -419,7 +354,6 @@ body.modal #modalWindow {
   z-index: 1;
 }
 
-/* line 12, ../sass/_map.scss */
 .mapRoom {
   z-index: 2;
   opacity: 0.5;
@@ -430,24 +364,19 @@ body.modal #modalWindow {
   height: auto;
   position: relative;
 }
-/* line 21, ../sass/_map.scss */
 .mapRoom:last-child {
   margin-right: 1ex;
 }
-/* line 26, ../sass/_map.scss */
 .mapRoom.linked:hover {
   opacity: 1;
   cursor: pointer;
 }
-/* line 31, ../sass/_map.scss */
 .mapRoom.current {
   opacity: 1;
 }
-/* line 33, ../sass/_map.scss */
 .mapRoom.current:hover {
   cursor: auto;
 }
-/* line 37, ../sass/_map.scss */
 .mapRoom.current::after {
   display: block;
   position: absolute;
@@ -457,19 +386,16 @@ body.modal #modalWindow {
   height: 100%;
   width: 100%;
 }
-/* line 55, ../sass/_map.scss */
 .mapRoom.unknown {
   opacity: 0;
   pointer-events: none;
 }
-/* line 60, ../sass/_map.scss */
 .mapRoom::before {
   content: '';
   float: left;
   padding-top: 100%;
 }
 
-/* line 67, ../sass/_map.scss */
 .mapRoomConnection, .mapRoomConnectionEast, .mapRoomConnectionWest, .mapRoomConnectionNorth, .mapRoomConnectionSouth {
   pointer-events: none;
   display: block;
@@ -478,7 +404,6 @@ body.modal #modalWindow {
   z-index: 3;
 }
 
-/* line 75, ../sass/_map.scss */
 .mapRoomConnectionEast {
   width: 1ex;
   height: 0.6ex;
@@ -487,7 +412,6 @@ body.modal #modalWindow {
   margin-top: -0.3ex;
 }
 
-/* line 84, ../sass/_map.scss */
 .mapRoomConnectionWest {
   width: 1ex;
   height: 0.6ex;
@@ -496,7 +420,6 @@ body.modal #modalWindow {
   margin-top: -0.3ex;
 }
 
-/* line 93, ../sass/_map.scss */
 .mapRoomConnectionNorth {
   width: 0.6ex;
   height: 1ex;
@@ -505,7 +428,6 @@ body.modal #modalWindow {
   margin-left: -0.3ex;
 }
 
-/* line 102, ../sass/_map.scss */
 .mapRoomConnectionSouth {
   width: 0.6ex;
   height: 1ex;
@@ -514,7 +436,6 @@ body.modal #modalWindow {
   margin-left: -0.3ex;
 }
 
-/* line 111, ../sass/_map.scss */
 .mapRoomName {
   display: none;
   position: absolute;
@@ -532,31 +453,25 @@ body.modal #modalWindow {
   pointer-events: none;
 }
 
-/* line 128, ../sass/_map.scss */
 .mapRoom.linked:hover > .mapRoomName {
   display: block;
 }
 
-/* line 132, ../sass/_map.scss */
 .tomato {
   background: tomato;
 }
 
-/* line 136, ../sass/_map.scss */
 .bloo {
   background: blue;
 }
 
-/* line 140, ../sass/_map.scss */
 .blocked {
   background-color: black;
 }
-/* line 143, ../sass/_map.scss */
 .blocked.unknown {
   opacity: 1;
 }
 
-/* line 1, ../sass/_hoverInfo.scss */
 #hoverInfo {
   z-index: 100;
   position: fixed;
@@ -572,35 +487,28 @@ body.modal #modalWindow {
   box-sizing: border-box;
 }
 
-/* line 1, ../sass/_mainMenu.scss */
 #forceTurnToTop {
   display: none;
 }
 
-/* line 6, ../sass/_mainMenu.scss */
 #mainPage.mainmenu #statusLine, #mainPage.mainmenu #statusLine, #mainPage.mainmenu #rightWindow, #mainPage.mainmenu #currentRoomTab, #mainPage.mainmenu #fakeparserTab, #mainPage.mainmenu #hyperlinksTab, #mainPage.mainmenu #leftWindow {
   display: none;
 }
-/* line 10, ../sass/_mainMenu.scss */
 #mainPage.mainmenu #forceTurnToTop {
   display: block;
 }
-/* line 14, ../sass/_mainMenu.scss */
 #mainPage.mainmenu #centerWindow {
   max-width: 100%;
 }
-/* line 18, ../sass/_mainMenu.scss */
 #mainPage.mainmenu #currentTurnTab {
   padding-left: 15%;
   padding-right: 15%;
   padding-bottom: 2%;
 }
-/* line 24, ../sass/_mainMenu.scss */
 #mainPage.mainmenu.mobile #currentTurnTab {
   padding-left: 0%;
   padding-right: 0%;
 }
-/* line 30, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice {
   border: none;
   background: none;
@@ -612,37 +520,30 @@ body.modal #modalWindow {
   margin-bottom: 0.1rem;
   margin-top: 0.1rem;
 }
-/* line 44, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice::before {
   color: #000;
 }
-/* line 47, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice::after {
   content: "  ";
   color: #000;
 }
-/* line 53, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice:hover::before {
   content: "< ";
   color: #000;
 }
-/* line 57, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice:hover::after {
   content: " >";
   color: #000;
 }
-/* line 64, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice[data-shortcut]:hover::before {
   content: "< " attr(data-shortcut) ") ";
   color: #000;
 }
-/* line 68, ../sass/_mainMenu.scss */
 #mainPage.mainmenu p.choice[data-shortcut]:hover::after {
   content: " >";
   color: #000;
 }
 
-/* line 1, ../sass/_characterCreation.scss */
 #characterCreation {
   display: flex;
   flex-direction: row;
@@ -652,47 +553,40 @@ body.modal #modalWindow {
   align-items: center;
 }
 
-/* line 10, ../sass/_characterCreation.scss */
 #ccLeft {
   flex: 0 1 auto;
   width: 50%;
   align-self: auto;
 }
 
-/* line 16, ../sass/_characterCreation.scss */
 #ccRight {
   flex: 0 1 auto;
   width: 50%;
   align-self: center;
 }
 
-/* line 22, ../sass/_characterCreation.scss */
 .ccOption {
   margin: auto;
   margin-bottom: 2.25ex;
   text-align: center;
 }
 
-/* line 28, ../sass/_characterCreation.scss */
 .ccRange {
   vertical-align: top;
   margin-left: 1.5ex;
   margin-right: 1.5ex;
 }
 
-/* line 36, ../sass/_characterCreation.scss */
 .ccButton[data-shortcut]:before {
   content: " (" attr(data-shortcut) ") " !important;
 }
 
-/* line 41, ../sass/_characterCreation.scss */
 .ccOptionTopLabel {
   text-align: center;
   font-weight: bold;
   margin-bottom: -0.25ex;
 }
 
-/* line 47, ../sass/_characterCreation.scss */
 .ccHeader {
   text-align: center;
   font-weight: bold;
@@ -701,70 +595,57 @@ body.modal #modalWindow {
   font-size: 1.1rem;
 }
 
-/* line 55, ../sass/_characterCreation.scss */
 .rangeValue {
   margin-top: -0.25ex;
 }
 
-/* line 1, ../sass/text/_links.scss */
 .textLink, .lineLink, .columnLink, .ccButton, .roomObject, .roomDirection, .rememberedRoomLink, .inventoryLink {
   font-weight: bold;
   color: #0000aa;
 }
-/* line 6, ../sass/text/_links.scss */
 .textLink:hover, .lineLink:hover, .columnLink:hover, .ccButton:hover, .roomObject:hover, .roomDirection:hover, .rememberedRoomLink:hover, .inventoryLink:hover {
   color: #0000ff;
   cursor: pointer;
 }
-/* line 9, ../sass/text/_links.scss */
 .textLink:hover:active, .lineLink:hover:active, .columnLink:hover:active, .ccButton:hover:active, .roomObject:hover:active, .roomDirection:hover:active, .rememberedRoomLink:hover:active, .inventoryLink:hover:active {
   color: #000099;
 }
-/* line 14, ../sass/text/_links.scss */
 .textLink[data-shortcut]:before, [data-shortcut].lineLink:before, [data-shortcut].columnLink:before, [data-shortcut].ccButton:before, [data-shortcut].roomObject:before, [data-shortcut].roomDirection:before, [data-shortcut].rememberedRoomLink:before, [data-shortcut].inventoryLink:before {
   content: attr(data-shortcut) ") ";
 }
 
-/* line 19, ../sass/text/_links.scss */
 .statusLink {
   font-weight: bold;
   color: #99f;
 }
-/* line 24, ../sass/text/_links.scss */
 .statusLink:hover {
   color: #ddf;
   cursor: pointer;
 }
-/* line 27, ../sass/text/_links.scss */
 .statusLink:hover:active {
   color: #66d;
 }
 
-/* line 1, ../sass/text/_room.scss */
 .roomName {
   font-size: 1.2rem;
   margin-bottom: 1ex;
 }
 
-/* line 7, ../sass/text/_room.scss */
 .roomDescription {
   text-indent: 1rem;
   text-align: justify;
   margin-top: 1ex;
 }
 
-/* line 13, ../sass/text/_room.scss */
 .roomExitsHeader {
   margin-top: 2ex;
 }
 
-/* line 18, ../sass/text/_room.scss */
 .roomExit {
   text-indent: 1.5rem;
   margin-top: 1ex;
 }
 
-/* line 27, ../sass/text/_room.scss */
 .rememberedRoomRow {
   display: flex;
   flex-direction: row;
@@ -773,25 +654,20 @@ body.modal #modalWindow {
   align-content: stretch;
   align-items: stretch;
 }
-/* line 34, ../sass/text/_room.scss */
 .rememberedRoomRow:hover {
   background-color: rgba(0, 0, 0, 0.12);
 }
-/* line 39, ../sass/text/_room.scss */
 .rememberedRoomRow.currentRoom > .rememberedRoomLink {
   color: #000;
   display: none;
 }
-/* line 43, ../sass/text/_room.scss */
 .rememberedRoomRow.currentRoom > .rememberedRoomLink:hover {
   cursor: auto;
 }
-/* line 47, ../sass/text/_room.scss */
 .rememberedRoomRow.currentRoom > .rememberedRoomLink.name {
   display: initial;
 }
 
-/* line 54, ../sass/text/_room.scss */
 .rememberedRoomLink {
   white-space: nowrap;
   overflow: hidden;
@@ -802,29 +678,24 @@ body.modal #modalWindow {
   padding-left: 1ex;
   padding-right: 1ex;
 }
-/* line 65, ../sass/text/_room.scss */
 .rememberedRoomLink.name {
   text-overflow: ellipsis;
   padding-left: 1.5ex;
   order: 1;
   flex: 1 1 auto;
 }
-/* line 71, ../sass/text/_room.scss */
 .rememberedRoomLink.name:hover {
   overflow: visible;
   white-space: initial;
 }
 
-/* line 1, ../sass/text/_say.scss */
 p.contentOld {
   color: #888;
 }
-/* line 4, ../sass/text/_say.scss */
 p.contentOld:hover {
   color: #000;
 }
 
-/* line 9, ../sass/text/_say.scss */
 p.contentOld, p.content {
   text-indent: 1rem;
   text-align: justify;
@@ -832,27 +703,22 @@ p.contentOld, p.content {
   padding-left: 1.6rem;
   padding-right: 1.6rem;
 }
-/* line 16, ../sass/text/_say.scss */
 p.contentOld.centered, p.content.centered {
   text-indent: 0px;
   text-align: center;
 }
 
-/* line 22, ../sass/text/_say.scss */
 .textIndenter {
   margin-right: 1rem;
 }
 
-/* line 26, ../sass/text/_say.scss */
 div.choiceContainer {
   padding-bottom: 0.8rem;
 }
-/* line 28, ../sass/text/_say.scss */
 div.choiceContainer:hover {
   background-color: rgba(10, 10, 80, 0.05);
 }
 
-/* line 33, ../sass/text/_say.scss */
 .roundButton, p.choice, .combatChoice {
   padding: 0.3rem;
   margin: 0.8rem;
@@ -862,43 +728,35 @@ div.choiceContainer:hover {
   border-radius: 1rem;
   background-color: #fff;
 }
-/* line 43, ../sass/text/_say.scss */
 .roundButton[data-shortcut]:before, p[data-shortcut].choice:before, [data-shortcut].combatChoice:before {
   content: attr(data-shortcut) ") ";
   font-weight: bold;
   color: #0000aa;
 }
-/* line 49, ../sass/text/_say.scss */
 .roundButton:hover, p.choice:hover, .combatChoice:hover {
   cursor: pointer;
   background-color: #eeeeff;
   border-color: #0000ff;
 }
-/* line 53, ../sass/text/_say.scss */
 .roundButton:hover[data-shortcut]:before, p.choice:hover[data-shortcut]:before, .combatChoice:hover[data-shortcut]:before {
   color: #0000ff;
 }
-/* line 58, ../sass/text/_say.scss */
 .roundButton:active, p.choice:active, .combatChoice:active {
   background-color: #c8c8ff;
   border-color: #000099;
 }
-/* line 61, ../sass/text/_say.scss */
 .roundButton:active[data-shortcut]:before, p.choice:active[data-shortcut]:before, .combatChoice:active[data-shortcut]:before {
   color: #000099;
 }
 
-/* line 67, ../sass/text/_say.scss */
 p.choice {
   text-indent: 0.5rem;
   text-align: justify;
 }
-/* line 71, ../sass/text/_say.scss */
 p.choice.picked {
   color: #888;
 }
 
-/* line 76, ../sass/text/_say.scss */
 .combatChoicesContainer {
   display: flex;
   flex-direction: row;
@@ -908,7 +766,6 @@ p.choice.picked {
   align-items: stretch;
 }
 
-/* line 85, ../sass/text/_say.scss */
 .combatChoice {
   flex: 0 0 auto;
   align-self: auto;
@@ -917,7 +774,6 @@ p.choice.picked {
   margin: 0.3em;
 }
 
-/* line 94, ../sass/text/_say.scss */
 p.turnStart {
   display: flex;
   flex-basis: 100%;
@@ -926,22 +782,18 @@ p.turnStart {
   font-weight: bold;
   line-height: 1em;
 }
-/* line 102, ../sass/text/_say.scss */
 p.turnStart::before {
   margin-right: 10px;
 }
-/* line 106, ../sass/text/_say.scss */
 p.turnStart::after {
   margin-left: 10px;
 }
-/* line 110, ../sass/text/_say.scss */
 p.turnStart::before, p.turnStart::after {
   content: "";
   flex-grow: 1;
   border-bottom: solid 1px rgba(180, 0, 0, 0.6);
 }
 
-/* line 117, ../sass/text/_say.scss */
 .horFlex {
   display: flex;
   flex-direction: row;
@@ -951,20 +803,17 @@ p.turnStart::before, p.turnStart::after {
   align-items: center;
 }
 
-/* line 126, ../sass/text/_say.scss */
 .horFlexColumn {
   flex: 0 1 auto;
   width: 50%;
   align-self: auto;
 }
 
-/* line 1, ../sass/text/_inventory.scss */
 .inventoryHeader {
   margin-top: 1em;
   margin-left: 1ex;
 }
 
-/* line 7, ../sass/text/_inventory.scss */
 .inventoryRow {
   display: flex;
   flex-direction: row;
@@ -973,12 +822,10 @@ p.turnStart::before, p.turnStart::after {
   align-content: stretch;
   align-items: stretch;
 }
-/* line 14, ../sass/text/_inventory.scss */
 .inventoryRow:hover {
   background-color: rgba(0, 0, 0, 0.12);
 }
 
-/* line 19, ../sass/text/_inventory.scss */
 .inventoryLink {
   white-space: nowrap;
   overflow: hidden;
@@ -989,20 +836,17 @@ p.turnStart::before, p.turnStart::after {
   padding-left: 1ex;
   padding-right: 1ex;
 }
-/* line 30, ../sass/text/_inventory.scss */
 .inventoryLink.name {
   text-overflow: ellipsis;
   padding-left: 1.5ex;
   order: 1;
   flex: 1 1 auto;
 }
-/* line 36, ../sass/text/_inventory.scss */
 .inventoryLink.name:hover {
   overflow: visible;
   white-space: initial;
 }
 
-/* line 42, ../sass/text/_inventory.scss */
 .inventoryGold {
   text-align: center;
   font-size: 1rem;
@@ -1010,12 +854,10 @@ p.turnStart::before, p.turnStart::after {
   padding-top: 1ex;
 }
 
-/* line 49, ../sass/text/_inventory.scss */
 #inventoryTarget {
   padding-bottom: 1ex;
 }
 
-/* line 5, ../sass/text/_appearance.scss */
 .appearanceDescription {
   padding: 0.5ex;
   text-align: justify;

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 1
stylesheets/screenInline.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 19 - 164
stylesheets/screenLink.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 65 - 190
stylesheets/screenRelative.css


+ 24 - 0
tools/FuckingDescription.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+<script type="text/javascript">
+    // (new FuckingDescription("Specific Orc Starts Cumming in Vagina"))
+    //     .setDescription(new Say("Specific Orc Starts Cumming in Vagina"))
+    //     .addUnit()
+    //     .setFucker(randomOrc)
+    //     .setHole(WorldState.player.getPart(HumanoidVagina))
+    //     .addMarker(FuckingState.CUM_START)
+    //     .setStick(randomOrc.getPart(HumanoidPenis));
+    FuckedTarget -> WorldState.player
+    Fucker -> Person
+    SetStick -> SexStick
+    SetHole -> SexHole
+
+
+</script>
+</body>
+</html>

+ 17 - 5
tools/dialogger/dialogger.js

@@ -120,7 +120,7 @@ joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
 		'<div class="node">',
 		'<span class="label"></span>',
 		'<button class="delete">x</button>',
-		'<input type="text" class="name" placeholder="Text" />',
+		'<textarea class="name" placeholder="Text"></textarea>',
 		'</div>',
 	].join(''),
 
@@ -131,10 +131,10 @@ joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
 
 		this.$box = $(_.template(this.template)());
 		// Prevent paper from handling pointerdown.
-		this.$box.find('input').on('mousedown click', function(evt) { evt.stopPropagation(); });
+		this.$box.find('textarea, input').on('mousedown click', function(evt) { evt.stopPropagation(); });
 
 		// This is an example of reacting on the input change and storing the input data in the cell model.
-		this.$box.find('input.name').on('change', _.bind(function(evt)
+		this.$box.find('textarea.name, input.name').on('change', _.bind(function(evt)
 		{
 			this.model.set('name', $(evt.target).val());
 		}, this));
@@ -161,7 +161,7 @@ joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
 		// Set the position and dimension of the box so that it covers the JointJS element.
 		var bbox = this.model.getBBox();
 		// Example of updating the HTML with a data stored in the cell model.
-		var nameField = this.$box.find('input.name');
+		var nameField = this.$box.find('textarea.name, input.name');
 		if (!nameField.is(':focus'))
 			nameField.val(this.model.get('name'));
 		var label = this.$box.find('.label');
@@ -169,7 +169,7 @@ joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
 		label.text(type);
 		label.attr('class', 'label ' + type);
 		this.$box[0].classList.add(type);
-		this.$box.css({ width: bbox.width, height: bbox.height, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
+		this.$box.css({ width: bbox.width, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
 	},
 
 	removeBox: function(evt)
@@ -222,6 +222,7 @@ joint.shapes.dialogue.Text = joint.shapes.devs.Model.extend(
 	(
 		{
 			type: 'dialogue.Text',
+			size: { width: 350 },
 			inPorts: ['input'],
 			outPorts: ['output'],
 			attrs:
@@ -1048,3 +1049,14 @@ func.ts_data = function(nodes, name) {
 		return false;
 	}, false);
 })();
+
+function updateTextareas () {
+	window.requestAnimationFrame(updateTextareas);
+	let textareas = new Array(...document.getElementsByTagName("textarea"));
+	for (let i = 0; i < textareas.length; i++) {
+		let textarea = textareas[i];
+		textarea.style.height =  "0 px";
+		textarea.style.height = textarea.scrollHeight + "px";
+	}
+}
+window.requestAnimationFrame(updateTextareas);

+ 16 - 2
tools/dialogger/index.css

@@ -145,6 +145,7 @@ Node styles
 	padding: 8px;
 	box-sizing: border-box;
 	z-index: 2;
+	height: auto !important;
 }
 
 .node.StartNode {
@@ -190,7 +191,7 @@ Node styles
 .node.Choice {background: #00293f}
 .node .label.Choice {color: #5bc6ff}
 
-.node.Text {background: #e5e5e5}
+.node.Text {background: #e5e5e5; height: auto !important;}
 .node.Text input {background-color: #FFF; color: #000}
 .node .label.Text {color: #383838}
 
@@ -225,7 +226,7 @@ Node styles
 .node.GoToLabel input {background-color: #ffcdaa; color: #000}
 .node .GoToLabel.Tree {color: #ec7f85}
 
-.node input, button, select
+.node input, button, select, textarea
 {
 	/* Enable interacting with inputs only. */
 	pointer-events: auto;	
@@ -235,6 +236,15 @@ Node styles
 	color: #fff;
 }
 
+textarea {
+	display: block;
+	resize: none;
+	width: 100%;
+	overflow: hidden;
+	text-align: justify;
+	padding: 3px;
+}
+
 .node .label
 {
 	color: #ddd;
@@ -328,3 +338,7 @@ span
 {
     background-color: #000;
 }
+
+.editableName {
+	word-wrap: break-word;
+}

+ 151 - 1
tools/dialogger/lib/joint.min.css

@@ -5,4 +5,154 @@ This Source Code Form is subject to the terms of the Mozilla Public
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
-.viewport{-webkit-user-select:none;-moz-user-select:none;user-select:none}[magnet=true]:not(.element){cursor:crosshair}[magnet=true]:not(.element):hover{opacity:.7}.element{cursor:move}.element *{vector-effect:non-scaling-stroke;user-drag:none}.connection-wrap{fill:none;stroke:#000;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;opacity:0;cursor:move}.connection-wrap:hover{opacity:.4;stroke-opacity:.4}.connection{fill:none;stroke-linejoin:round}.marker-source,.marker-target{vector-effect:non-scaling-stroke}.marker-vertices{opacity:0;cursor:move}.marker-arrowheads{opacity:0;cursor:move;cursor:-webkit-grab;cursor:-moz-grab}.link-tools{opacity:0;cursor:pointer}.link-tools .tool-options{display:none}.link-tools .tool-remove circle{fill:red}.link-tools .tool-remove path{fill:#fff}.link:hover .marker-vertices,.link:hover .marker-arrowheads,.link:hover .link-tools{opacity:1}.marker-vertex{fill:#1ABC9C}.marker-vertex:hover{fill:#34495E;stroke:none}.marker-arrowhead{fill:#1ABC9C}.marker-arrowhead:hover{fill:#F39C12;stroke:none}.marker-vertex-remove{cursor:pointer;opacity:.1;fill:#fff}.marker-vertex-group:hover .marker-vertex-remove{opacity:1}.marker-vertex-remove-area{opacity:.1;cursor:pointer}.marker-vertex-group:hover .marker-vertex-remove-area{opacity:1}.highlighted{opacity:.7}text.highlighted{fill:red}@media screen and (-webkit-min-device-pixel-ratio:0){.highlighted{outline:2px solid red;opacity:initial}}.element .fobj{overflow:hidden}.element .fobj body{background-color:transparent;margin:0}.element .fobj div{text-align:center;vertical-align:middle;display:table-cell;padding:0 5px}
+
+.viewport {
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    user-select: none
+}
+
+[magnet=true]:not(.element) {
+    cursor: crosshair
+}
+
+[magnet=true]:not(.element):hover {
+    opacity: .7
+}
+
+.element {
+    cursor: move
+}
+
+.element * {
+    vector-effect: non-scaling-stroke;
+    user-drag: none
+}
+
+.connection-wrap {
+    fill: none;
+    //stroke: #000;
+    //stroke-width: 15;
+    stroke-linecap: round;
+    stroke-linejoin: round;
+    opacity: 0;
+    cursor: move
+}
+
+.connection-wrap:hover {
+    opacity: .4;
+    stroke-opacity: .4
+}
+
+.connection {
+    fill: none;
+    stroke-linejoin: round
+}
+
+.marker-source,
+.marker-target {
+    vector-effect: non-scaling-stroke
+}
+
+.marker-vertices {
+    opacity: 0;
+    cursor: move
+}
+
+.marker-arrowheads {
+    opacity: 0;
+    cursor: move;
+    cursor: -webkit-grab;
+    cursor: -moz-grab
+}
+
+.link-tools {
+    opacity: 0;
+    cursor: pointer
+}
+
+.link-tools .tool-options {
+    display: none
+}
+
+.link-tools .tool-remove circle {
+    fill: red
+}
+
+.link-tools .tool-remove path {
+    fill: #fff
+}
+
+.link:hover .marker-vertices,
+.link:hover .marker-arrowheads,
+.link:hover .link-tools {
+    opacity: 1
+}
+
+.marker-vertex {
+    fill: #1ABC9C
+}
+
+.marker-vertex:hover {
+    fill: #34495E;
+    stroke: none
+}
+
+.marker-arrowhead {
+    fill: #1ABC9C
+}
+
+.marker-arrowhead:hover {
+    fill: #F39C12;
+    stroke: none
+}
+
+.marker-vertex-remove {
+    cursor: pointer;
+    opacity: .1;
+    fill: #fff
+}
+
+.marker-vertex-group:hover .marker-vertex-remove {
+    opacity: 1
+}
+
+.marker-vertex-remove-area {
+    opacity: .1;
+    cursor: pointer
+}
+
+.marker-vertex-group:hover .marker-vertex-remove-area {
+    opacity: 1
+}
+
+.highlighted {
+    opacity: .7
+}
+
+text.highlighted {
+    fill: red
+}
+
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+    .highlighted {
+        outline: 2px solid red;
+        opacity: initial
+    }
+}
+
+.element .fobj {
+    overflow: hidden
+}
+
+.element .fobj body {
+    background-color: transparent;
+    margin: 0
+}
+
+.element .fobj div {
+    text-align: center;
+    vertical-align: middle;
+    display: table-cell;
+    padding: 0 5px
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff