├── .gitignore ├── .test └── run.sh ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── analytics_exception_setup.png ├── assets ├── appicons │ ├── icon_114.png │ ├── icon_120.png │ ├── icon_144.png │ ├── icon_152.png │ ├── icon_167.png │ ├── icon_180.png │ ├── icon_192.png │ ├── icon_36.png │ ├── icon_48.png │ ├── icon_512.png │ ├── icon_57.png │ ├── icon_72.png │ ├── icon_76.png │ └── icon_96.png ├── character │ ├── character.atlas │ ├── character.json │ ├── character.spinemodel │ ├── character.spinescene │ ├── images │ │ ├── eye_indifferent.png │ │ ├── eye_surprised.png │ │ ├── eyes.png │ │ ├── front_bracer.png │ │ ├── front_fist_closed.png │ │ ├── front_fist_open.png │ │ ├── front_foot.png │ │ ├── front_foot_bend1.png │ │ ├── front_foot_bend2.png │ │ ├── front_shin.png │ │ ├── front_thigh.png │ │ ├── front_upper_arm.png │ │ ├── goggles.png │ │ ├── gun.png │ │ ├── head.png │ │ ├── mouth_grind.png │ │ ├── mouth_oooo.png │ │ ├── mouth_smile.png │ │ ├── muzzle.png │ │ ├── neck.png │ │ ├── rear_bracer.png │ │ ├── rear_foot.png │ │ ├── rear_foot_bend1.png │ │ ├── rear_foot_bend2.png │ │ ├── rear_shin.png │ │ ├── rear_thigh.png │ │ ├── rear_upper_arm.png │ │ └── torso.png │ └── sounds │ │ ├── sound_1_footstep_left.wav │ │ └── sound_2_footstep_right.wav └── loadingscreens │ ├── screen_1024x768.png │ ├── screen_1242x2208.png │ ├── screen_1536x2048.png │ ├── screen_2048x1536.png │ ├── screen_2048x2732.png │ ├── screen_2208x1242.png │ ├── screen_2732x2048.png │ ├── screen_320x480.png │ ├── screen_640x1136.png │ ├── screen_640x960.png │ ├── screen_750x1334.png │ └── screen_768x1024.png ├── example ├── example.collection ├── example.gui └── example.gui_script ├── game.project ├── googleanalytics ├── ga.lua ├── internal │ ├── file.lua │ ├── json_encode.lua │ ├── queue.lua │ ├── user_agent.lua │ └── uuid.lua └── tracker.lua ├── input └── game.input_binding ├── logo.png └── tests ├── test_file.lua ├── test_ga.lua ├── test_queue.lua ├── test_tracker.lua ├── test_user_agent.lua ├── test_uuid.lua ├── tests.collection └── tests.script /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .externalToolBuilders 3 | .DS_Store 4 | .lock-wscript 5 | build 6 | *.pyc 7 | .project 8 | .cproject 9 | builtins 10 | .internal 11 | -------------------------------------------------------------------------------- /.test/run.sh: -------------------------------------------------------------------------------- 1 | 2 | if [ $# -eq 0 ]; then 3 | PLATFORM="x86_64-linux" 4 | else 5 | PLATFORM="$1" 6 | fi 7 | 8 | echo "${PLATFORM}" 9 | 10 | # {"version": "1.2.89", "sha1": "5ca3dd134cc960c35ecefe12f6dc81a48f212d40"} 11 | SHA1=$(curl -s http://d.defold.com/stable/info.json | sed 's/.*sha1": "\(.*\)".*/\1/') 12 | echo "Using Defold dmengine_headless version ${SHA1}" 13 | 14 | #DMENGINE_URL="http://d.defold.com/archive/${SHA1}/engine/linux/dmengine_headless" 15 | DMENGINE_URL="http://d.defold.com/archive/${SHA1}/engine/${PLATFORM}/dmengine_headless" 16 | BOB_URL="http://d.defold.com/archive/${SHA1}/bob/bob.jar" 17 | 18 | echo "Downloading ${DMENGINE_URL}" 19 | curl -o dmengine_headless ${DMENGINE_URL} 20 | chmod +x dmengine_headless 21 | 22 | echo "Downloading ${BOB_URL}" 23 | curl -o bob.jar ${BOB_URL} 24 | 25 | if [ -n "${DEFOLD_AUTH}" ] && [ -n "${DEFOLD_USER}" ]; then 26 | echo "Running bob.jar - resolving dependencies" 27 | java -jar bob.jar --auth "${DEFOLD_AUTH}" --email "${DEFOLD_USER}" resolve 28 | fi 29 | 30 | echo "Running bob.jar - building" 31 | java -jar bob.jar --debug build 32 | 33 | echo "Starting dmengine_headless" 34 | ./dmengine_headless 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | dist: bionic 4 | 5 | jdk: 6 | - oraclejdk11 7 | 8 | env: 9 | global: 10 | - DEFOLD_AUTH=foobar 11 | - DEFOLD_USER=bjorn.ritzl@gmail.com 12 | - DEFOLD_BOOSTRAP_COLLECTION=/test/test.collectionc 13 | 14 | 15 | script: 16 | - "./.test/run.sh" 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Defold Google Analytics 1.3.2 [britzl released 2017-11-23] 2 | FIX: Operator precedence fix for version tracking 3 | 4 | ## Defold Google Analytics 1.3.1 [britzl released 2017-08-24] 5 | Use project version default value if none exists in game.project 6 | 7 | ## Defold Google Analytics 1.3 [britzl released 2017-01-24] 8 | The library will no longer attempt to set a user-agent string on HTML5 builds since this is prevented by modern browsers (manifesting itself as a non-critical 'Refused to set unsafe header "User-Agent"' message). 9 | 10 | 11 | ## Defold Google Analytics 1.2 [britzl released 2016-12-20] 12 | Added user agent request header when posting analytics data. This will fix the issue with Real-Time data in the Google Analytics Dashboard always showing all visitors as Desktop. 13 | 14 | 15 | ## Defold Google Analytics 1.1 [britzl released 2016-12-19] 16 | Fixed issues when saving the queue on Android 17 | 18 | 19 | ## First public release [britzl released 2016-11-29] 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Björn Ritzl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](logo.png) 2 | 3 | [![Build Status](https://travis-ci.org/britzl/defold-googleanalytics.svg?branch=master)](https://travis-ci.org/britzl/defold-googleanalytics) 4 | 5 | # Google Analytics for Defold 6 | This is a Lua implementation of [Google Analytics](https://www.google.com/analytics) for the [Defold game engine](http://www.defold.com). The project is provided as a Defold library project for easy integration into Defold games. The implementation is loosely based on the design of the Google Analytics Android SDK, but with several simplifications thanks to the dynamic and flexible nature of Lua. 7 | 8 | This Lua implementation uses the [Google Analytics Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/v1/) to make direct calls to the Google Analytics servers. On top of these raw calls the implementation also adds support for offline tracking, automatic crash/exception reporting and automatic retrieval of relevant tracking parameters such as app name, app id, language, screen resolution and so on. 9 | 10 | ## Installation 11 | You can use Google Analytics in your own project by adding this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your game.project file and in the dependencies field under project add: 12 | 13 | https://github.com/britzl/defold-googleanalytics/archive/master.zip 14 | 15 | Or point to the ZIP file of a [specific release](https://github.com/britzl/defold-googleanalytics/releases). 16 | 17 | ## Configuration 18 | Before you can use Google Analytics in your project you need to add your analytics tracking ID to game.project (`*`). Open game.project as a text file and create a new section: 19 | 20 | [googleanalytics] 21 | tracking_id = UA-1234567-1 22 | 23 | Additional optional values are: 24 | 25 | [googleanalytics] 26 | dispatch_period = 1800 27 | queue_save_period = 60 28 | verbose = 1 29 | 30 | `dispatch_period` is the interval, in seconds, at which tracking data is sent to the server. 31 | 32 | `queue_save_period` is the minimum interval, in seconds, at which tracking data is saved to disk. 33 | 34 | `verbose` set to 1 will print some additional data about when and how many hits are sent to Google Analytics. Set to 0 or omit the value to not print anything. 35 | 36 | `*` = Google has changed the way you set up analytics and now make a clear distinction between mobile apps and websites. Mobile apps should use [Firebase Analytics](https://www.github.com/defold/extension-firebase-analytics) and websites should use Google Analytics. This is actually not strictly necessary and it is possible to use Google Analytics for a mobile apps and games. To get a tracking id you need to create a web property. 37 | 38 | ## Data processing latency 39 | Keep in mind that there is a data processing latency of one or two days which means that you will not be able to see the data you generate in your application straight away. You should be able to see data in the realtime view with only a very slight delay though. 40 | 41 | More information: https://support.google.com/analytics/answer/1070983?hl=en 42 | 43 | ## Usage 44 | Once you have added your tracking ID to game.project you're all set to start sending tracking data: 45 | 46 | local ga = require "googleanalytics.ga" 47 | 48 | function init(self) 49 | ga.get_default_tracker().screenview("my_cool_screen") 50 | end 51 | 52 | function update(self, dt) 53 | ga.update() 54 | end 55 | 56 | function on_input(self, action_id, action) 57 | if gui.pick_node(node1, action.x, action.y) and action.pressed then 58 | ga.get_default_tracker().event("category", "action") 59 | end 60 | 61 | if gui.pick_node(node2, action.x, action.y) and action.pressed then 62 | local time = socket.gettime() 63 | http.request("http://d.defold.com/stable/info.json", "GET", function(self, id, response) 64 | local millis = math.floor((socket.gettime() - time) * 1000) 65 | ga.get_default_tracker().timing("http", "get", millis) 66 | end) 67 | end 68 | end 69 | 70 | Note that all tracking arguments of type string will be automatically URL encoded by the library. 71 | 72 | ## Supported hit types 73 | This implementation supports the following hit types: 74 | 75 | * Event - `ga.get_default_tracker().event(category, action, label, value)` 76 | * Screen View - `ga.get_default_tracker().screenview(screen_name)` 77 | * Timing - `ga.get_default_tracker().timing(category, variable, time, label)` 78 | * Exception - `ga.get_default_tracker().exception(description, is_fatal)`, also see section on automatic crash/exception tracking 79 | 80 | You can also register a raw hit where you specify all parameters yourself: 81 | 82 | ga.get_default_tracker().raw("v=1&tid=UA-123456-1&cid=5555&t=pageview&dp=%2Fpage") 83 | 84 | A set of base parameters such as screen dimensions, uuid, application name etc are provided in `base_params` on the tracker instance. These can be useful when creating the params for a raw hit: 85 | 86 | print(ga.get_default_tracker().base_params) 87 | 88 | v=1&ds=app&cid=b80e6164-fc1f-4d76-cdae-dfb7e9a9507c&tid=UA-87977671-1&vp=1280x720&ul=en&an=Google_Analytics&aid=Google_AnalyticsDarwin&av=0.9 89 | 90 | ## Automatic crash/exception tracking 91 | You can let Google Analytics automatically send tracking data when your app crashes. The library can handle soft crashes (ie when your Lua code crashes) using [sys.set_error_handler](http://www.defold.com/ref/sys/#sys.set_error_handler:error_handler) and hard crashes (ie when the Defold engine crashes) using [crash API](http://www.defold.com/ref/crash/). Enable automatic crash tracking like this: 92 | 93 | local ga = require "googleanalytics.ga" 94 | 95 | function init(self) 96 | ga.get_default_tracker().enable_crash_reporting(true) 97 | end 98 | 99 | In order to see crashes in Google Analytics you need to [create a new Dashboard with a Table Widget](https://support.google.com/analytics/answer/1068218?hl=en) showing the `Exception Description` column and the `Exception` metric: 100 | 101 | ![](analytics_exception_setup.png) 102 | 103 | ## Third party tools and modules used 104 | The library uses the following modules: 105 | 106 | * [json.lua by rxi](https://github.com/rxi/json.lua) (MIT License) 107 | * [uuid.lua by Tieske](https://github.com/Tieske/uuid) (Apache 2.0) 108 | * [url_encode() from Lua String Recipes](http://lua-users.org/wiki/StringRecipes) 109 | 110 | The example project uses: 111 | 112 | * [Dirty Larry UI library](https://github.com/andsve/dirtylarry) 113 | * [Spineboy animation from the Spine animation tool](https://github.com/EsotericSoftware/spine-superspineboy). 114 | -------------------------------------------------------------------------------- /analytics_exception_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/analytics_exception_setup.png -------------------------------------------------------------------------------- /assets/appicons/icon_114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_114.png -------------------------------------------------------------------------------- /assets/appicons/icon_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_120.png -------------------------------------------------------------------------------- /assets/appicons/icon_144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_144.png -------------------------------------------------------------------------------- /assets/appicons/icon_152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_152.png -------------------------------------------------------------------------------- /assets/appicons/icon_167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_167.png -------------------------------------------------------------------------------- /assets/appicons/icon_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_180.png -------------------------------------------------------------------------------- /assets/appicons/icon_192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_192.png -------------------------------------------------------------------------------- /assets/appicons/icon_36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_36.png -------------------------------------------------------------------------------- /assets/appicons/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_48.png -------------------------------------------------------------------------------- /assets/appicons/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_512.png -------------------------------------------------------------------------------- /assets/appicons/icon_57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_57.png -------------------------------------------------------------------------------- /assets/appicons/icon_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_72.png -------------------------------------------------------------------------------- /assets/appicons/icon_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_76.png -------------------------------------------------------------------------------- /assets/appicons/icon_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/appicons/icon_96.png -------------------------------------------------------------------------------- /assets/character/character.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/assets/character/images/eye_indifferent.png" 3 | } 4 | images { 5 | image: "/assets/character/images/eye_surprised.png" 6 | } 7 | images { 8 | image: "/assets/character/images/eyes.png" 9 | } 10 | images { 11 | image: "/assets/character/images/front_bracer.png" 12 | } 13 | images { 14 | image: "/assets/character/images/front_fist_closed.png" 15 | } 16 | images { 17 | image: "/assets/character/images/front_fist_open.png" 18 | } 19 | images { 20 | image: "/assets/character/images/front_foot.png" 21 | } 22 | images { 23 | image: "/assets/character/images/front_foot_bend1.png" 24 | } 25 | images { 26 | image: "/assets/character/images/front_foot_bend2.png" 27 | } 28 | images { 29 | image: "/assets/character/images/front_shin.png" 30 | } 31 | images { 32 | image: "/assets/character/images/front_thigh.png" 33 | } 34 | images { 35 | image: "/assets/character/images/front_upper_arm.png" 36 | } 37 | images { 38 | image: "/assets/character/images/goggles.png" 39 | } 40 | images { 41 | image: "/assets/character/images/gun.png" 42 | } 43 | images { 44 | image: "/assets/character/images/head.png" 45 | } 46 | images { 47 | image: "/assets/character/images/mouth_grind.png" 48 | } 49 | images { 50 | image: "/assets/character/images/mouth_oooo.png" 51 | } 52 | images { 53 | image: "/assets/character/images/mouth_smile.png" 54 | } 55 | images { 56 | image: "/assets/character/images/muzzle.png" 57 | } 58 | images { 59 | image: "/assets/character/images/neck.png" 60 | } 61 | images { 62 | image: "/assets/character/images/rear_bracer.png" 63 | } 64 | images { 65 | image: "/assets/character/images/rear_foot.png" 66 | } 67 | images { 68 | image: "/assets/character/images/rear_foot_bend1.png" 69 | } 70 | images { 71 | image: "/assets/character/images/rear_foot_bend2.png" 72 | } 73 | images { 74 | image: "/assets/character/images/rear_shin.png" 75 | } 76 | images { 77 | image: "/assets/character/images/rear_thigh.png" 78 | } 79 | images { 80 | image: "/assets/character/images/rear_upper_arm.png" 81 | } 82 | images { 83 | image: "/assets/character/images/torso.png" 84 | } 85 | margin: 0 86 | extrude_borders: 2 87 | inner_padding: 0 88 | -------------------------------------------------------------------------------- /assets/character/character.json: -------------------------------------------------------------------------------- 1 | { 2 | "bones": [ 3 | { "name": "hip", "y": 247.47 }, 4 | { "name": "front_thigh", "parent": "hip", "length": 74.8, "x": -17.45, "y": -11.64, "rotation": -95.51, "color": "00ff04ff" }, 5 | { "name": "rear_thigh", "parent": "hip", "length": 85.71, "x": 8.91, "y": -5.62, "rotation": -72.54, "color": "ff000dff" }, 6 | { "name": "torso", "parent": "hip", "length": 127.55, "x": -1.61, "y": 4.9, "rotation": 103.82, "color": "e0da19ff" }, 7 | { 8 | "name": "front_shin", 9 | "parent": "front_thigh", 10 | "length": 128.76, 11 | "x": 78.69, 12 | "y": 1.6, 13 | "rotation": -2.21, 14 | "inheritScale": false, 15 | "color": "00ff04ff" 16 | }, 17 | { "name": "front_upper_arm", "parent": "torso", "length": 69.45, "x": 103.75, "y": 19.32, "rotation": 168.37, "color": "00ff04ff" }, 18 | { "name": "neck", "parent": "torso", "length": 25.45, "x": 127.49, "y": -0.3, "rotation": -31.53, "color": "e0da19ff" }, 19 | { "name": "rear_shin", "parent": "rear_thigh", "length": 121.87, "x": 86.1, "y": -1.32, "rotation": -19.83, "color": "ff000dff" }, 20 | { "name": "rear_upper_arm", "parent": "torso", "length": 51.93, "x": 92.35, "y": -19.22, "rotation": -169.55, "color": "ff000dff" }, 21 | { 22 | "name": "front_bracer", 23 | "parent": "front_upper_arm", 24 | "length": 40.57, 25 | "x": 68.8, 26 | "y": -0.68, 27 | "rotation": 18.29, 28 | "color": "00ff04ff" 29 | }, 30 | { "name": "front_foot", "parent": "front_shin", "length": 91.34, "x": 128.75, "y": -0.33, "rotation": 77.9, "color": "00ff04ff" }, 31 | { "name": "head", "parent": "neck", "length": 263.57, "x": 27.66, "y": -0.25, "rotation": 23.18, "color": "e0da19ff" }, 32 | { "name": "rear_bracer", "parent": "rear_upper_arm", "length": 34.55, "x": 51.35, "rotation": 23.15, "color": "ff000dff" }, 33 | { "name": "rear_foot", "parent": "rear_shin", "length": 82.57, "x": 121.45, "y": -0.75, "rotation": 69.3, "color": "ff000dff" }, 34 | { "name": "front_fist", "parent": "front_bracer", "length": 65.38, "x": 40.56, "y": 0.19, "rotation": 12.43, "color": "00ff04ff" }, 35 | { "name": "gun", "parent": "rear_bracer", "length": 43.1, "x": 34.42, "y": -0.45, "rotation": 5.34, "color": "ff000dff" }, 36 | { "name": "gunTip", "parent": "gun", "x": 201.04, "y": 52.13, "rotation": 6.83, "color": "ff000dff" } 37 | ], 38 | "slots": [ 39 | { "name": "rear_upper_arm", "bone": "rear_upper_arm", "attachment": "rear_upper_arm" }, 40 | { "name": "rear_bracer", "bone": "rear_bracer", "attachment": "rear_bracer" }, 41 | { "name": "gun", "bone": "gun", "attachment": "gun" }, 42 | { "name": "rear_foot", "bone": "rear_foot", "attachment": "rear_foot" }, 43 | { "name": "rear_thigh", "bone": "rear_thigh", "attachment": "rear_thigh" }, 44 | { "name": "rear_shin", "bone": "rear_shin", "attachment": "rear_shin" }, 45 | { "name": "neck", "bone": "neck", "attachment": "neck" }, 46 | { "name": "torso", "bone": "torso", "attachment": "torso" }, 47 | { "name": "front_upper_arm", "bone": "front_upper_arm", "attachment": "front_upper_arm" }, 48 | { "name": "head", "bone": "head", "attachment": "head" }, 49 | { "name": "eye", "bone": "head", "attachment": "eye_indifferent" }, 50 | { "name": "front_thigh", "bone": "front_thigh", "attachment": "front_thigh" }, 51 | { "name": "front_foot", "bone": "front_foot", "attachment": "front_foot" }, 52 | { "name": "front_shin", "bone": "front_shin", "attachment": "front_shin" }, 53 | { "name": "mouth", "bone": "head", "attachment": "mouth_smile" }, 54 | { "name": "goggles", "bone": "head", "attachment": "goggles" }, 55 | { "name": "front_bracer", "bone": "front_bracer", "attachment": "front_bracer" }, 56 | { "name": "front_fist", "bone": "front_fist", "attachment": "front_fist_closed" }, 57 | { "name": "muzzle", "bone": "gunTip", "additive": true } 58 | ], 59 | "skins": { 60 | "default": { 61 | "eye": { 62 | "eye_indifferent": { "x": 85.72, "y": -28.18, "rotation": -70.63, "width": 93, "height": 89 }, 63 | "eye_surprised": { "x": 85.72, "y": -28.18, "rotation": -70.63, "width": 93, "height": 89 } 64 | }, 65 | "front_bracer": { 66 | "front_bracer": { "x": 12.03, "y": -1.67, "rotation": 79.59, "width": 58, "height": 80 } 67 | }, 68 | "front_fist": { 69 | "front_fist_closed": { "x": 35.49, "y": 6, "rotation": 67.16, "width": 75, "height": 82 }, 70 | "front_fist_open": { "x": 39.56, "y": 7.76, "rotation": 67.16, "width": 86, "height": 87 } 71 | }, 72 | "front_foot": { 73 | "front_foot": { "x": 29.51, "y": 7.83, "rotation": 18.68, "width": 126, "height": 69 }, 74 | "front_foot_bend1": { "x": 29.51, "y": 7.83, "rotation": 18.68, "width": 128, "height": 70 }, 75 | "front_foot_bend2": { "x": 16.07, "y": 13.83, "rotation": 18.68, "width": 108, "height": 93 } 76 | }, 77 | "front_shin": { 78 | "front_shin": { "x": 55.11, "y": -3.54, "rotation": 96.59, "width": 82, "height": 184 } 79 | }, 80 | "front_thigh": { 81 | "front_thigh": { "x": 42.47, "y": 4.44, "rotation": 84.86, "width": 48, "height": 112 } 82 | }, 83 | "front_upper_arm": { 84 | "front_upper_arm": { "x": 28.3, "y": 7.37, "rotation": 97.89, "width": 54, "height": 97 } 85 | }, 86 | "goggles": { 87 | "goggles": { "x": 97.07, "y": 6.54, "rotation": -70.63, "width": 261, "height": 166 } 88 | }, 89 | "gun": { 90 | "gun": { "x": 77.3, "y": 16.4, "rotation": 60.82, "width": 210, "height": 203 } 91 | }, 92 | "head": { 93 | "head": { "x": 128.95, "y": 0.29, "rotation": -70.63, "width": 271, "height": 298 } 94 | }, 95 | "mouth": { 96 | "mouth_grind": { "x": 23.68, "y": -32.23, "rotation": -70.63, "width": 93, "height": 59 }, 97 | "mouth_oooo": { "x": 23.68, "y": -32.23, "rotation": -70.63, "width": 93, "height": 59 }, 98 | "mouth_smile": { "x": 23.68, "y": -32.23, "rotation": -70.63, "width": 93, "height": 59 } 99 | }, 100 | "muzzle": { 101 | "muzzle": { "x": 18.25, "y": 5.44, "rotation": 0.15, "width": 462, "height": 400 } 102 | }, 103 | "neck": { 104 | "neck": { "x": 9.76, "y": -3.01, "rotation": -55.22, "width": 36, "height": 41 } 105 | }, 106 | "rear_bracer": { 107 | "rear_bracer": { "x": 11.15, "y": -2.2, "rotation": 66.17, "width": 56, "height": 72 } 108 | }, 109 | "rear_foot": { 110 | "rear_foot": { "x": 31.51, "y": 3.57, "rotation": 23.07, "width": 113, "height": 60 }, 111 | "rear_foot_bend1": { "x": 34.39, "y": 4.8, "rotation": 23.07, "width": 117, "height": 66 }, 112 | "rear_foot_bend2": { "x": 30.38, "y": 12.62, "rotation": 23.07, "width": 103, "height": 83 } 113 | }, 114 | "rear_shin": { 115 | "rear_shin": { "x": 58.29, "y": -2.75, "rotation": 92.37, "width": 75, "height": 178 } 116 | }, 117 | "rear_thigh": { 118 | "rear_thigh": { "x": 33.1, "y": -4.11, "rotation": 72.54, "width": 65, "height": 104 } 119 | }, 120 | "rear_upper_arm": { 121 | "rear_upper_arm": { "x": 21.12, "y": 4.08, "rotation": 89.32, "width": 47, "height": 87 } 122 | }, 123 | "torso": { 124 | "torso": { "x": 63.61, "y": 7.12, "rotation": -94.53, "width": 98, "height": 180 } 125 | } 126 | } 127 | }, 128 | "events": { 129 | "footstep": {}, 130 | "headAttach": { "int": 3, "float": 4 }, 131 | "headBehind": { "int": 5, "float": 6, "string": "setup" }, 132 | "headPop": { "int": 1, "float": 2 } 133 | }, 134 | "animations": { 135 | "death": { 136 | "slots": { 137 | "eye": { 138 | "attachment": [ 139 | { "time": 0, "name": "eye_surprised" }, 140 | { "time": 0.4666, "name": "eye_indifferent" }, 141 | { "time": 2.2333, "name": "eye_surprised" }, 142 | { "time": 4.5333, "name": "eye_indifferent" } 143 | ] 144 | }, 145 | "front_fist": { 146 | "attachment": [ 147 | { "time": 0, "name": "front_fist_open" } 148 | ] 149 | }, 150 | "mouth": { 151 | "attachment": [ 152 | { "time": 0, "name": "mouth_oooo" }, 153 | { "time": 2.2333, "name": "mouth_grind" }, 154 | { "time": 4.5333, "name": "mouth_oooo" } 155 | ] 156 | } 157 | }, 158 | "bones": { 159 | "head": { 160 | "rotate": [ 161 | { "time": 0, "angle": -2.82 }, 162 | { "time": 0.1333, "angle": -28.74 }, 163 | { "time": 0.2333, "angle": 11.42 }, 164 | { "time": 0.3333, "angle": -50.24 }, 165 | { "time": 0.4, "angle": -72.66, "curve": "stepped" }, 166 | { "time": 0.4333, "angle": -72.66 }, 167 | { "time": 0.5, "angle": -20.24 }, 168 | { "time": 0.5666, "angle": -85.28, "curve": "stepped" }, 169 | { "time": 0.9333, "angle": -85.28, "curve": "stepped" }, 170 | { "time": 2.2333, "angle": -85.28 }, 171 | { "time": 2.5, "angle": -51.96, "curve": "stepped" }, 172 | { "time": 4.5333, "angle": -51.96 }, 173 | { "time": 4.6666, "angle": -85.28 } 174 | ], 175 | "translate": [ 176 | { "time": 0, "x": 0, "y": 0 } 177 | ], 178 | "scale": [ 179 | { "time": 0, "x": 1, "y": 1 } 180 | ] 181 | }, 182 | "neck": { 183 | "rotate": [ 184 | { "time": 0, "angle": -2.82 }, 185 | { "time": 0.1333, "angle": 12.35 }, 186 | { "time": 0.2333, "angle": 29.89 }, 187 | { "time": 0.3, "angle": 70.36 }, 188 | { "time": 0.4, "angle": -10.22, "curve": "stepped" }, 189 | { "time": 0.4333, "angle": -10.22 }, 190 | { "time": 0.5, "angle": 2.92 }, 191 | { "time": 0.5666, "angle": 47.94, "curve": "stepped" }, 192 | { "time": 2.2333, "angle": 47.94 }, 193 | { "time": 2.5, "angle": 18.5, "curve": "stepped" }, 194 | { "time": 4.5333, "angle": 18.5 }, 195 | { "time": 4.6666, "angle": 47.94 } 196 | ], 197 | "translate": [ 198 | { "time": 0, "x": 0, "y": 0 } 199 | ], 200 | "scale": [ 201 | { "time": 0, "x": 1, "y": 1 } 202 | ] 203 | }, 204 | "torso": { 205 | "rotate": [ 206 | { "time": 0, "angle": -8.61 }, 207 | { "time": 0.1333, "angle": 28.19 }, 208 | { "time": 0.2666, "angle": -280.19 }, 209 | { "time": 0.4, "angle": -237.22, "curve": "stepped" }, 210 | { "time": 0.4333, "angle": -237.22 }, 211 | { "time": 0.5, "angle": 76.03, "curve": "stepped" }, 212 | { "time": 0.8, "angle": 76.03, "curve": "stepped" }, 213 | { "time": 0.9333, "angle": 76.03, "curve": "stepped" }, 214 | { "time": 2.2333, "angle": 76.03 } 215 | ], 216 | "translate": [ 217 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 218 | { "time": 0.9333, "x": 0, "y": 0, "curve": "stepped" }, 219 | { "time": 2.2333, "x": 0, "y": 0 } 220 | ], 221 | "scale": [ 222 | { "time": 0, "x": 1, "y": 1 } 223 | ] 224 | }, 225 | "front_upper_arm": { 226 | "rotate": [ 227 | { "time": 0, "angle": -38.85 }, 228 | { "time": 0.1333, "angle": -299.58 }, 229 | { "time": 0.2666, "angle": -244.74 }, 230 | { "time": 0.4, "angle": -292.35 }, 231 | { "time": 0.4333, "angle": -315.84 }, 232 | { "time": 0.5, "angle": -347.94 }, 233 | { "time": 0.7, "angle": -347.33, "curve": "stepped" }, 234 | { "time": 2.2333, "angle": -347.33 }, 235 | { "time": 2.7, "angle": -290.68 }, 236 | { "time": 2.7666, "angle": -285.1 }, 237 | { "time": 4.6666, "angle": -290.68 }, 238 | { "time": 4.8, "angle": 8.61 }, 239 | { "time": 4.8666, "angle": 10.94 } 240 | ], 241 | "translate": [ 242 | { "time": 0, "x": 0, "y": 0 } 243 | ], 244 | "scale": [ 245 | { "time": 0, "x": 1, "y": 1 } 246 | ] 247 | }, 248 | "rear_upper_arm": { 249 | "rotate": [ 250 | { "time": 0, "angle": -44.69 }, 251 | { "time": 0.1333, "angle": 112.26 }, 252 | { "time": 0.2666, "angle": 129.07 }, 253 | { "time": 0.4, "angle": 134.94, "curve": "stepped" }, 254 | { "time": 0.4333, "angle": 134.94 }, 255 | { "time": 0.5666, "angle": 172.6, "curve": "stepped" }, 256 | { "time": 0.9333, "angle": 172.6, "curve": "stepped" }, 257 | { "time": 2.2333, "angle": 172.6 } 258 | ], 259 | "translate": [ 260 | { "time": 0, "x": 0, "y": 0 } 261 | ], 262 | "scale": [ 263 | { "time": 0, "x": 1, "y": 1 } 264 | ] 265 | }, 266 | "front_bracer": { 267 | "rotate": [ 268 | { "time": 0, "angle": 21.88 }, 269 | { "time": 0.1333, "angle": 11.48 }, 270 | { "time": 0.2666, "angle": -18.81 }, 271 | { "time": 0.4, "angle": -18.92 }, 272 | { "time": 0.4333, "angle": -18.28 }, 273 | { "time": 0.5, "angle": 60.61 }, 274 | { "time": 0.7, "angle": -18.87, "curve": "stepped" }, 275 | { "time": 2.2333, "angle": -18.87 }, 276 | { "time": 2.7, "angle": -1.95, "curve": "stepped" }, 277 | { "time": 4.6666, "angle": -1.95 }, 278 | { "time": 4.8, "angle": 34.55 }, 279 | { "time": 4.9333, "angle": -18.74 } 280 | ], 281 | "translate": [ 282 | { "time": 0, "x": 0, "y": 0 } 283 | ], 284 | "scale": [ 285 | { "time": 0, "x": 1, "y": 1 } 286 | ] 287 | }, 288 | "front_fist": { 289 | "rotate": [ 290 | { "time": 0, "angle": -2.33 }, 291 | { "time": 0.2666, "angle": 26.34 }, 292 | { "time": 0.7, "angle": -6.07, "curve": "stepped" }, 293 | { "time": 2.2333, "angle": -6.07 }, 294 | { "time": 2.7, "angle": 5.72, "curve": "stepped" }, 295 | { "time": 4.6666, "angle": 5.72 }, 296 | { "time": 4.8666, "angle": -6.52 } 297 | ], 298 | "translate": [ 299 | { "time": 0, "x": 0, "y": 0 } 300 | ], 301 | "scale": [ 302 | { "time": 0, "x": 1, "y": 1 } 303 | ] 304 | }, 305 | "rear_bracer": { 306 | "rotate": [ 307 | { "time": 0, "angle": 10.36 }, 308 | { "time": 0.1333, "angle": -23.12 }, 309 | { "time": 0.2666, "angle": -23.11 }, 310 | { "time": 0.4, "angle": -23.16, "curve": "stepped" }, 311 | { "time": 0.4333, "angle": -23.16 }, 312 | { "time": 0.5666, "angle": -23.2, "curve": "stepped" }, 313 | { "time": 0.9333, "angle": -23.2, "curve": "stepped" }, 314 | { "time": 2.2333, "angle": -23.2 } 315 | ], 316 | "translate": [ 317 | { "time": 0, "x": 0, "y": 0 } 318 | ], 319 | "scale": [ 320 | { "time": 0, "x": 1, "y": 1 } 321 | ] 322 | }, 323 | "gun": { 324 | "rotate": [ 325 | { "time": 0, "angle": -2.78 }, 326 | { "time": 0.1333, "angle": -24.58 } 327 | ], 328 | "translate": [ 329 | { "time": 0, "x": 0, "y": 0 } 330 | ], 331 | "scale": [ 332 | { "time": 0, "x": 1, "y": 1 } 333 | ] 334 | }, 335 | "hip": { 336 | "rotate": [ 337 | { "time": 0, "angle": 0, "curve": "stepped" }, 338 | { "time": 0.9333, "angle": 0, "curve": "stepped" }, 339 | { "time": 2.2333, "angle": 0 } 340 | ], 341 | "translate": [ 342 | { "time": 0, "x": 0, "y": 0 }, 343 | { "time": 0.2, "x": 50.34, "y": 151.73 }, 344 | { "time": 0.4, "x": 5.16, "y": -119.64, "curve": "stepped" }, 345 | { "time": 0.4333, "x": 5.16, "y": -119.64 }, 346 | { "time": 0.5, "x": 50.34, "y": -205.18, "curve": "stepped" }, 347 | { "time": 0.8, "x": 50.34, "y": -205.18, "curve": "stepped" }, 348 | { "time": 0.9333, "x": 50.34, "y": -205.18, "curve": "stepped" }, 349 | { "time": 2.2333, "x": 50.34, "y": -205.18 } 350 | ], 351 | "scale": [ 352 | { "time": 0, "x": 1, "y": 1 } 353 | ] 354 | }, 355 | "front_thigh": { 356 | "rotate": [ 357 | { "time": 0, "angle": 0 }, 358 | { "time": 0.1333, "angle": 8.47 }, 359 | { "time": 0.2666, "angle": 115.95 }, 360 | { "time": 0.4, "angle": 180.66, "curve": "stepped" }, 361 | { "time": 0.4333, "angle": 180.66 }, 362 | { "time": 0.5, "angle": 155.22 }, 363 | { "time": 0.6, "angle": 97.73 } 364 | ], 365 | "translate": [ 366 | { "time": 0, "x": 0, "y": 0 } 367 | ], 368 | "scale": [ 369 | { "time": 0, "x": 1, "y": 1 } 370 | ] 371 | }, 372 | "front_shin": { 373 | "rotate": [ 374 | { "time": 0, "angle": 0 }, 375 | { "time": 0.1333, "angle": -27.37 }, 376 | { "time": 0.2666, "angle": -35.1 }, 377 | { "time": 0.4, "angle": -37.72, "curve": "stepped" }, 378 | { "time": 0.4333, "angle": -37.72 }, 379 | { "time": 0.5, "angle": -40.06 }, 380 | { "time": 0.6, "angle": 2.76 } 381 | ], 382 | "translate": [ 383 | { "time": 0, "x": 0, "y": 0 } 384 | ], 385 | "scale": [ 386 | { "time": 0, "x": 1, "y": 1 } 387 | ] 388 | }, 389 | "rear_thigh": { 390 | "rotate": [ 391 | { "time": 0, "angle": 0 }, 392 | { "time": 0.1333, "angle": 70.45 }, 393 | { "time": 0.2666, "angle": 155.34 }, 394 | { "time": 0.4, "angle": 214.31, "curve": "stepped" }, 395 | { "time": 0.4333, "angle": 214.31 }, 396 | { "time": 0.5, "angle": 169.67 }, 397 | { "time": 0.8, "angle": 83.27 } 398 | ], 399 | "translate": [ 400 | { "time": 0, "x": 0, "y": 0 } 401 | ], 402 | "scale": [ 403 | { "time": 0, "x": 1, "y": 1 } 404 | ] 405 | }, 406 | "rear_shin": { 407 | "rotate": [ 408 | { "time": 0, "angle": 0 }, 409 | { "time": 0.1333, "angle": 18.93 }, 410 | { "time": 0.2666, "angle": -21.04 }, 411 | { "time": 0.4, "angle": -29.93, "curve": "stepped" }, 412 | { "time": 0.4333, "angle": -29.93 }, 413 | { "time": 0.5, "angle": -16.79 }, 414 | { "time": 0.8, "angle": 7.77 } 415 | ], 416 | "translate": [ 417 | { "time": 0, "x": 0, "y": 0 } 418 | ], 419 | "scale": [ 420 | { "time": 0, "x": 1, "y": 1 } 421 | ] 422 | }, 423 | "rear_foot": { 424 | "rotate": [ 425 | { "time": 0, "angle": 0 }, 426 | { "time": 0.1333, "angle": -11.62 }, 427 | { "time": 0.4, "angle": -45.59, "curve": "stepped" }, 428 | { "time": 0.4333, "angle": -45.59 } 429 | ], 430 | "translate": [ 431 | { "time": 0, "x": 0, "y": 0 } 432 | ], 433 | "scale": [ 434 | { "time": 0, "x": 1, "y": 1 } 435 | ] 436 | }, 437 | "front_foot": { 438 | "rotate": [ 439 | { "time": 0, "angle": 0 }, 440 | { "time": 0.4, "angle": -48.75, "curve": "stepped" }, 441 | { "time": 0.4333, "angle": -48.75 } 442 | ], 443 | "translate": [ 444 | { "time": 0, "x": 0, "y": 0 } 445 | ], 446 | "scale": [ 447 | { "time": 0, "x": 1, "y": 1 } 448 | ] 449 | }, 450 | "gunTip": { 451 | "rotate": [ 452 | { "time": 0, "angle": 0 } 453 | ], 454 | "translate": [ 455 | { "time": 0, "x": 0, "y": 0 } 456 | ], 457 | "scale": [ 458 | { "time": 0, "x": 1, "y": 1 } 459 | ] 460 | } 461 | } 462 | }, 463 | "hit": { 464 | "slots": { 465 | "front_fist": { 466 | "attachment": [ 467 | { "time": 0.1666, "name": "front_fist_open" } 468 | ] 469 | }, 470 | "mouth": { 471 | "attachment": [ 472 | { "time": 0, "name": "mouth_grind" }, 473 | { "time": 0.3333, "name": "mouth_smile" } 474 | ] 475 | } 476 | }, 477 | "bones": { 478 | "torso": { 479 | "rotate": [ 480 | { "time": 0, "angle": 56.42 }, 481 | { "time": 0.3333, "angle": 8.89 } 482 | ] 483 | }, 484 | "neck": { 485 | "rotate": [ 486 | { "time": 0, "angle": 35.38 }, 487 | { "time": 0.2333, "angle": 24.94 } 488 | ] 489 | }, 490 | "head": { 491 | "rotate": [ 492 | { "time": 0, "angle": 10.21 }, 493 | { "time": 0.3333, "angle": -41.3 } 494 | ] 495 | }, 496 | "front_upper_arm": { 497 | "rotate": [ 498 | { 499 | "time": 0, 500 | "angle": -310.92, 501 | "curve": [ 0.38, 0.53, 0.744, 1 ] 502 | }, 503 | { "time": 0.3333, "angle": -112.59 } 504 | ], 505 | "translate": [ 506 | { "time": 0, "x": 7.23, "y": -13.13 } 507 | ] 508 | }, 509 | "front_bracer": { 510 | "rotate": [ 511 | { "time": 0, "angle": 36.99 }, 512 | { "time": 0.3333, "angle": -28.64 } 513 | ] 514 | }, 515 | "front_fist": { 516 | "rotate": [ 517 | { "time": 0, "angle": 13.59 }, 518 | { "time": 0.3333, "angle": 7.55 } 519 | ] 520 | }, 521 | "rear_upper_arm": { 522 | "rotate": [ 523 | { 524 | "time": 0, 525 | "angle": 271.02, 526 | "curve": [ 0.342, 0.36, 0.68, 0.71 ] 527 | }, 528 | { "time": 0.3333, "angle": -15.84 } 529 | ], 530 | "translate": [ 531 | { "time": 0.3333, "x": -0.09, "y": -0.46 } 532 | ] 533 | }, 534 | "rear_bracer": { 535 | "rotate": [ 536 | { "time": 0, "angle": 0 }, 537 | { "time": 0.3333, "angle": 40.03 } 538 | ] 539 | }, 540 | "gun": { 541 | "rotate": [ 542 | { "time": 0, "angle": 14.98 }, 543 | { "time": 0.3333, "angle": 39.75 } 544 | ] 545 | }, 546 | "hip": { 547 | "translate": [ 548 | { "time": 0, "x": -75.54, "y": -78.03 }, 549 | { "time": 0.2333, "x": -36.48, "y": 12.42 }, 550 | { "time": 0.3333, "x": -36.48, "y": -2.99 } 551 | ] 552 | }, 553 | "front_thigh": { 554 | "rotate": [ 555 | { 556 | "time": 0, 557 | "angle": 90.94, 558 | "curve": [ 0.227, 0.26, 0.432, 1 ] 559 | }, 560 | { "time": 0.3333, "angle": 32.02 } 561 | ], 562 | "translate": [ 563 | { "time": 0, "x": 7.21, "y": -4 } 564 | ] 565 | }, 566 | "rear_thigh": { 567 | "rotate": [ 568 | { 569 | "time": 0, 570 | "angle": 40.51, 571 | "curve": [ 0.295, 0.3, 0.59, 0.99 ] 572 | }, 573 | { "time": 0.3333, "angle": 90.76 } 574 | ], 575 | "translate": [ 576 | { "time": 0, "x": -1.96, "y": -0.32 } 577 | ] 578 | }, 579 | "front_shin": { 580 | "rotate": [ 581 | { "time": 0, "angle": -96.62 }, 582 | { "time": 0.3333, "angle": -15.13 } 583 | ] 584 | }, 585 | "rear_shin": { 586 | "rotate": [ 587 | { "time": 0, "angle": 7.99 }, 588 | { "time": 0.3333, "angle": -67.54 } 589 | ], 590 | "scale": [ 591 | { "time": 0, "x": 1, "y": 1 } 592 | ] 593 | }, 594 | "front_foot": { 595 | "rotate": [ 596 | { "time": 0, "angle": 5.4 }, 597 | { "time": 0.3333, "angle": -16.26 } 598 | ], 599 | "translate": [ 600 | { "time": 0, "x": 0, "y": 0 } 601 | ], 602 | "scale": [ 603 | { "time": 0, "x": 1, "y": 1 } 604 | ] 605 | }, 606 | "rear_foot": { 607 | "rotate": [ 608 | { "time": 0, "angle": 2.67 }, 609 | { "time": 0.3333, "angle": -10.31 } 610 | ] 611 | } 612 | } 613 | }, 614 | "idle": { 615 | "slots": { 616 | "front_fist": { 617 | "attachment": [ 618 | { "time": 0, "name": "front_fist_open" }, 619 | { "time": 1.6666, "name": "front_fist_open" } 620 | ] 621 | }, 622 | "mouth": { 623 | "attachment": [ 624 | { "time": 0, "name": "mouth_smile" }, 625 | { "time": 1.6666, "name": "mouth_smile" } 626 | ] 627 | } 628 | }, 629 | "bones": { 630 | "torso": { 631 | "rotate": [ 632 | { 633 | "time": 0, 634 | "angle": -5.61, 635 | "curve": [ 0.25, 0, 0.75, 1 ] 636 | }, 637 | { 638 | "time": 0.8333, 639 | "angle": -9.65, 640 | "curve": [ 0.25, 0, 0.75, 1 ] 641 | }, 642 | { "time": 1.6666, "angle": -5.61 } 643 | ], 644 | "translate": [ 645 | { "time": 0, "x": -6.49, "y": 0 } 646 | ], 647 | "scale": [ 648 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 649 | { "time": 1.6666, "x": 1, "y": 1 } 650 | ] 651 | }, 652 | "front_upper_arm": { 653 | "rotate": [ 654 | { 655 | "time": 0, 656 | "angle": -59.85, 657 | "curve": [ 0.492, 0, 0.75, 1 ] 658 | }, 659 | { 660 | "time": 0.6666, 661 | "angle": -54.31, 662 | "curve": [ 0.324, 0.11, 0.75, 1 ] 663 | }, 664 | { "time": 1.6666, "angle": -59.85 } 665 | ], 666 | "translate": [ 667 | { "time": 0, "x": -7.12, "y": -8.23 }, 668 | { "time": 0.6666, "x": -6.32, "y": -8.3 }, 669 | { "time": 1.6666, "x": -7.12, "y": -8.23 } 670 | ], 671 | "scale": [ 672 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 673 | { "time": 1.6666, "x": 1, "y": 1 } 674 | ] 675 | }, 676 | "rear_upper_arm": { 677 | "rotate": [ 678 | { 679 | "time": 0, 680 | "angle": 62.41, 681 | "curve": [ 0.504, 0.02, 0.75, 1 ] 682 | }, 683 | { 684 | "time": 0.7333, 685 | "angle": 43.83, 686 | "curve": [ 0.25, 0, 0.75, 1 ] 687 | }, 688 | { "time": 1.6666, "angle": 62.41 } 689 | ], 690 | "translate": [ 691 | { "time": 0, "x": -1.83, "y": -16.78 }, 692 | { "time": 0.6666, "x": 0.34, "y": -15.23 }, 693 | { "time": 1.6666, "x": -1.83, "y": -16.78 } 694 | ], 695 | "scale": [ 696 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 697 | { "time": 1.6666, "x": 1, "y": 1 } 698 | ] 699 | }, 700 | "neck": { 701 | "rotate": [ 702 | { "time": 0, "angle": 0 }, 703 | { "time": 0.6666, "angle": 2.39 }, 704 | { "time": 1.6666, "angle": 0 } 705 | ], 706 | "translate": [ 707 | { "time": 0, "x": -1.88, "y": -4.76, "curve": "stepped" }, 708 | { "time": 1.6666, "x": -1.88, "y": -4.76 } 709 | ], 710 | "scale": [ 711 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 712 | { "time": 1.6666, "x": 1, "y": 1 } 713 | ] 714 | }, 715 | "front_thigh": { 716 | "rotate": [ 717 | { 718 | "time": 0, 719 | "angle": 0.64, 720 | "curve": [ 0.235, 0, 0.558, 0.99 ] 721 | }, 722 | { 723 | "time": 0.6666, 724 | "angle": -4.34, 725 | "curve": [ 0.594, 0, 0.653, 1 ] 726 | }, 727 | { "time": 1.6666, "angle": 0.64 } 728 | ], 729 | "translate": [ 730 | { "time": 0, "x": -13.39, "y": 6.69, "curve": "stepped" }, 731 | { "time": 1.6666, "x": -13.39, "y": 6.69 } 732 | ], 733 | "scale": [ 734 | { 735 | "time": 0, 736 | "x": 0.896, 737 | "y": 1, 738 | "curve": [ 0.235, 0, 0.558, 0.99 ] 739 | }, 740 | { 741 | "time": 0.6666, 742 | "x": 0.825, 743 | "y": 1, 744 | "curve": [ 0.594, 0, 0.653, 1 ] 745 | }, 746 | { "time": 1.6666, "x": 0.896, "y": 1 } 747 | ] 748 | }, 749 | "front_shin": { 750 | "rotate": [ 751 | { "time": 0, "angle": -19.28, "curve": "stepped" }, 752 | { "time": 1.6666, "angle": -19.28 } 753 | ], 754 | "scale": [ 755 | { 756 | "time": 0, 757 | "x": 1, 758 | "y": 1, 759 | "curve": [ 0.235, 0, 0.558, 0.99 ] 760 | }, 761 | { 762 | "time": 0.6666, 763 | "x": 0.994, 764 | "y": 1, 765 | "curve": [ 0.594, 0, 0.653, 1 ] 766 | }, 767 | { "time": 1.6666, "x": 1, "y": 1 } 768 | ] 769 | }, 770 | "rear_thigh": { 771 | "rotate": [ 772 | { 773 | "time": 0, 774 | "angle": 30.5, 775 | "curve": [ 0.235, 0, 0.558, 0.99 ] 776 | }, 777 | { 778 | "time": 0.6666, 779 | "angle": 40.15, 780 | "curve": [ 0.594, 0, 0.653, 1 ] 781 | }, 782 | { "time": 1.6666, "angle": 30.5 } 783 | ], 784 | "scale": [ 785 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 786 | { "time": 1.6666, "x": 1, "y": 1 } 787 | ] 788 | }, 789 | "rear_shin": { 790 | "rotate": [ 791 | { 792 | "time": 0, 793 | "angle": -23.83, 794 | "curve": [ 0.235, 0, 0.558, 0.99 ] 795 | }, 796 | { 797 | "time": 0.6666, 798 | "angle": -43.77, 799 | "curve": [ 0.594, 0, 0.653, 1 ] 800 | }, 801 | { "time": 1.6666, "angle": -23.83 } 802 | ], 803 | "scale": [ 804 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 805 | { "time": 1.6666, "x": 1, "y": 1 } 806 | ] 807 | }, 808 | "front_foot": { 809 | "rotate": [ 810 | { 811 | "time": 0, 812 | "angle": 5.13, 813 | "curve": [ 0.235, 0, 0.558, 0.99 ] 814 | }, 815 | { 816 | "time": 0.6666, 817 | "angle": 10.04, 818 | "curve": [ 0.594, 0, 0.653, 1 ] 819 | }, 820 | { "time": 1.6666, "angle": 5.13 } 821 | ], 822 | "scale": [ 823 | { "time": 0, "x": 0.755, "y": 1.309, "curve": "stepped" }, 824 | { "time": 1.6666, "x": 0.755, "y": 1.309 } 825 | ] 826 | }, 827 | "hip": { 828 | "translate": [ 829 | { 830 | "time": 0, 831 | "x": -6.63, 832 | "y": -23.01, 833 | "curve": [ 0.235, 0, 0.558, 0.99 ] 834 | }, 835 | { 836 | "time": 0.6666, 837 | "x": 6.27, 838 | "y": -35, 839 | "curve": [ 0.594, 0, 0.653, 1 ] 840 | }, 841 | { "time": 1.6666, "x": -6.63, "y": -23.01 } 842 | ], 843 | "scale": [ 844 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 845 | { "time": 1.6666, "x": 1, "y": 1 } 846 | ] 847 | }, 848 | "rear_foot": { 849 | "rotate": [ 850 | { 851 | "time": 0, 852 | "angle": -7.34, 853 | "curve": [ 0.235, 0, 0.558, 0.99 ] 854 | }, 855 | { 856 | "time": 0.6666, 857 | "angle": 3.85, 858 | "curve": [ 0.594, 0, 0.653, 1 ] 859 | }, 860 | { "time": 1.6666, "angle": -7.34 } 861 | ], 862 | "scale": [ 863 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 864 | { "time": 1.6666, "x": 1, "y": 1 } 865 | ] 866 | }, 867 | "rear_bracer": { 868 | "rotate": [ 869 | { 870 | "time": 0, 871 | "angle": -17.16, 872 | "curve": [ 0.25, 0, 0.75, 1 ] 873 | }, 874 | { 875 | "time": 0.6666, 876 | "angle": 12.52, 877 | "curve": [ 0.25, 0, 0.75, 1 ] 878 | }, 879 | { "time": 1.6666, "angle": -17.16 } 880 | ], 881 | "scale": [ 882 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 883 | { "time": 1.6666, "x": 1, "y": 1 } 884 | ] 885 | }, 886 | "head": { 887 | "rotate": [ 888 | { 889 | "time": 0, 890 | "angle": -5.51, 891 | "curve": [ 0.25, 0, 0.75, 1 ] 892 | }, 893 | { 894 | "time": 0.6666, 895 | "angle": -3.12, 896 | "curve": [ 0.25, 0, 0.75, 1 ] 897 | }, 898 | { "time": 1.6666, "angle": -5.51 } 899 | ], 900 | "scale": [ 901 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 902 | { "time": 1.6666, "x": 1, "y": 1 } 903 | ] 904 | }, 905 | "front_bracer": { 906 | "rotate": [ 907 | { 908 | "time": 0, 909 | "angle": 45.46, 910 | "curve": [ 0.492, 0, 0.75, 1 ] 911 | }, 912 | { 913 | "time": 0.6666, 914 | "angle": 41.33, 915 | "curve": [ 0.32, 0.1, 0.736, 0.91 ] 916 | }, 917 | { "time": 1.6666, "angle": 45.46 } 918 | ], 919 | "scale": [ 920 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 921 | { "time": 1.6666, "x": 1, "y": 1 } 922 | ] 923 | }, 924 | "gun": { 925 | "rotate": [ 926 | { 927 | "time": 0, 928 | "angle": 0, 929 | "curve": [ 0.25, 0, 0.75, 1 ] 930 | }, 931 | { 932 | "time": 0.6666, 933 | "angle": -15.59, 934 | "curve": [ 0.732, 0, 0.769, 0.99 ] 935 | }, 936 | { "time": 1.6666, "angle": 0 } 937 | ], 938 | "scale": [ 939 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 940 | { "time": 1.6666, "x": 1, "y": 1 } 941 | ] 942 | }, 943 | "front_fist": { 944 | "rotate": [ 945 | { 946 | "time": 0, 947 | "angle": -6.84, 948 | "curve": [ 0.492, 0, 0.75, 1 ] 949 | }, 950 | { 951 | "time": 0.6666, 952 | "angle": -14.63, 953 | "curve": [ 0.324, 0.11, 0.75, 1 ] 954 | }, 955 | { "time": 1.6666, "angle": -6.84 } 956 | ], 957 | "scale": [ 958 | { 959 | "time": 0, 960 | "x": 1, 961 | "y": 1, 962 | "curve": [ 0.25, 0, 0.75, 1 ] 963 | }, 964 | { 965 | "time": 0.6666, 966 | "x": 0.689, 967 | "y": 1.1, 968 | "curve": [ 0.25, 0, 0.75, 1 ] 969 | }, 970 | { "time": 1.6666, "x": 1, "y": 1 } 971 | ] 972 | } 973 | } 974 | }, 975 | "jump": { 976 | "slots": { 977 | "front_fist": { 978 | "attachment": [ 979 | { "time": 0, "name": "front_fist_open" }, 980 | { "time": 0.2, "name": "front_fist_closed" }, 981 | { "time": 0.6666, "name": "front_fist_open" } 982 | ] 983 | }, 984 | "mouth": { 985 | "attachment": [ 986 | { "time": 0, "name": "mouth_grind" } 987 | ] 988 | }, 989 | "torso": { 990 | "attachment": [ 991 | { "time": 0, "name": "torso" } 992 | ] 993 | } 994 | }, 995 | "bones": { 996 | "front_thigh": { 997 | "rotate": [ 998 | { 999 | "time": 0, 1000 | "angle": 91.53, 1001 | "curve": [ 0.278, 0.46, 0.763, 1 ] 1002 | }, 1003 | { 1004 | "time": 0.2, 1005 | "angle": -35.83, 1006 | "curve": [ 0.761, 0, 0.75, 1 ] 1007 | }, 1008 | { "time": 0.4333, "angle": 127.74 }, 1009 | { 1010 | "time": 0.7333, 1011 | "angle": 48.18, 1012 | "curve": [ 0.227, 0.26, 0.432, 1 ] 1013 | }, 1014 | { "time": 0.8333, "angle": 25.35 }, 1015 | { "time": 0.9333, "angle": 45.37 }, 1016 | { "time": 1.0333, "angle": 38.12 }, 1017 | { "time": 1.1333, "angle": 25.35 }, 1018 | { "time": 1.3333, "angle": 91.53 } 1019 | ], 1020 | "translate": [ 1021 | { "time": 0, "x": -2.56, "y": 5.77 }, 1022 | { "time": 0.4333, "x": 8.3, "y": 7.98 }, 1023 | { "time": 0.7333, "x": 7.21, "y": -4 }, 1024 | { "time": 1.3333, "x": -2.56, "y": 5.77 } 1025 | ], 1026 | "scale": [ 1027 | { "time": 0, "x": 1, "y": 1 } 1028 | ] 1029 | }, 1030 | "torso": { 1031 | "rotate": [ 1032 | { "time": 0, "angle": -42.63 }, 1033 | { "time": 0.2, "angle": -5.74 }, 1034 | { "time": 0.4333, "angle": -50.76 }, 1035 | { "time": 0.7333, "angle": 1.89 }, 1036 | { "time": 0.8333, "angle": 11.58 }, 1037 | { "time": 0.9666, "angle": -1.89 }, 1038 | { "time": 1.1333, "angle": 11.58 }, 1039 | { "time": 1.3333, "angle": -42.63 } 1040 | ], 1041 | "translate": [ 1042 | { "time": 0, "x": 0, "y": 0 } 1043 | ], 1044 | "scale": [ 1045 | { "time": 0, "x": 1, "y": 1 } 1046 | ] 1047 | }, 1048 | "rear_thigh": { 1049 | "rotate": [ 1050 | { "time": 0, "angle": -26.32 }, 1051 | { "time": 0.2, "angle": 121.44 }, 1052 | { "time": 0.4333, "angle": 70.54 }, 1053 | { 1054 | "time": 0.7333, 1055 | "angle": 79.89, 1056 | "curve": [ 0.295, 0.3, 0.59, 0.99 ] 1057 | }, 1058 | { "time": 0.8333, "angle": 99.12 }, 1059 | { "time": 0.9333, "angle": 74.05 }, 1060 | { "time": 1.0333, "angle": 98.04 }, 1061 | { "time": 1.1333, "angle": 99.12 }, 1062 | { "time": 1.3333, "angle": -26.32 } 1063 | ], 1064 | "translate": [ 1065 | { "time": 0, "x": -0.56, "y": -0.32 }, 1066 | { "time": 0.4333, "x": -8.5, "y": 10.58 }, 1067 | { "time": 0.7333, "x": -1.96, "y": -0.32 }, 1068 | { "time": 1.3333, "x": -0.56, "y": -0.32 } 1069 | ], 1070 | "scale": [ 1071 | { "time": 0, "x": 1, "y": 1 } 1072 | ] 1073 | }, 1074 | "rear_shin": { 1075 | "rotate": [ 1076 | { "time": 0, "angle": -78.69 }, 1077 | { "time": 0.4333, "angle": -55.56 }, 1078 | { "time": 0.7333, "angle": -62.84 }, 1079 | { "time": 0.8333, "angle": -80.74 }, 1080 | { "time": 0.9333, "angle": -41.12 }, 1081 | { "time": 1.0333, "angle": -77.4 }, 1082 | { "time": 1.1333, "angle": -80.74 }, 1083 | { "time": 1.3333, "angle": -78.69 } 1084 | ], 1085 | "translate": [ 1086 | { "time": 0, "x": 0, "y": 0 } 1087 | ], 1088 | "scale": [ 1089 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1090 | { "time": 0.7333, "x": 1, "y": 1 } 1091 | ] 1092 | }, 1093 | "front_upper_arm": { 1094 | "rotate": [ 1095 | { "time": 0, "angle": -22.61 }, 1096 | { "time": 0.2, "angle": -246.68 }, 1097 | { 1098 | "time": 0.6, 1099 | "angle": 11.28, 1100 | "curve": [ 0.246, 0, 0.633, 0.53 ] 1101 | }, 1102 | { 1103 | "time": 0.7333, 1104 | "angle": -57.45, 1105 | "curve": [ 0.38, 0.53, 0.744, 1 ] 1106 | }, 1107 | { "time": 0.8666, "angle": -112.59 }, 1108 | { "time": 0.9333, "angle": -102.17 }, 1109 | { "time": 1.0333, "angle": -108.61 }, 1110 | { "time": 1.1333, "angle": -112.59 }, 1111 | { "time": 1.3333, "angle": -22.61 } 1112 | ], 1113 | "translate": [ 1114 | { "time": 0, "x": 6.08, "y": 7.15 }, 1115 | { "time": 0.2, "x": 7.23, "y": -13.13, "curve": "stepped" }, 1116 | { "time": 0.7333, "x": 7.23, "y": -13.13 }, 1117 | { "time": 1.3333, "x": 6.08, "y": 7.15 } 1118 | ], 1119 | "scale": [ 1120 | { "time": 0, "x": 1, "y": 1 } 1121 | ] 1122 | }, 1123 | "front_bracer": { 1124 | "rotate": [ 1125 | { "time": 0, "angle": 66.46 }, 1126 | { "time": 0.2, "angle": 42.39 }, 1127 | { "time": 0.4333, "angle": 26.06 }, 1128 | { "time": 0.7333, "angle": 13.28 }, 1129 | { "time": 0.8666, "angle": -28.64 }, 1130 | { "time": 0.9333, "angle": -22.31 }, 1131 | { "time": 1.0333, "angle": -35.39 }, 1132 | { "time": 1.1333, "angle": -28.64 }, 1133 | { "time": 1.3333, "angle": 66.46 } 1134 | ], 1135 | "translate": [ 1136 | { "time": 0, "x": 0, "y": 0 } 1137 | ], 1138 | "scale": [ 1139 | { "time": 0, "x": 1, "y": 1 } 1140 | ] 1141 | }, 1142 | "front_fist": { 1143 | "rotate": [ 1144 | { "time": 0, "angle": -28.43 }, 1145 | { "time": 0.4333, "angle": -45.6 }, 1146 | { "time": 0.7333, "angle": -53.66 }, 1147 | { "time": 0.8666, "angle": 7.55 }, 1148 | { "time": 0.9333, "angle": 31.15 }, 1149 | { "time": 1.0333, "angle": -32.58 }, 1150 | { "time": 1.1333, "angle": 7.55 }, 1151 | { "time": 1.3333, "angle": -28.43 } 1152 | ], 1153 | "translate": [ 1154 | { "time": 0, "x": 0, "y": 0 } 1155 | ], 1156 | "scale": [ 1157 | { "time": 0, "x": 1, "y": 1 } 1158 | ] 1159 | }, 1160 | "rear_upper_arm": { 1161 | "rotate": [ 1162 | { "time": 0, "angle": 39.68 }, 1163 | { "time": 0.2, "angle": 276.57 }, 1164 | { "time": 0.3, "angle": 17.73 }, 1165 | { "time": 0.4333, "angle": 83.38 }, 1166 | { 1167 | "time": 0.6, 1168 | "angle": -4.71, 1169 | "curve": [ 0.246, 0, 0.633, 0.53 ] 1170 | }, 1171 | { 1172 | "time": 0.7333, 1173 | "angle": -69.63, 1174 | "curve": [ 0.342, 0.36, 0.68, 0.71 ] 1175 | }, 1176 | { 1177 | "time": 0.7666, 1178 | "angle": 321.47, 1179 | "curve": [ 0.333, 0.33, 0.667, 0.66 ] 1180 | }, 1181 | { 1182 | "time": 0.8, 1183 | "angle": 33.7, 1184 | "curve": [ 0.358, 0.64, 0.693, 1 ] 1185 | }, 1186 | { "time": 0.8666, "angle": 34.56 }, 1187 | { "time": 1.0333, "angle": 71.96 }, 1188 | { "time": 1.1333, "angle": 34.56 }, 1189 | { "time": 1.3333, "angle": 39.68 } 1190 | ], 1191 | "translate": [ 1192 | { "time": 0, "x": -3.1, "y": -4.86 }, 1193 | { "time": 0.2, "x": 23.33, "y": 49.07 }, 1194 | { "time": 0.4333, "x": 20.78, "y": 40.21 }, 1195 | { "time": 1.3333, "x": -3.1, "y": -4.86 } 1196 | ], 1197 | "scale": [ 1198 | { "time": 0, "x": 1, "y": 1 } 1199 | ] 1200 | }, 1201 | "rear_bracer": { 1202 | "rotate": [ 1203 | { "time": 0, "angle": 29.66 }, 1204 | { "time": 0.2, "angle": 45.06 }, 1205 | { "time": 0.4333, "angle": -4.34 }, 1206 | { "time": 0.7666, "angle": 61.68 }, 1207 | { "time": 0.8, "angle": 82.59 }, 1208 | { "time": 0.8666, "angle": 80.06 }, 1209 | { "time": 1.0333, "angle": 57.56 }, 1210 | { "time": 1.1333, "angle": 80.06 }, 1211 | { "time": 1.3333, "angle": 29.66 } 1212 | ], 1213 | "translate": [ 1214 | { "time": 0, "x": 0, "y": 0 } 1215 | ], 1216 | "scale": [ 1217 | { "time": 0, "x": 1, "y": 1 } 1218 | ] 1219 | }, 1220 | "neck": { 1221 | "rotate": [ 1222 | { "time": 0, "angle": 24.9 }, 1223 | { "time": 0.2, "angle": 16.31 }, 1224 | { "time": 0.4333, "angle": 7.44 }, 1225 | { "time": 0.7333, "angle": -20.35 }, 1226 | { "time": 0.8333, "angle": -0.69, "curve": "stepped" }, 1227 | { "time": 1.1333, "angle": -0.69 }, 1228 | { "time": 1.3333, "angle": 24.9 } 1229 | ], 1230 | "translate": [ 1231 | { "time": 0, "x": 0, "y": 0 } 1232 | ], 1233 | "scale": [ 1234 | { "time": 0, "x": 1, "y": 1 } 1235 | ] 1236 | }, 1237 | "head": { 1238 | "rotate": [ 1239 | { "time": 0, "angle": 24.92 }, 1240 | { "time": 0.2, "angle": 10.36 }, 1241 | { "time": 0.4333, "angle": 28.65 }, 1242 | { "time": 0.7333, "angle": -2.65 }, 1243 | { "time": 0.8333, "angle": -28.94, "curve": "stepped" }, 1244 | { "time": 1.1333, "angle": -28.94 }, 1245 | { "time": 1.3333, "angle": 24.92 } 1246 | ], 1247 | "translate": [ 1248 | { "time": 0, "x": 0, "y": 0 } 1249 | ], 1250 | "scale": [ 1251 | { "time": 0, "x": 1, "y": 1 } 1252 | ] 1253 | }, 1254 | "hip": { 1255 | "rotate": [ 1256 | { "time": 0, "angle": 0 } 1257 | ], 1258 | "translate": [ 1259 | { 1260 | "time": 0, 1261 | "x": -34.51, 1262 | "y": -78.62, 1263 | "curve": [ 0.232, 1, 0.75, 1 ] 1264 | }, 1265 | { 1266 | "time": 0.2, 1267 | "x": -34.51, 1268 | "y": 182.5, 1269 | "curve": [ 0.232, 0.48, 0.598, 0.79 ] 1270 | }, 1271 | { 1272 | "time": 0.7666, 1273 | "x": -34.51, 1274 | "y": 596.22, 1275 | "curve": [ 0.329, 0.17, 0.66, 0.21 ] 1276 | }, 1277 | { "time": 1.1333, "x": -34.51, "y": 2.49 }, 1278 | { "time": 1.3333, "x": -34.51, "y": -78.62 } 1279 | ], 1280 | "scale": [ 1281 | { "time": 0, "x": 1, "y": 1 } 1282 | ] 1283 | }, 1284 | "front_shin": { 1285 | "rotate": [ 1286 | { 1287 | "time": 0, 1288 | "angle": -90.62, 1289 | "curve": [ 0.416, 0.54, 0.743, 1 ] 1290 | }, 1291 | { 1292 | "time": 0.2, 1293 | "angle": -10.52, 1294 | "curve": [ 0.644, 0, 0.75, 1 ] 1295 | }, 1296 | { "time": 0.4333, "angle": -127.72 }, 1297 | { "time": 0.7333, "angle": -19.91 }, 1298 | { "time": 0.8333, "angle": -5.16 }, 1299 | { "time": 0.9333, "angle": -35.06 }, 1300 | { "time": 1.0333, "angle": -43.97 }, 1301 | { "time": 1.1333, "angle": -5.16 }, 1302 | { "time": 1.3333, "angle": -90.62 } 1303 | ], 1304 | "translate": [ 1305 | { "time": 0, "x": 0, "y": 0 } 1306 | ], 1307 | "scale": [ 1308 | { "time": 0, "x": 1, "y": 1 } 1309 | ] 1310 | }, 1311 | "front_foot": { 1312 | "rotate": [ 1313 | { "time": 0, "angle": -0.79 }, 1314 | { "time": 0.0333, "angle": 16.27 }, 1315 | { "time": 0.0666, "angle": 23.52 }, 1316 | { "time": 0.1, "angle": 21.02 }, 1317 | { "time": 0.1333, "angle": 10.92 }, 1318 | { "time": 0.2, "angle": -38.45 }, 1319 | { "time": 0.4333, "angle": 6.62 }, 1320 | { "time": 0.7333, "angle": -11.51 }, 1321 | { "time": 1.0333, "angle": -22.91 }, 1322 | { "time": 1.3333, "angle": -0.79 } 1323 | ], 1324 | "translate": [ 1325 | { "time": 0, "x": 0, "y": 0 } 1326 | ], 1327 | "scale": [ 1328 | { "time": 0, "x": 1, "y": 1 } 1329 | ] 1330 | }, 1331 | "rear_foot": { 1332 | "rotate": [ 1333 | { "time": 0, "angle": -12.77 }, 1334 | { "time": 0.2, "angle": 17.05 }, 1335 | { "time": 0.4333, "angle": 19.45 }, 1336 | { "time": 0.7333, "angle": 2.67 }, 1337 | { "time": 1.0333, "angle": -28.49 }, 1338 | { "time": 1.3333, "angle": -12.77 } 1339 | ], 1340 | "translate": [ 1341 | { "time": 0, "x": 0, "y": 0 } 1342 | ], 1343 | "scale": [ 1344 | { "time": 0, "x": 1, "y": 1 } 1345 | ] 1346 | }, 1347 | "gun": { 1348 | "rotate": [ 1349 | { "time": 0, "angle": 6.18 }, 1350 | { "time": 0.2, "angle": 30.81 }, 1351 | { "time": 0.4333, "angle": 13.25 }, 1352 | { "time": 0.7333, "angle": 14.98 }, 1353 | { "time": 0.7666, "angle": 25.64 }, 1354 | { "time": 0.8, "angle": 20.62 }, 1355 | { "time": 0.8666, "angle": 64.52 }, 1356 | { "time": 1.0333, "angle": 8.59 }, 1357 | { "time": 1.1333, "angle": 64.52 }, 1358 | { "time": 1.3333, "angle": 6.18 } 1359 | ], 1360 | "translate": [ 1361 | { "time": 0, "x": 0, "y": 0 } 1362 | ], 1363 | "scale": [ 1364 | { "time": 0, "x": 1, "y": 1 } 1365 | ] 1366 | } 1367 | } 1368 | }, 1369 | "run": { 1370 | "slots": { 1371 | "front_fist": { 1372 | "attachment": [ 1373 | { "time": 0, "name": "front_fist_closed" } 1374 | ] 1375 | }, 1376 | "mouth": { 1377 | "attachment": [ 1378 | { "time": 0, "name": "mouth_grind" } 1379 | ] 1380 | }, 1381 | "torso": { 1382 | "attachment": [ 1383 | { "time": 0, "name": "torso" } 1384 | ] 1385 | } 1386 | }, 1387 | "bones": { 1388 | "front_thigh": { 1389 | "rotate": [ 1390 | { 1391 | "time": 0, 1392 | "angle": 42.05, 1393 | "curve": [ 0.195, 0.86, 0.75, 1 ] 1394 | }, 1395 | { "time": 0.0666, "angle": 46.07 }, 1396 | { "time": 0.1333, "angle": -20.28 }, 1397 | { "time": 0.2, "angle": -27.23 }, 1398 | { "time": 0.2666, "angle": -47.16 }, 1399 | { "time": 0.3333, "angle": -39.79 }, 1400 | { "time": 0.4, "angle": -25.86 }, 1401 | { "time": 0.4666, "angle": 14.35 }, 1402 | { "time": 0.5333, "angle": 55.62 }, 1403 | { "time": 0.6, "angle": 69.65 }, 1404 | { "time": 0.6666, "angle": 86.4 }, 1405 | { "time": 0.7333, "angle": 65.87 }, 1406 | { "time": 0.8, "angle": 42.05 } 1407 | ], 1408 | "translate": [ 1409 | { "time": 0, "x": 0, "y": 0 }, 1410 | { "time": 0.0333, "x": -5.79, "y": 11.15 }, 1411 | { "time": 0.0666, "x": -5.13, "y": 11.55 }, 1412 | { "time": 0.1333, "x": -7.7, "y": 8.98 }, 1413 | { "time": 0.5333, "x": -1.26, "y": 3.83 }, 1414 | { "time": 0.8, "x": 0, "y": 0 } 1415 | ], 1416 | "scale": [ 1417 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1418 | { "time": 0.8, "x": 1, "y": 1 } 1419 | ] 1420 | }, 1421 | "torso": { 1422 | "rotate": [ 1423 | { "time": 0, "angle": -39.7 }, 1424 | { "time": 0.2, "angle": -57.29 }, 1425 | { "time": 0.4, "angle": -39.7 }, 1426 | { "time": 0.6, "angle": -57.29 }, 1427 | { "time": 0.8, "angle": -39.7 } 1428 | ], 1429 | "translate": [ 1430 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1431 | { "time": 0.4, "x": 0, "y": 0, "curve": "stepped" }, 1432 | { "time": 0.8, "x": 0, "y": 0 } 1433 | ], 1434 | "scale": [ 1435 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1436 | { "time": 0.8, "x": 1, "y": 1 } 1437 | ] 1438 | }, 1439 | "rear_thigh": { 1440 | "rotate": [ 1441 | { "time": 0, "angle": -56.59 }, 1442 | { "time": 0.0666, "angle": -21.57 }, 1443 | { "time": 0.1333, "angle": 27.95 }, 1444 | { "time": 0.2, "angle": 42.42 }, 1445 | { "time": 0.2666, "angle": 62.37 }, 1446 | { "time": 0.3333, "angle": 45.42 }, 1447 | { "time": 0.4, "angle": 15.67 }, 1448 | { "time": 0.4666, "angle": 28.22 }, 1449 | { "time": 0.5333, "angle": -38.62 }, 1450 | { "time": 0.6, "angle": -53.26 }, 1451 | { "time": 0.6666, "angle": -79.31 }, 1452 | { "time": 0.7333, "angle": -86.47 }, 1453 | { "time": 0.8, "angle": -56.59 } 1454 | ], 1455 | "translate": [ 1456 | { "time": 0, "x": 0, "y": 0 }, 1457 | { "time": 0.4, "x": -6.76, "y": -3.86 }, 1458 | { "time": 0.4333, "x": -15.85, "y": 7.28 }, 1459 | { "time": 0.4666, "x": -13.04, "y": 4.04 }, 1460 | { "time": 0.5, "x": -10.24, "y": 7.11 }, 1461 | { "time": 0.5333, "x": -9.01, "y": -5.15 }, 1462 | { "time": 0.6666, "x": -23.18, "y": -2.57 }, 1463 | { "time": 0.8, "x": 0, "y": 0 } 1464 | ], 1465 | "scale": [ 1466 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1467 | { "time": 0.8, "x": 1, "y": 1 } 1468 | ] 1469 | }, 1470 | "rear_shin": { 1471 | "rotate": [ 1472 | { "time": 0, "angle": -74 }, 1473 | { "time": 0.0666, "angle": -83.38 }, 1474 | { "time": 0.1333, "angle": -106.69 }, 1475 | { "time": 0.2, "angle": -66.01 }, 1476 | { "time": 0.2666, "angle": -55.22 }, 1477 | { "time": 0.3333, "angle": -24.8 }, 1478 | { 1479 | "time": 0.4, 1480 | "angle": 18.44, 1481 | "curve": [ 0.25, 0, 0.75, 1 ] 1482 | }, 1483 | { "time": 0.4666, "angle": -56.65 }, 1484 | { 1485 | "time": 0.5333, 1486 | "angle": -11.94, 1487 | "curve": [ 0.25, 0, 0.75, 1 ] 1488 | }, 1489 | { "time": 0.6666, "angle": -41.26 }, 1490 | { "time": 0.7333, "angle": -43.6 }, 1491 | { "time": 0.8, "angle": -74 } 1492 | ], 1493 | "translate": [ 1494 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1495 | { "time": 0.8, "x": 0, "y": 0 } 1496 | ], 1497 | "scale": [ 1498 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1499 | { "time": 0.8, "x": 1, "y": 1 } 1500 | ] 1501 | }, 1502 | "front_upper_arm": { 1503 | "rotate": [ 1504 | { "time": 0, "angle": -89.36 }, 1505 | { "time": 0.0666, "angle": -95.67 }, 1506 | { "time": 0.1333, "angle": -22 }, 1507 | { "time": 0.2, "angle": -316.04 }, 1508 | { "time": 0.2666, "angle": -274.94 }, 1509 | { "time": 0.3333, "angle": -273.74 }, 1510 | { "time": 0.4, "angle": -272.09 }, 1511 | { "time": 0.4666, "angle": -264.89 }, 1512 | { "time": 0.5333, "angle": -320.09 }, 1513 | { "time": 0.6, "angle": -50.83 }, 1514 | { "time": 0.6666, "angle": -81.72 }, 1515 | { "time": 0.7333, "angle": -83.92 }, 1516 | { "time": 0.8, "angle": -89.36 } 1517 | ], 1518 | "translate": [ 1519 | { "time": 0, "x": 6.24, "y": 10.05 }, 1520 | { "time": 0.2666, "x": 4.95, "y": -13.13 }, 1521 | { "time": 0.6, "x": -2.43, "y": 1.94 }, 1522 | { "time": 0.8, "x": 6.24, "y": 10.05 } 1523 | ], 1524 | "scale": [ 1525 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1526 | { "time": 0.8, "x": 1, "y": 1 } 1527 | ] 1528 | }, 1529 | "front_bracer": { 1530 | "rotate": [ 1531 | { "time": 0, "angle": 33.43 }, 1532 | { "time": 0.0666, "angle": 20.53 }, 1533 | { "time": 0.1333, "angle": 15.26 }, 1534 | { "time": 0.2, "angle": 19.28 }, 1535 | { "time": 0.2666, "angle": 22.62 }, 1536 | { "time": 0.3333, "angle": 37.29 }, 1537 | { "time": 0.4, "angle": 41.53 }, 1538 | { "time": 0.4666, "angle": 31.73 }, 1539 | { "time": 0.5333, "angle": 67.45 }, 1540 | { "time": 0.6666, "angle": 39.77 }, 1541 | { "time": 0.7333, "angle": 30.95 }, 1542 | { "time": 0.8, "angle": 33.43 } 1543 | ], 1544 | "translate": [ 1545 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1546 | { "time": 0.8, "x": 0, "y": 0 } 1547 | ], 1548 | "scale": [ 1549 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1550 | { "time": 0.8, "x": 1, "y": 1 } 1551 | ] 1552 | }, 1553 | "front_fist": { 1554 | "rotate": [ 1555 | { "time": 0, "angle": -19.75 }, 1556 | { "time": 0.0666, "angle": -37.11 }, 1557 | { "time": 0.1333, "angle": -50.79 }, 1558 | { "time": 0.2666, "angle": -12.69 }, 1559 | { "time": 0.3333, "angle": 3.01 }, 1560 | { "time": 0.4333, "angle": 12.05 }, 1561 | { "time": 0.5333, "angle": 13.25 }, 1562 | { "time": 0.8, "angle": -19.75 } 1563 | ], 1564 | "translate": [ 1565 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1566 | { "time": 0.8, "x": 0, "y": 0 } 1567 | ], 1568 | "scale": [ 1569 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1570 | { "time": 0.8, "x": 1, "y": 1 } 1571 | ] 1572 | }, 1573 | "rear_upper_arm": { 1574 | "rotate": [ 1575 | { "time": 0, "angle": 68.68 }, 1576 | { "time": 0.0666, "angle": 73.89 }, 1577 | { "time": 0.1333, "angle": -9.64 }, 1578 | { "time": 0.2, "angle": 284.27 }, 1579 | { "time": 0.2666, "angle": 283.29 }, 1580 | { "time": 0.3333, "angle": 278.28 }, 1581 | { "time": 0.4, "angle": 271.02 }, 1582 | { "time": 0.4666, "angle": 263.2 }, 1583 | { "time": 0.5333, "angle": 314.25 }, 1584 | { "time": 0.6, "angle": 16.83 }, 1585 | { "time": 0.6666, "angle": 70.35 }, 1586 | { "time": 0.7333, "angle": 73.53 }, 1587 | { "time": 0.8, "angle": 68.68 } 1588 | ], 1589 | "translate": [ 1590 | { "time": 0, "x": -2.57, "y": -8.89 }, 1591 | { "time": 0.1333, "x": -4.68, "y": 7.2 }, 1592 | { "time": 0.2, "x": 21.73, "y": 51.17 }, 1593 | { "time": 0.6, "x": 4.33, "y": 2.05 }, 1594 | { "time": 0.8, "x": -2.57, "y": -8.89 } 1595 | ], 1596 | "scale": [ 1597 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1598 | { "time": 0.8, "x": 1, "y": 1 } 1599 | ] 1600 | }, 1601 | "rear_bracer": { 1602 | "rotate": [ 1603 | { "time": 0, "angle": 31.04 }, 1604 | { "time": 0.0666, "angle": 28.28 }, 1605 | { "time": 0.1333, "angle": 49.36 }, 1606 | { "time": 0.2, "angle": 59.37 }, 1607 | { "time": 0.2666, "angle": 8.56 }, 1608 | { "time": 0.3333, "angle": 9.38 }, 1609 | { "time": 0.4, "angle": 11.51 }, 1610 | { "time": 0.4666, "angle": 7.22 }, 1611 | { "time": 0.5333, "angle": -18.44 }, 1612 | { "time": 0.6, "angle": 11.44 }, 1613 | { "time": 0.6666, "angle": 9.99 }, 1614 | { "time": 0.7333, "angle": 8.28 }, 1615 | { "time": 0.8, "angle": 31.04 } 1616 | ], 1617 | "translate": [ 1618 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1619 | { "time": 0.8, "x": 0, "y": 0 } 1620 | ], 1621 | "scale": [ 1622 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1623 | { "time": 0.8, "x": 1, "y": 1 } 1624 | ] 1625 | }, 1626 | "neck": { 1627 | "rotate": [ 1628 | { "time": 0, "angle": 11.03 }, 1629 | { "time": 0.2, "angle": 13.58 }, 1630 | { "time": 0.4, "angle": 11.03 }, 1631 | { "time": 0.6, "angle": 13.58 }, 1632 | { "time": 0.8, "angle": 11.03 } 1633 | ], 1634 | "translate": [ 1635 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1636 | { "time": 0.4, "x": 0, "y": 0, "curve": "stepped" }, 1637 | { "time": 0.8, "x": 0, "y": 0 } 1638 | ], 1639 | "scale": [ 1640 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1641 | { "time": 0.8, "x": 1, "y": 1 } 1642 | ] 1643 | }, 1644 | "head": { 1645 | "rotate": [ 1646 | { "time": 0, "angle": 11.03 }, 1647 | { "time": 0.1, "angle": 12.34 }, 1648 | { "time": 0.2, "angle": 25.55 }, 1649 | { "time": 0.4, "angle": 11.03 }, 1650 | { "time": 0.5, "angle": 12.34 }, 1651 | { "time": 0.6, "angle": 25.55 }, 1652 | { "time": 0.8, "angle": 11.03 } 1653 | ], 1654 | "translate": [ 1655 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1656 | { "time": 0.4, "x": 0, "y": 0, "curve": "stepped" }, 1657 | { "time": 0.8, "x": 0, "y": 0 } 1658 | ], 1659 | "scale": [ 1660 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1661 | { "time": 0.8, "x": 1, "y": 1 } 1662 | ] 1663 | }, 1664 | "hip": { 1665 | "rotate": [ 1666 | { "time": 0, "angle": 0, "curve": "stepped" }, 1667 | { "time": 0.8, "angle": 0 } 1668 | ], 1669 | "translate": [ 1670 | { "time": 0, "x": -62.47, "y": -23.1 }, 1671 | { 1672 | "time": 0.0666, 1673 | "x": -62.47, 1674 | "y": -38.51, 1675 | "curve": [ 0.244, 0.04, 0.75, 1 ] 1676 | }, 1677 | { 1678 | "time": 0.2666, 1679 | "x": -62.47, 1680 | "y": 22.28, 1681 | "curve": [ 0.17, 0.52, 0.75, 1 ] 1682 | }, 1683 | { "time": 0.4, "x": -62.47, "y": -23.1 }, 1684 | { "time": 0.4333, "x": -62.47, "y": -24.59 }, 1685 | { 1686 | "time": 0.4666, 1687 | "x": -62.47, 1688 | "y": -43.29, 1689 | "curve": [ 0.25, 0, 0.75, 1 ] 1690 | }, 1691 | { "time": 0.6666, "x": -62.47, "y": 22.28 }, 1692 | { "time": 0.8, "x": -62.47, "y": -23.1 } 1693 | ], 1694 | "scale": [ 1695 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1696 | { "time": 0.8, "x": 1, "y": 1 } 1697 | ] 1698 | }, 1699 | "front_shin": { 1700 | "rotate": [ 1701 | { 1702 | "time": 0, 1703 | "angle": 0, 1704 | "curve": [ 0.481, 0.01, 0.75, 1 ] 1705 | }, 1706 | { "time": 0.0666, "angle": -64.42 }, 1707 | { 1708 | "time": 0.1333, 1709 | "angle": -20.59, 1710 | "curve": [ 0.25, 0, 0.75, 1 ] 1711 | }, 1712 | { "time": 0.2666, "angle": -62.51 }, 1713 | { "time": 0.3333, "angle": -79.74 }, 1714 | { "time": 0.4, "angle": -78.28 }, 1715 | { 1716 | "time": 0.4666, 1717 | "angle": -118.96, 1718 | "curve": [ 0.93, 0, 0.952, 0.95 ] 1719 | }, 1720 | { "time": 0.6, "angle": -88.95 }, 1721 | { "time": 0.6666, "angle": -79.09 }, 1722 | { "time": 0.7333, "angle": -47.77 }, 1723 | { "time": 0.8, "angle": 0 } 1724 | ], 1725 | "translate": [ 1726 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1727 | { "time": 0.8, "x": 0, "y": 0 } 1728 | ], 1729 | "scale": [ 1730 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1731 | { "time": 0.8, "x": 1, "y": 1 } 1732 | ] 1733 | }, 1734 | "front_foot": { 1735 | "rotate": [ 1736 | { "time": 0, "angle": 0 }, 1737 | { 1738 | "time": 0.0333, 1739 | "angle": -21.13, 1740 | "curve": [ 0.121, 0.23, 0.75, 1 ] 1741 | }, 1742 | { "time": 0.0666, "angle": 17.64 }, 1743 | { "time": 0.1, "angle": 29.92 }, 1744 | { "time": 0.1333, "angle": 16.44 }, 1745 | { "time": 0.2, "angle": -29.22 }, 1746 | { "time": 0.2666, "angle": -1.61 }, 1747 | { "time": 0.3333, "angle": -10.22 }, 1748 | { "time": 0.4666, "angle": -15.99 }, 1749 | { "time": 0.6, "angle": 9.03 }, 1750 | { "time": 0.7333, "angle": 17.32 }, 1751 | { "time": 0.8, "angle": 0 } 1752 | ], 1753 | "translate": [ 1754 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1755 | { "time": 0.8, "x": 0, "y": 0 } 1756 | ], 1757 | "scale": [ 1758 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1759 | { "time": 0.8, "x": 1, "y": 1 } 1760 | ] 1761 | }, 1762 | "rear_foot": { 1763 | "rotate": [ 1764 | { "time": 0, "angle": 0 }, 1765 | { "time": 0.0666, "angle": -12.04 }, 1766 | { "time": 0.1333, "angle": -0.87 }, 1767 | { "time": 0.2, "angle": 25.81 }, 1768 | { "time": 0.2666, "angle": 4.71 }, 1769 | { 1770 | "time": 0.4, 1771 | "angle": 18.09, 1772 | "curve": [ 0.281, 0.73, 0.75, 1 ] 1773 | }, 1774 | { "time": 0.4333, "angle": -1.7 }, 1775 | { "time": 0.4666, "angle": 27.12 }, 1776 | { "time": 0.5, "angle": 38.83 }, 1777 | { "time": 0.5333, "angle": 30.76 }, 1778 | { "time": 0.5666, "angle": -20.49 }, 1779 | { "time": 0.6, "angle": -30.8 }, 1780 | { "time": 0.6666, "angle": -1.31 }, 1781 | { "time": 0.8, "angle": 0 } 1782 | ], 1783 | "translate": [ 1784 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1785 | { "time": 0.8, "x": 0, "y": 0 } 1786 | ], 1787 | "scale": [ 1788 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1789 | { "time": 0.8, "x": 1, "y": 1 } 1790 | ] 1791 | }, 1792 | "gun": { 1793 | "rotate": [ 1794 | { "time": 0, "angle": 0 }, 1795 | { "time": 0.1333, "angle": 24.72 }, 1796 | { "time": 0.5, "angle": -11.87 }, 1797 | { "time": 0.8, "angle": 0 } 1798 | ], 1799 | "translate": [ 1800 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 1801 | { "time": 0.8, "x": 0, "y": 0 } 1802 | ], 1803 | "scale": [ 1804 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 1805 | { "time": 0.8, "x": 1, "y": 1 } 1806 | ] 1807 | } 1808 | }, 1809 | "events": [ 1810 | { "time": 0, "name": "footstep" }, 1811 | { "time": 0.4, "name": "footstep", "int": 1, "float": 0.47, "string": "script" } 1812 | ] 1813 | }, 1814 | "shoot": { 1815 | "slots": { 1816 | "front_fist": { 1817 | "attachment": [ 1818 | { "time": 0.1333, "name": "front_fist_closed" }, 1819 | { "time": 0.4, "name": "front_fist_open" } 1820 | ] 1821 | }, 1822 | "mouth": { 1823 | "attachment": [ 1824 | { "time": 0.1333, "name": "mouth_grind" } 1825 | ] 1826 | }, 1827 | "muzzle": { 1828 | "attachment": [ 1829 | { "time": 0.1333, "name": "muzzle" }, 1830 | { "time": 0.2666, "name": null } 1831 | ], 1832 | "color": [ 1833 | { 1834 | "time": 0.1333, 1835 | "color": "ffffff00", 1836 | "curve": [ 0.118, 0.99, 0.75, 1 ] 1837 | }, 1838 | { 1839 | "time": 0.1666, 1840 | "color": "ffffffff", 1841 | "curve": [ 0.821, 0, 0.909, 0.89 ] 1842 | }, 1843 | { "time": 0.2666, "color": "ffffff00" } 1844 | ] 1845 | } 1846 | }, 1847 | "bones": { 1848 | "front_fist": { 1849 | "scale": [ 1850 | { "time": 0.1333, "x": 1, "y": 1, "curve": "stepped" }, 1851 | { "time": 0.4, "x": 1, "y": 1 } 1852 | ] 1853 | }, 1854 | "gunTip": { 1855 | "translate": [ 1856 | { "time": 0.1333, "x": 0, "y": 0 }, 1857 | { "time": 0.2, "x": 20.93, "y": 1.57 } 1858 | ], 1859 | "scale": [ 1860 | { "time": 0.1333, "x": 1, "y": 1 }, 1861 | { "time": 0.2, "x": 1.247, "y": 1.516 } 1862 | ] 1863 | }, 1864 | "gun": { 1865 | "rotate": [ 1866 | { "time": 0, "angle": 1.9 } 1867 | ], 1868 | "translate": [ 1869 | { 1870 | "time": 0, 1871 | "x": 7.95, 1872 | "y": 5.84, 1873 | "curve": [ 0, 0.3, 0.678, 1 ] 1874 | }, 1875 | { "time": 0.3, "x": -9.3, "y": -1.41 }, 1876 | { "time": 0.4, "x": 0, "y": 0 } 1877 | ] 1878 | }, 1879 | "rear_bracer": { 1880 | "rotate": [ 1881 | { "time": 0, "angle": -30.47 } 1882 | ], 1883 | "translate": [ 1884 | { 1885 | "time": 0, 1886 | "x": 0, 1887 | "y": 0, 1888 | "curve": [ 0, 0.3, 0.678, 1 ] 1889 | }, 1890 | { "time": 0.3, "x": -5.99, "y": -3.71 }, 1891 | { "time": 0.4, "x": 0, "y": 0 } 1892 | ] 1893 | }, 1894 | "rear_upper_arm": { 1895 | "rotate": [ 1896 | { "time": 0, "angle": 62.3 } 1897 | ], 1898 | "translate": [ 1899 | { 1900 | "time": 0, 1901 | "x": 0, 1902 | "y": 0, 1903 | "curve": [ 0, 0.3, 0.678, 1 ] 1904 | }, 1905 | { "time": 0.3, "x": 2.81, "y": 11.41 }, 1906 | { "time": 0.4, "x": 0, "y": 0 } 1907 | ] 1908 | } 1909 | } 1910 | }, 1911 | "test": { 1912 | "slots": { 1913 | "front_foot": { 1914 | "color": [ 1915 | { "time": 0.6666, "color": "ffffffff" }, 1916 | { "time": 1.3333, "color": "ff0700ff" } 1917 | ] 1918 | }, 1919 | "gun": { 1920 | "color": [ 1921 | { "time": 0, "color": "ffffffff", "curve": "stepped" }, 1922 | { "time": 0.6666, "color": "ffffffff" }, 1923 | { "time": 1.3333, "color": "32ff00ff" } 1924 | ] 1925 | }, 1926 | "rear_foot": { 1927 | "color": [ 1928 | { "time": 0.6666, "color": "ffffffff" }, 1929 | { "time": 1.3333, "color": "ff0700ff" } 1930 | ] 1931 | } 1932 | }, 1933 | "bones": { 1934 | "head": { 1935 | "rotate": [ 1936 | { "time": 0, "angle": 0 }, 1937 | { "time": 0.3333, "angle": -20.72 }, 1938 | { "time": 0.6666, "angle": -32.41 }, 1939 | { "time": 1, "angle": -5.3 }, 1940 | { "time": 1.3333, "angle": 24.96 }, 1941 | { "time": 1.6666, "angle": 15.61 }, 1942 | { "time": 2, "angle": 0 } 1943 | ], 1944 | "translate": [ 1945 | { 1946 | "time": 0, 1947 | "x": 0, 1948 | "y": 0, 1949 | "curve": [ 0.172, 0.37, 0.574, 0.73 ] 1950 | }, 1951 | { 1952 | "time": 0.1666, 1953 | "x": 144.19, 1954 | "y": -77.59, 1955 | "curve": [ 0.372, 0.61, 0.765, 1 ] 1956 | }, 1957 | { 1958 | "time": 0.3333, 1959 | "x": 217.61, 1960 | "y": -192.63, 1961 | "curve": [ 0.282, 0, 0.624, 0.31 ] 1962 | }, 1963 | { 1964 | "time": 0.5, 1965 | "x": 181.21, 1966 | "y": -365.66, 1967 | "curve": [ 0.313, 0.21, 0.654, 0.54 ] 1968 | }, 1969 | { 1970 | "time": 0.6666, 1971 | "x": 20.09, 1972 | "y": -500.4, 1973 | "curve": [ 0.147, 0.27, 0.75, 1 ] 1974 | }, 1975 | { "time": 0.8333, "x": -194.24, "y": -341.84 }, 1976 | { "time": 1, "x": -307.93, "y": -114 }, 1977 | { 1978 | "time": 1.1666, 1979 | "x": -330.38, 1980 | "y": 121.42, 1981 | "curve": [ 0.25, 0, 0.764, 0.48 ] 1982 | }, 1983 | { 1984 | "time": 1.3333, 1985 | "x": -240.42, 1986 | "y": 335.66, 1987 | "curve": [ 0.229, 0.37, 0.58, 0.73 ] 1988 | }, 1989 | { 1990 | "time": 1.5, 1991 | "x": -56.12, 1992 | "y": 288.06, 1993 | "curve": [ 0.296, 0.6, 0.641, 1 ] 1994 | }, 1995 | { 1996 | "time": 1.6666, 1997 | "x": 87.63, 1998 | "y": 191.33, 1999 | "curve": [ 0.238, 0, 0.626, 0.39 ] 2000 | }, 2001 | { 2002 | "time": 1.8333, 2003 | "x": 60.62, 2004 | "y": 95.14, 2005 | "curve": [ 0.41, 0.26, 0.803, 0.62 ] 2006 | }, 2007 | { "time": 2, "x": 0, "y": 0 } 2008 | ] 2009 | } 2010 | }, 2011 | "draworder": [ 2012 | { 2013 | "time": 0.6666, 2014 | "offsets": [ 2015 | { "slot": "head", "offset": -9 }, 2016 | { "slot": "eye", "offset": -9 }, 2017 | { "slot": "mouth", "offset": -12 }, 2018 | { "slot": "goggles", "offset": -12 } 2019 | ] 2020 | }, 2021 | { "time": 1.3333 } 2022 | ], 2023 | "events": [ 2024 | { "time": 0, "name": "headPop", "int": 0, "float": 0, "string": "pop.wav" }, 2025 | { "time": 1, "name": "headBehind", "int": 7, "float": 8, "string": "animate" }, 2026 | { "time": 2, "name": "headAttach", "int": 0, "float": 0, "string": "attach.wav" } 2027 | ] 2028 | }, 2029 | "walk": { 2030 | "slots": { 2031 | "front_fist": { 2032 | "attachment": [ 2033 | { "time": 0, "name": "front_fist_closed" } 2034 | ] 2035 | }, 2036 | "mouth": { 2037 | "attachment": [ 2038 | { "time": 0, "name": "mouth_smile" } 2039 | ] 2040 | }, 2041 | "torso": { 2042 | "attachment": [ 2043 | { "time": 0, "name": "torso" } 2044 | ] 2045 | } 2046 | }, 2047 | "bones": { 2048 | "front_thigh": { 2049 | "rotate": [ 2050 | { "time": 0, "angle": 15.79 }, 2051 | { "time": 0.1, "angle": 27.39 }, 2052 | { "time": 0.2, "angle": -7.94 }, 2053 | { "time": 0.3, "angle": -16.94 }, 2054 | { "time": 0.4, "angle": -28.62 }, 2055 | { "time": 0.5, "angle": -19.3 }, 2056 | { "time": 0.6, "angle": -3.08 }, 2057 | { "time": 0.7, "angle": 29.51 }, 2058 | { "time": 0.8, "angle": 15.79 } 2059 | ], 2060 | "translate": [ 2061 | { "time": 0, "x": 0, "y": 0 }, 2062 | { "time": 0.4, "x": -1.18, "y": 0.54 }, 2063 | { "time": 0.5, "x": 0.11, "y": 0.41 }, 2064 | { "time": 0.6, "x": 9.48, "y": 0.27 }, 2065 | { "time": 0.8, "x": 0, "y": 0 } 2066 | ], 2067 | "scale": [ 2068 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2069 | { "time": 0.4, "x": 1, "y": 1, "curve": "stepped" }, 2070 | { "time": 0.8, "x": 1, "y": 1 } 2071 | ] 2072 | }, 2073 | "front_shin": { 2074 | "rotate": [ 2075 | { "time": 0, "angle": 5.12 }, 2076 | { "time": 0.1, "angle": -20.87 }, 2077 | { "time": 0.2, "angle": 13.37 }, 2078 | { "time": 0.3, "angle": 15.98 }, 2079 | { "time": 0.4, "angle": 5.94 }, 2080 | { "time": 0.5, "angle": -26.76 }, 2081 | { "time": 0.7, "angle": -55.44 }, 2082 | { "time": 0.8, "angle": 5.12 } 2083 | ], 2084 | "translate": [ 2085 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2086 | { "time": 0.8, "x": 0, "y": 0 } 2087 | ], 2088 | "scale": [ 2089 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2090 | { "time": 0.8, "x": 1, "y": 1 } 2091 | ] 2092 | }, 2093 | "rear_thigh": { 2094 | "rotate": [ 2095 | { "time": 0, "angle": -34.38 }, 2096 | { "time": 0.1, "angle": -30.32 }, 2097 | { "time": 0.2, "angle": -37.22 }, 2098 | { "time": 0.3, "angle": 20.73 }, 2099 | { "time": 0.4, "angle": 8.69 }, 2100 | { "time": 0.5, "angle": 12.16 }, 2101 | { "time": 0.6, "angle": -24.62 }, 2102 | { "time": 0.7, "angle": -27.26 }, 2103 | { "time": 0.8, "angle": -34.38 } 2104 | ], 2105 | "translate": [ 2106 | { "time": 0, "x": 0, "y": 0 }, 2107 | { "time": 0.4, "x": 4.08, "y": -9.53 }, 2108 | { "time": 0.5, "x": 0, "y": 0 }, 2109 | { "time": 0.7, "x": -21.14, "y": -9.6 }, 2110 | { "time": 0.8, "x": 0, "y": 0 } 2111 | ], 2112 | "scale": [ 2113 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2114 | { "time": 0.8, "x": 1, "y": 1 } 2115 | ] 2116 | }, 2117 | "rear_shin": { 2118 | "rotate": [ 2119 | { "time": 0, "angle": 14.26 }, 2120 | { "time": 0.1, "angle": -17.3 }, 2121 | { "time": 0.2, "angle": -12.67 }, 2122 | { "time": 0.3, "angle": -58.89 }, 2123 | { "time": 0.4, "angle": 15.95 }, 2124 | { "time": 0.5, "angle": -9 }, 2125 | { "time": 0.6, "angle": 26.06 }, 2126 | { "time": 0.7, "angle": 21.85 }, 2127 | { "time": 0.8, "angle": 14.26 } 2128 | ], 2129 | "translate": [ 2130 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2131 | { "time": 0.8, "x": 0, "y": 0 } 2132 | ], 2133 | "scale": [ 2134 | { "time": 0, "x": 1, "y": 1 }, 2135 | { "time": 0.1, "x": 0.951, "y": 1 }, 2136 | { "time": 0.5, "x": 0.975, "y": 1 }, 2137 | { "time": 0.8, "x": 1, "y": 1 } 2138 | ] 2139 | }, 2140 | "rear_foot": { 2141 | "rotate": [ 2142 | { "time": 0, "angle": 10.13 }, 2143 | { "time": 0.1, "angle": 12.27 }, 2144 | { "time": 0.2, "angle": -2.94 }, 2145 | { "time": 0.3, "angle": 6.29 }, 2146 | { "time": 0.4, "angle": 13.45 }, 2147 | { "time": 0.5, "angle": -3.57 }, 2148 | { "time": 0.6, "angle": -0.97 }, 2149 | { "time": 0.7, "angle": 2.97 }, 2150 | { "time": 0.8, "angle": 10.13 } 2151 | ], 2152 | "translate": [ 2153 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2154 | { "time": 0.8, "x": 0, "y": 0 } 2155 | ], 2156 | "scale": [ 2157 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2158 | { "time": 0.8, "x": 1, "y": 1 } 2159 | ] 2160 | }, 2161 | "front_upper_arm": { 2162 | "rotate": [ 2163 | { "time": 0, "angle": -23.74 }, 2164 | { "time": 0.4, "angle": -320.57 }, 2165 | { "time": 0.8, "angle": -23.74 } 2166 | ], 2167 | "translate": [ 2168 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2169 | { "time": 0.8, "x": 0, "y": 0 } 2170 | ], 2171 | "scale": [ 2172 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2173 | { "time": 0.8, "x": 1, "y": 1 } 2174 | ] 2175 | }, 2176 | "rear_upper_arm": { 2177 | "rotate": [ 2178 | { "time": 0, "angle": 11.62 }, 2179 | { "time": 0.1, "angle": 19.36 }, 2180 | { "time": 0.4, "angle": 345.26 }, 2181 | { "time": 0.5, "angle": 343.44 }, 2182 | { "time": 0.8, "angle": 11.62 } 2183 | ], 2184 | "translate": [ 2185 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2186 | { "time": 0.8, "x": 0, "y": 0 } 2187 | ], 2188 | "scale": [ 2189 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2190 | { "time": 0.8, "x": 1, "y": 1 } 2191 | ] 2192 | }, 2193 | "torso": { 2194 | "rotate": [ 2195 | { "time": 0, "angle": -12.11 }, 2196 | { "time": 0.1666, "angle": -17.16 }, 2197 | { "time": 0.4, "angle": -12.11 }, 2198 | { "time": 0.5666, "angle": -15.81 }, 2199 | { "time": 0.8, "angle": -12.11 } 2200 | ], 2201 | "scale": [ 2202 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2203 | { "time": 0.8, "x": 1, "y": 1 } 2204 | ] 2205 | }, 2206 | "neck": { 2207 | "rotate": [ 2208 | { "time": 0, "angle": 1.41 }, 2209 | { "time": 0.2333, "angle": -3.04 }, 2210 | { "time": 0.4, "angle": 1.41 }, 2211 | { "time": 0.6333, "angle": -3.04 }, 2212 | { "time": 0.8, "angle": 1.41 } 2213 | ], 2214 | "translate": [ 2215 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2216 | { "time": 0.4, "x": 0, "y": 0, "curve": "stepped" }, 2217 | { "time": 0.8, "x": 0, "y": 0 } 2218 | ], 2219 | "scale": [ 2220 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2221 | { "time": 0.8, "x": 1, "y": 1 } 2222 | ] 2223 | }, 2224 | "head": { 2225 | "rotate": [ 2226 | { "time": 0, "angle": 6.97 }, 2227 | { "time": 0.1666, "angle": 8.02 }, 2228 | { "time": 0.2666, "angle": 12.65 }, 2229 | { "time": 0.4, "angle": 6.97 }, 2230 | { "time": 0.5666, "angle": 8.02 }, 2231 | { "time": 0.6666, "angle": 12.65 }, 2232 | { "time": 0.8, "angle": 6.97 } 2233 | ], 2234 | "translate": [ 2235 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2236 | { "time": 0.4, "x": 0, "y": 0, "curve": "stepped" }, 2237 | { "time": 0.8, "x": 0, "y": 0 } 2238 | ], 2239 | "scale": [ 2240 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2241 | { "time": 0.8, "x": 1, "y": 1 } 2242 | ] 2243 | }, 2244 | "hip": { 2245 | "rotate": [ 2246 | { "time": 0, "angle": 0, "curve": "stepped" }, 2247 | { "time": 0.8, "angle": 0 } 2248 | ], 2249 | "translate": [ 2250 | { 2251 | "time": 0, 2252 | "x": -23.93, 2253 | "y": 3.22, 2254 | "curve": [ 0.518, 0.03, 0.807, 0.61 ] 2255 | }, 2256 | { 2257 | "time": 0.1, 2258 | "x": -23.93, 2259 | "y": -9.24, 2260 | "curve": [ 0.135, 0.33, 0.601, 0.99 ] 2261 | }, 2262 | { 2263 | "time": 0.2, 2264 | "x": -23.93, 2265 | "y": 4.35, 2266 | "curve": [ 0.204, 0.68, 0.75, 1 ] 2267 | }, 2268 | { 2269 | "time": 0.3, 2270 | "x": -23.93, 2271 | "y": 2.38, 2272 | "curve": [ 0.25, 0, 0.75, 1 ] 2273 | }, 2274 | { 2275 | "time": 0.4, 2276 | "x": -23.93, 2277 | "y": -2.5, 2278 | "curve": [ 0.692, 0.01, 0.75, 1 ] 2279 | }, 2280 | { 2281 | "time": 0.5, 2282 | "x": -23.93, 2283 | "y": -10.32, 2284 | "curve": [ 0.235, 0.77, 0.75, 1 ] 2285 | }, 2286 | { 2287 | "time": 0.6, 2288 | "x": -23.93, 2289 | "y": 4.35, 2290 | "curve": [ 0.287, 0.37, 0.718, 0.76 ] 2291 | }, 2292 | { 2293 | "time": 0.7, 2294 | "x": -23.93, 2295 | "y": 10.34, 2296 | "curve": [ 0.615, 0, 0.75, 1 ] 2297 | }, 2298 | { "time": 0.8, "x": -23.93, "y": 3.22 } 2299 | ], 2300 | "scale": [ 2301 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2302 | { "time": 0.8, "x": 1, "y": 1 } 2303 | ] 2304 | }, 2305 | "front_bracer": { 2306 | "rotate": [ 2307 | { "time": 0, "angle": 0 }, 2308 | { "time": 0.4, "angle": 20.59 }, 2309 | { "time": 0.8, "angle": 0 } 2310 | ], 2311 | "translate": [ 2312 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2313 | { "time": 0.8, "x": 0, "y": 0 } 2314 | ], 2315 | "scale": [ 2316 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2317 | { "time": 0.8, "x": 1, "y": 1 } 2318 | ] 2319 | }, 2320 | "front_foot": { 2321 | "rotate": [ 2322 | { "time": 0, "angle": 12.49 }, 2323 | { "time": 0.1, "angle": -8.34 }, 2324 | { "time": 0.2, "angle": -6.17 }, 2325 | { "time": 0.3, "angle": -0.75 }, 2326 | { "time": 0.3333, "angle": 3.89 }, 2327 | { "time": 0.4, "angle": 10.22 }, 2328 | { "time": 0.5, "angle": 11.44 }, 2329 | { "time": 0.6, "angle": -0.33 }, 2330 | { "time": 0.7, "angle": 0.15 }, 2331 | { "time": 0.8, "angle": 12.49 } 2332 | ], 2333 | "translate": [ 2334 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2335 | { "time": 0.8, "x": 0, "y": 0 } 2336 | ], 2337 | "scale": [ 2338 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2339 | { "time": 0.8, "x": 1, "y": 1 } 2340 | ] 2341 | }, 2342 | "rear_bracer": { 2343 | "rotate": [ 2344 | { "time": 0, "angle": 3.58 }, 2345 | { "time": 0.1, "angle": 5.51 }, 2346 | { "time": 0.4, "angle": -22.77 }, 2347 | { "time": 0.5, "angle": -9.65 }, 2348 | { "time": 0.8, "angle": 3.58 } 2349 | ], 2350 | "translate": [ 2351 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2352 | { "time": 0.8, "x": 0, "y": 0 } 2353 | ], 2354 | "scale": [ 2355 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2356 | { "time": 0.8, "x": 1, "y": 1 } 2357 | ] 2358 | }, 2359 | "front_fist": { 2360 | "rotate": [ 2361 | { "time": 0, "angle": -15.22 }, 2362 | { "time": 0.1, "angle": -51.4 }, 2363 | { "time": 0.4, "angle": -39.4 }, 2364 | { "time": 0.5, "angle": 19.26 }, 2365 | { "time": 0.8, "angle": -15.22 } 2366 | ], 2367 | "translate": [ 2368 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2369 | { "time": 0.8, "x": 0, "y": 0 } 2370 | ], 2371 | "scale": [ 2372 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2373 | { "time": 0.8, "x": 1, "y": 1 } 2374 | ] 2375 | }, 2376 | "gun": { 2377 | "rotate": [ 2378 | { 2379 | "time": 0, 2380 | "angle": -24.06, 2381 | "curve": [ 0.25, 0, 0.75, 1 ] 2382 | }, 2383 | { 2384 | "time": 0.1, 2385 | "angle": -10.94, 2386 | "curve": [ 0.381, 0.54, 0.742, 1 ] 2387 | }, 2388 | { 2389 | "time": 0.4, 2390 | "angle": 25.34, 2391 | "curve": [ 0.25, 0, 0.75, 1 ] 2392 | }, 2393 | { 2394 | "time": 0.6666, 2395 | "angle": -27.47, 2396 | "curve": [ 0.25, 0, 0.75, 1 ] 2397 | }, 2398 | { "time": 0.8, "angle": -24.06 } 2399 | ], 2400 | "translate": [ 2401 | { "time": 0, "x": 0, "y": 0, "curve": "stepped" }, 2402 | { "time": 0.8, "x": 0, "y": 0 } 2403 | ], 2404 | "scale": [ 2405 | { "time": 0, "x": 1, "y": 1, "curve": "stepped" }, 2406 | { "time": 0.8, "x": 1, "y": 1 } 2407 | ] 2408 | } 2409 | } 2410 | } 2411 | } 2412 | } -------------------------------------------------------------------------------- /assets/character/character.spinemodel: -------------------------------------------------------------------------------- 1 | spine_scene: "/assets/character/character.spinescene" 2 | default_animation: "" 3 | skin: "" 4 | blend_mode: BLEND_MODE_ALPHA 5 | material: "/builtins/materials/spine.material" 6 | -------------------------------------------------------------------------------- /assets/character/character.spinescene: -------------------------------------------------------------------------------- 1 | spine_json: "/assets/character/character.json" 2 | atlas: "/assets/character/character.atlas" 3 | -------------------------------------------------------------------------------- /assets/character/images/eye_indifferent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/eye_indifferent.png -------------------------------------------------------------------------------- /assets/character/images/eye_surprised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/eye_surprised.png -------------------------------------------------------------------------------- /assets/character/images/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/eyes.png -------------------------------------------------------------------------------- /assets/character/images/front_bracer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_bracer.png -------------------------------------------------------------------------------- /assets/character/images/front_fist_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_fist_closed.png -------------------------------------------------------------------------------- /assets/character/images/front_fist_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_fist_open.png -------------------------------------------------------------------------------- /assets/character/images/front_foot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_foot.png -------------------------------------------------------------------------------- /assets/character/images/front_foot_bend1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_foot_bend1.png -------------------------------------------------------------------------------- /assets/character/images/front_foot_bend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_foot_bend2.png -------------------------------------------------------------------------------- /assets/character/images/front_shin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_shin.png -------------------------------------------------------------------------------- /assets/character/images/front_thigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_thigh.png -------------------------------------------------------------------------------- /assets/character/images/front_upper_arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/front_upper_arm.png -------------------------------------------------------------------------------- /assets/character/images/goggles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/goggles.png -------------------------------------------------------------------------------- /assets/character/images/gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/gun.png -------------------------------------------------------------------------------- /assets/character/images/head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/head.png -------------------------------------------------------------------------------- /assets/character/images/mouth_grind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/mouth_grind.png -------------------------------------------------------------------------------- /assets/character/images/mouth_oooo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/mouth_oooo.png -------------------------------------------------------------------------------- /assets/character/images/mouth_smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/mouth_smile.png -------------------------------------------------------------------------------- /assets/character/images/muzzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/muzzle.png -------------------------------------------------------------------------------- /assets/character/images/neck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/neck.png -------------------------------------------------------------------------------- /assets/character/images/rear_bracer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_bracer.png -------------------------------------------------------------------------------- /assets/character/images/rear_foot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_foot.png -------------------------------------------------------------------------------- /assets/character/images/rear_foot_bend1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_foot_bend1.png -------------------------------------------------------------------------------- /assets/character/images/rear_foot_bend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_foot_bend2.png -------------------------------------------------------------------------------- /assets/character/images/rear_shin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_shin.png -------------------------------------------------------------------------------- /assets/character/images/rear_thigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_thigh.png -------------------------------------------------------------------------------- /assets/character/images/rear_upper_arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/rear_upper_arm.png -------------------------------------------------------------------------------- /assets/character/images/torso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/images/torso.png -------------------------------------------------------------------------------- /assets/character/sounds/sound_1_footstep_left.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/sounds/sound_1_footstep_left.wav -------------------------------------------------------------------------------- /assets/character/sounds/sound_2_footstep_right.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/character/sounds/sound_2_footstep_right.wav -------------------------------------------------------------------------------- /assets/loadingscreens/screen_1024x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_1024x768.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_1242x2208.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_1536x2048.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_2048x1536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_2048x1536.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_2048x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_2048x2732.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_2208x1242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_2208x1242.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_2732x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_2732x2048.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_320x480.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_640x1136.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_640x960.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_750x1334.png -------------------------------------------------------------------------------- /assets/loadingscreens/screen_768x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/assets/loadingscreens/screen_768x1024.png -------------------------------------------------------------------------------- /example/example.collection: -------------------------------------------------------------------------------- 1 | name: "example" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"gui\"\n" 7 | " component: \"/example/example.gui\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 640.0 23 | y: 340.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/example.gui: -------------------------------------------------------------------------------- 1 | script: "/example/example.gui_script" 2 | textures { 3 | name: "dirtylarry" 4 | texture: "/dirtylarry/dirtylarry.atlas" 5 | } 6 | background_color { 7 | x: 0.0 8 | y: 0.0 9 | z: 0.0 10 | w: 1.0 11 | } 12 | nodes { 13 | position { 14 | x: 231.89201 15 | y: 360.0 16 | z: 0.0 17 | w: 1.0 18 | } 19 | rotation { 20 | x: 0.0 21 | y: 0.0 22 | z: 0.0 23 | w: 1.0 24 | } 25 | scale { 26 | x: 1.0 27 | y: 1.0 28 | z: 1.0 29 | w: 1.0 30 | } 31 | size { 32 | x: 400.0 33 | y: 600.0 34 | z: 0.0 35 | w: 1.0 36 | } 37 | color { 38 | x: 1.0 39 | y: 1.0 40 | z: 1.0 41 | w: 1.0 42 | } 43 | type: TYPE_BOX 44 | blend_mode: BLEND_MODE_ALPHA 45 | texture: "dirtylarry/button_normal" 46 | id: "settings" 47 | xanchor: XANCHOR_NONE 48 | yanchor: YANCHOR_NONE 49 | pivot: PIVOT_CENTER 50 | adjust_mode: ADJUST_MODE_FIT 51 | layer: "" 52 | inherit_alpha: true 53 | slice9 { 54 | x: 12.0 55 | y: 12.0 56 | z: 12.0 57 | w: 12.0 58 | } 59 | clipping_mode: CLIPPING_MODE_NONE 60 | clipping_visible: true 61 | clipping_inverted: false 62 | alpha: 1.0 63 | template_node_child: false 64 | size_mode: SIZE_MODE_MANUAL 65 | } 66 | nodes { 67 | position { 68 | x: -31.89201 69 | y: -3.7831788 70 | z: 0.0 71 | w: 1.0 72 | } 73 | rotation { 74 | x: 0.0 75 | y: 0.0 76 | z: 0.0 77 | w: 1.0 78 | } 79 | scale { 80 | x: 1.0 81 | y: 1.0 82 | z: 1.0 83 | w: 1.0 84 | } 85 | size { 86 | x: 200.0 87 | y: 100.0 88 | z: 0.0 89 | w: 1.0 90 | } 91 | color { 92 | x: 1.0 93 | y: 1.0 94 | z: 1.0 95 | w: 1.0 96 | } 97 | type: TYPE_TEMPLATE 98 | id: "walk" 99 | parent: "settings" 100 | layer: "" 101 | inherit_alpha: true 102 | alpha: 1.0 103 | template: "/dirtylarry/button.gui" 104 | template_node_child: false 105 | } 106 | nodes { 107 | position { 108 | x: 0.0 109 | y: 0.0 110 | z: 0.0 111 | w: 1.0 112 | } 113 | rotation { 114 | x: 0.0 115 | y: 0.0 116 | z: 0.0 117 | w: 1.0 118 | } 119 | scale { 120 | x: 1.0 121 | y: 1.0 122 | z: 1.0 123 | w: 1.0 124 | } 125 | size { 126 | x: 300.0 127 | y: 88.0 128 | z: 0.0 129 | w: 1.0 130 | } 131 | color { 132 | x: 1.0 133 | y: 1.0 134 | z: 1.0 135 | w: 1.0 136 | } 137 | type: TYPE_BOX 138 | blend_mode: BLEND_MODE_ALPHA 139 | texture: "button/button_normal" 140 | id: "walk/larrybutton" 141 | xanchor: XANCHOR_NONE 142 | yanchor: YANCHOR_NONE 143 | pivot: PIVOT_CENTER 144 | adjust_mode: ADJUST_MODE_FIT 145 | parent: "walk" 146 | layer: "" 147 | inherit_alpha: true 148 | slice9 { 149 | x: 32.0 150 | y: 32.0 151 | z: 32.0 152 | w: 32.0 153 | } 154 | clipping_mode: CLIPPING_MODE_NONE 155 | clipping_visible: true 156 | clipping_inverted: false 157 | alpha: 1.0 158 | template_node_child: true 159 | size_mode: SIZE_MODE_MANUAL 160 | } 161 | nodes { 162 | position { 163 | x: 0.0 164 | y: 0.0 165 | z: 0.0 166 | w: 1.0 167 | } 168 | rotation { 169 | x: 0.0 170 | y: 0.0 171 | z: 0.0 172 | w: 1.0 173 | } 174 | scale { 175 | x: 1.0 176 | y: 1.0 177 | z: 1.0 178 | w: 1.0 179 | } 180 | size { 181 | x: 200.0 182 | y: 100.0 183 | z: 0.0 184 | w: 1.0 185 | } 186 | color { 187 | x: 1.0 188 | y: 1.0 189 | z: 1.0 190 | w: 1.0 191 | } 192 | type: TYPE_TEXT 193 | blend_mode: BLEND_MODE_ALPHA 194 | text: "WALK" 195 | font: "larryfont" 196 | id: "walk/larrylabel" 197 | xanchor: XANCHOR_NONE 198 | yanchor: YANCHOR_NONE 199 | pivot: PIVOT_CENTER 200 | outline { 201 | x: 0.0 202 | y: 0.0 203 | z: 0.0 204 | w: 1.0 205 | } 206 | shadow { 207 | x: 1.0 208 | y: 1.0 209 | z: 1.0 210 | w: 1.0 211 | } 212 | adjust_mode: ADJUST_MODE_FIT 213 | line_break: false 214 | parent: "walk/larrybutton" 215 | layer: "" 216 | inherit_alpha: true 217 | alpha: 1.0 218 | outline_alpha: 1.0 219 | shadow_alpha: 1.0 220 | overridden_fields: 8 221 | template_node_child: true 222 | text_leading: 1.0 223 | text_tracking: 0.0 224 | } 225 | nodes { 226 | position { 227 | x: -31.89201 228 | y: -103.928825 229 | z: 0.0 230 | w: 1.0 231 | } 232 | rotation { 233 | x: 0.0 234 | y: 0.0 235 | z: 0.0 236 | w: 1.0 237 | } 238 | scale { 239 | x: 1.0 240 | y: 1.0 241 | z: 1.0 242 | w: 1.0 243 | } 244 | size { 245 | x: 200.0 246 | y: 100.0 247 | z: 0.0 248 | w: 1.0 249 | } 250 | color { 251 | x: 1.0 252 | y: 1.0 253 | z: 1.0 254 | w: 1.0 255 | } 256 | type: TYPE_TEMPLATE 257 | id: "run" 258 | parent: "settings" 259 | layer: "" 260 | inherit_alpha: true 261 | alpha: 1.0 262 | template: "/dirtylarry/button.gui" 263 | template_node_child: false 264 | } 265 | nodes { 266 | position { 267 | x: 0.0 268 | y: 0.0 269 | z: 0.0 270 | w: 1.0 271 | } 272 | rotation { 273 | x: 0.0 274 | y: 0.0 275 | z: 0.0 276 | w: 1.0 277 | } 278 | scale { 279 | x: 1.0 280 | y: 1.0 281 | z: 1.0 282 | w: 1.0 283 | } 284 | size { 285 | x: 300.0 286 | y: 88.0 287 | z: 0.0 288 | w: 1.0 289 | } 290 | color { 291 | x: 1.0 292 | y: 1.0 293 | z: 1.0 294 | w: 1.0 295 | } 296 | type: TYPE_BOX 297 | blend_mode: BLEND_MODE_ALPHA 298 | texture: "button/button_normal" 299 | id: "run/larrybutton" 300 | xanchor: XANCHOR_NONE 301 | yanchor: YANCHOR_NONE 302 | pivot: PIVOT_CENTER 303 | adjust_mode: ADJUST_MODE_FIT 304 | parent: "run" 305 | layer: "" 306 | inherit_alpha: true 307 | slice9 { 308 | x: 32.0 309 | y: 32.0 310 | z: 32.0 311 | w: 32.0 312 | } 313 | clipping_mode: CLIPPING_MODE_NONE 314 | clipping_visible: true 315 | clipping_inverted: false 316 | alpha: 1.0 317 | template_node_child: true 318 | size_mode: SIZE_MODE_MANUAL 319 | } 320 | nodes { 321 | position { 322 | x: 0.0 323 | y: 0.0 324 | z: 0.0 325 | w: 1.0 326 | } 327 | rotation { 328 | x: 0.0 329 | y: 0.0 330 | z: 0.0 331 | w: 1.0 332 | } 333 | scale { 334 | x: 1.0 335 | y: 1.0 336 | z: 1.0 337 | w: 1.0 338 | } 339 | size { 340 | x: 200.0 341 | y: 100.0 342 | z: 0.0 343 | w: 1.0 344 | } 345 | color { 346 | x: 1.0 347 | y: 1.0 348 | z: 1.0 349 | w: 1.0 350 | } 351 | type: TYPE_TEXT 352 | blend_mode: BLEND_MODE_ALPHA 353 | text: "RUN" 354 | font: "larryfont" 355 | id: "run/larrylabel" 356 | xanchor: XANCHOR_NONE 357 | yanchor: YANCHOR_NONE 358 | pivot: PIVOT_CENTER 359 | outline { 360 | x: 0.0 361 | y: 0.0 362 | z: 0.0 363 | w: 1.0 364 | } 365 | shadow { 366 | x: 1.0 367 | y: 1.0 368 | z: 1.0 369 | w: 1.0 370 | } 371 | adjust_mode: ADJUST_MODE_FIT 372 | line_break: false 373 | parent: "run/larrybutton" 374 | layer: "" 375 | inherit_alpha: true 376 | alpha: 1.0 377 | outline_alpha: 1.0 378 | shadow_alpha: 1.0 379 | overridden_fields: 8 380 | template_node_child: true 381 | text_leading: 1.0 382 | text_tracking: 0.0 383 | } 384 | nodes { 385 | position { 386 | x: -31.89201 387 | y: -205.7529 388 | z: 0.0 389 | w: 1.0 390 | } 391 | rotation { 392 | x: 0.0 393 | y: 0.0 394 | z: 0.0 395 | w: 1.0 396 | } 397 | scale { 398 | x: 1.0 399 | y: 1.0 400 | z: 1.0 401 | w: 1.0 402 | } 403 | size { 404 | x: 200.0 405 | y: 100.0 406 | z: 0.0 407 | w: 1.0 408 | } 409 | color { 410 | x: 1.0 411 | y: 1.0 412 | z: 1.0 413 | w: 1.0 414 | } 415 | type: TYPE_TEMPLATE 416 | id: "idle" 417 | parent: "settings" 418 | layer: "" 419 | inherit_alpha: true 420 | alpha: 1.0 421 | template: "/dirtylarry/button.gui" 422 | template_node_child: false 423 | } 424 | nodes { 425 | position { 426 | x: 0.0 427 | y: 0.0 428 | z: 0.0 429 | w: 1.0 430 | } 431 | rotation { 432 | x: 0.0 433 | y: 0.0 434 | z: 0.0 435 | w: 1.0 436 | } 437 | scale { 438 | x: 1.0 439 | y: 1.0 440 | z: 1.0 441 | w: 1.0 442 | } 443 | size { 444 | x: 300.0 445 | y: 88.0 446 | z: 0.0 447 | w: 1.0 448 | } 449 | color { 450 | x: 1.0 451 | y: 1.0 452 | z: 1.0 453 | w: 1.0 454 | } 455 | type: TYPE_BOX 456 | blend_mode: BLEND_MODE_ALPHA 457 | texture: "button/button_normal" 458 | id: "idle/larrybutton" 459 | xanchor: XANCHOR_NONE 460 | yanchor: YANCHOR_NONE 461 | pivot: PIVOT_CENTER 462 | adjust_mode: ADJUST_MODE_FIT 463 | parent: "idle" 464 | layer: "" 465 | inherit_alpha: true 466 | slice9 { 467 | x: 32.0 468 | y: 32.0 469 | z: 32.0 470 | w: 32.0 471 | } 472 | clipping_mode: CLIPPING_MODE_NONE 473 | clipping_visible: true 474 | clipping_inverted: false 475 | alpha: 1.0 476 | template_node_child: true 477 | size_mode: SIZE_MODE_MANUAL 478 | } 479 | nodes { 480 | position { 481 | x: 0.0 482 | y: 0.0 483 | z: 0.0 484 | w: 1.0 485 | } 486 | rotation { 487 | x: 0.0 488 | y: 0.0 489 | z: 0.0 490 | w: 1.0 491 | } 492 | scale { 493 | x: 1.0 494 | y: 1.0 495 | z: 1.0 496 | w: 1.0 497 | } 498 | size { 499 | x: 200.0 500 | y: 100.0 501 | z: 0.0 502 | w: 1.0 503 | } 504 | color { 505 | x: 1.0 506 | y: 1.0 507 | z: 1.0 508 | w: 1.0 509 | } 510 | type: TYPE_TEXT 511 | blend_mode: BLEND_MODE_ALPHA 512 | text: "IDLE" 513 | font: "larryfont" 514 | id: "idle/larrylabel" 515 | xanchor: XANCHOR_NONE 516 | yanchor: YANCHOR_NONE 517 | pivot: PIVOT_CENTER 518 | outline { 519 | x: 0.0 520 | y: 0.0 521 | z: 0.0 522 | w: 1.0 523 | } 524 | shadow { 525 | x: 1.0 526 | y: 1.0 527 | z: 1.0 528 | w: 1.0 529 | } 530 | adjust_mode: ADJUST_MODE_FIT 531 | line_break: false 532 | parent: "idle/larrybutton" 533 | layer: "" 534 | inherit_alpha: true 535 | alpha: 1.0 536 | outline_alpha: 1.0 537 | shadow_alpha: 1.0 538 | overridden_fields: 8 539 | template_node_child: true 540 | text_leading: 1.0 541 | text_tracking: 0.0 542 | } 543 | nodes { 544 | position { 545 | x: 837.4093 546 | y: 20.554094 547 | z: 0.0 548 | w: 1.0 549 | } 550 | rotation { 551 | x: 0.0 552 | y: 0.0 553 | z: 0.0 554 | w: 1.0 555 | } 556 | scale { 557 | x: 1.0 558 | y: 1.0 559 | z: 1.0 560 | w: 1.0 561 | } 562 | size { 563 | x: 1.0 564 | y: 1.0 565 | z: 0.0 566 | w: 1.0 567 | } 568 | color { 569 | x: 1.0 570 | y: 1.0 571 | z: 1.0 572 | w: 1.0 573 | } 574 | type: TYPE_SPINE 575 | blend_mode: BLEND_MODE_ALPHA 576 | id: "spine" 577 | xanchor: XANCHOR_NONE 578 | yanchor: YANCHOR_NONE 579 | pivot: PIVOT_CENTER 580 | adjust_mode: ADJUST_MODE_FIT 581 | layer: "" 582 | inherit_alpha: true 583 | clipping_mode: CLIPPING_MODE_NONE 584 | clipping_visible: true 585 | clipping_inverted: false 586 | alpha: 1.0 587 | template_node_child: false 588 | size_mode: SIZE_MODE_AUTO 589 | spine_scene: "character" 590 | spine_default_animation: "" 591 | spine_skin: "" 592 | } 593 | material: "/builtins/materials/gui.material" 594 | adjust_reference: ADJUST_REFERENCE_PARENT 595 | max_nodes: 512 596 | spine_scenes { 597 | name: "character" 598 | spine_scene: "/assets/character/character.spinescene" 599 | } 600 | -------------------------------------------------------------------------------- /example/example.gui_script: -------------------------------------------------------------------------------- 1 | local ga = require "googleanalytics.ga" 2 | local dirty = require "dirtylarry.dirtylarry" 3 | 4 | local CATEGORY_SETTINGS = "settings" 5 | local ACTION_PLAY_ANIMATION = "play_animation" 6 | local SCREEN_NAME = "settings" 7 | 8 | 9 | function init(self) 10 | msg.post(".", "acquire_input_focus") 11 | ga.get_default_tracker().screenview(SCREEN_NAME) 12 | sys.set_error_handler(function(source, message, traceback) 13 | print("error", source, message, traceback) 14 | end) 15 | end 16 | 17 | function final(self) 18 | msg.post(".", "release_input_focus") 19 | end 20 | 21 | function update(self, dt) 22 | ga.update() 23 | end 24 | 25 | function on_message(self, message_id, message, sender) 26 | -- Add message-handling code here 27 | -- Remove this function if not needed 28 | end 29 | 30 | function on_input(self, action_id, action) 31 | dirty.button(self, "walk", action_id, action, function() 32 | gui.play_spine_anim(gui.get_node("spine"), hash("walk"), gui.PLAYBACK_LOOP_FORWARD) 33 | ga.get_default_tracker().event(CATEGORY_SETTINGS, ACTION_PLAY_ANIMATION, "walk") 34 | end) 35 | dirty.button(self, "run", action_id, action, function() 36 | gui.play_spine_anim(gui.get_node("spine"), hash("run"), gui.PLAYBACK_LOOP_FORWARD) 37 | ga.get_default_tracker().event(CATEGORY_SETTINGS, ACTION_PLAY_ANIMATION, "run") 38 | end) 39 | dirty.button(self, "idle", action_id, action, function() 40 | gui.play_spine_anim(gui.get_node("spine"), hash("idle"), gui.PLAYBACK_LOOP_FORWARD) 41 | ga.get_default_tracker().event(CATEGORY_SETTINGS, ACTION_PLAY_ANIMATION, "idle") 42 | end) 43 | end 44 | 45 | function on_reload(self) 46 | -- Add input-handling code here 47 | -- Remove this function if not needed 48 | end 49 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [project] 2 | title = Google Analytics 3 | version = 0.9 4 | dependencies = https://github.com/andsve/dirtylarry/archive/master.zip,https://github.com/britzl/deftest/archive/2.3.0.zip 5 | 6 | [bootstrap] 7 | main_collection = /tests/tests.collectionc 8 | 9 | [input] 10 | game_binding = /input/game.input_bindingc 11 | 12 | [display] 13 | width = 1280 14 | height = 720 15 | 16 | [physics] 17 | scale = 0.02 18 | 19 | [script] 20 | shared_state = 1 21 | 22 | [library] 23 | include_dirs = googleanalytics 24 | 25 | [googleanalytics] 26 | tracking_id = UA-87977671-1 27 | dispatch_period = 300 28 | queue_save_period = 60 29 | verbose = 1 30 | 31 | [ios] 32 | bundle_identifier = com.britzl.defold.googleanalytics 33 | 34 | [android] 35 | package = com.britzl.defold.googleanalytics 36 | 37 | [osx] 38 | bundle_identifier = com.britzl.defold.googleanalytics 39 | 40 | -------------------------------------------------------------------------------- /googleanalytics/ga.lua: -------------------------------------------------------------------------------- 1 | --- Main module for the Google Analytics implementation for the Defold game 2 | -- engine. 3 | -- 4 | -- @usage 5 | -- local ga = require "googleanalytics.ga" 6 | -- 7 | -- function init(self) 8 | -- ga.get_default_tracker().screenview("my_cool_screen") 9 | -- end 10 | -- 11 | -- function update(self, dt) 12 | -- ga.update() 13 | -- end 14 | -- 15 | -- function on_input(self, action_id, action) 16 | -- if gui.pick_node(node, action.x, action.y) and action.pressed then 17 | -- go.get_default_tracker().event("category", "action")) 18 | -- end 19 | -- end 20 | 21 | 22 | local tracker = require "googleanalytics.tracker" 23 | local queue = require "googleanalytics.internal.queue" 24 | 25 | local M = { 26 | dispatch_period = tonumber(sys.get_config("googleanalytics.dispatch_period", 30 * 60)), 27 | } 28 | 29 | local default_tracker = nil 30 | 31 | --- Get the default tracker 32 | -- @return Tracker instance 33 | function M.get_default_tracker() 34 | if not default_tracker then 35 | local tracking_id = sys.get_config("googleanalytics.tracking_id") 36 | assert(tracking_id, "You must set tracking_id in section [googleanalytics] in game.project before using this module") 37 | default_tracker = tracker.create(tracking_id) 38 | end 39 | return default_tracker 40 | end 41 | 42 | --- Dispatch hits to Google Analytics 43 | function M.dispatch() 44 | queue.dispatch() 45 | end 46 | 47 | --- Update the Google Analytics module. 48 | -- This will check if automatic dispatch of hits are enabled and if so, if it is 49 | -- time to dispatch stored hits. 50 | function M.update() 51 | -- manual dispatch only? 52 | if M.dispatch_period <= 0 then 53 | return 54 | end 55 | 56 | if not queue.last_dispatch_time or (socket.gettime() >= (queue.last_dispatch_time + M.dispatch_period)) then 57 | M.dispatch() 58 | end 59 | end 60 | 61 | 62 | return M -------------------------------------------------------------------------------- /googleanalytics/internal/file.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | --- Get the full path to a save file 5 | -- @param name Name of file to get full path for 6 | -- @return The full path to the specified file 7 | function M.get_save_file_name(name) 8 | local application_name = sys.get_config("project.title"):gsub(" ", "_") 9 | return sys.get_save_file(application_name, name) 10 | end 11 | 12 | 13 | --- Load a file from disk 14 | -- @param name Name of file to load 15 | -- @return contents File contents or nil if file could not be read 16 | -- @return error_message Error message if file could not be read 17 | function M.load(name) 18 | assert(name, "You must provide a file name") 19 | local filename = M.get_save_file_name(name) 20 | local file, err = io.open(filename, "r") 21 | if not file then 22 | return nil, err 23 | end 24 | local contents = file:read("*a") 25 | if not contents then 26 | return nil, "Unable to read file" 27 | end 28 | return contents 29 | end 30 | 31 | 32 | --- Save a file to disk 33 | -- Saves are atomic and first written to a temporary file 34 | -- If the file already exists it will be overwritten 35 | -- @param name Name of file to write data to 36 | -- @param data The data to write 37 | -- @return success 38 | -- @return error_message 39 | function M.save(name, data) 40 | assert(name, "You must provide a file name") 41 | assert(data, "You must provide some data") 42 | assert(type(data) == "string", "You can only write strings") 43 | local tmpname = M.get_save_file_name("__ga_tmp") 44 | local file, err = io.open(tmpname, "w+") 45 | if not file then 46 | return nil, err 47 | end 48 | file:write(data) 49 | file:close() 50 | 51 | local filename = M.get_save_file_name(name) 52 | os.remove(filename) 53 | return os.rename(tmpname, filename) 54 | end 55 | 56 | return M -------------------------------------------------------------------------------- /googleanalytics/internal/json_encode.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2015 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local json = { _version = "0.1.0" } 11 | 12 | ------------------------------------------------------------------------------- 13 | -- Encode 14 | ------------------------------------------------------------------------------- 15 | 16 | local encode 17 | 18 | local escape_char_map = { 19 | [ "\\" ] = "\\\\", 20 | [ "\"" ] = "\\\"", 21 | [ "\b" ] = "\\b", 22 | [ "\f" ] = "\\f", 23 | [ "\n" ] = "\\n", 24 | [ "\r" ] = "\\r", 25 | [ "\t" ] = "\\t", 26 | } 27 | 28 | local escape_char_map_inv = { [ "\\/" ] = "/" } 29 | for k, v in pairs(escape_char_map) do 30 | escape_char_map_inv[v] = k 31 | end 32 | 33 | 34 | local function escape_char(c) 35 | return escape_char_map[c] or string.format("\\u%04x", c:byte()) 36 | end 37 | 38 | 39 | local function encode_nil(val) 40 | return "null" 41 | end 42 | 43 | 44 | local function encode_table(val, stack) 45 | local res = {} 46 | stack = stack or {} 47 | 48 | -- Circular reference? 49 | if stack[val] then error("circular reference") end 50 | 51 | stack[val] = true 52 | 53 | if val[1] ~= nil or next(val) == nil then 54 | -- Treat as array -- check keys are valid and it is not sparse 55 | local n = 0 56 | for k in pairs(val) do 57 | if type(k) ~= "number" then 58 | error("invalid table: mixed or invalid key types") 59 | end 60 | n = n + 1 61 | end 62 | if n ~= #val then 63 | error("invalid table: sparse array") 64 | end 65 | -- Encode 66 | for i, v in ipairs(val) do 67 | table.insert(res, encode(v, stack)) 68 | end 69 | stack[val] = nil 70 | return "[" .. table.concat(res, ",") .. "]" 71 | 72 | else 73 | -- Treat as an object 74 | for k, v in pairs(val) do 75 | if type(k) ~= "string" then 76 | error("invalid table: mixed or invalid key types") 77 | end 78 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 79 | end 80 | stack[val] = nil 81 | return "{" .. table.concat(res, ",") .. "}" 82 | end 83 | end 84 | 85 | 86 | local function encode_string(val) 87 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 88 | end 89 | 90 | 91 | local function encode_number(val) 92 | -- Check for NaN, -inf and inf 93 | if val ~= val or val <= -math.huge or val >= math.huge then 94 | error("unexpected number value '" .. tostring(val) .. "'") 95 | end 96 | return string.format("%.14g", val) 97 | end 98 | 99 | 100 | local type_func_map = { 101 | [ "nil" ] = encode_nil, 102 | [ "table" ] = encode_table, 103 | [ "string" ] = encode_string, 104 | [ "number" ] = encode_number, 105 | [ "boolean" ] = tostring, 106 | } 107 | 108 | 109 | encode = function(val, stack) 110 | local t = type(val) 111 | local f = type_func_map[t] 112 | if f then 113 | return f(val, stack) 114 | end 115 | error("unexpected type '" .. t .. "'") 116 | end 117 | 118 | 119 | function json.encode(val) 120 | return ( encode(val) ) 121 | end 122 | 123 | 124 | ------------------------------------------------------------------------------- 125 | -- Decode 126 | ------------------------------------------------------------------------------- 127 | 128 | local parse 129 | 130 | local function create_set(...) 131 | local res = {} 132 | for i = 1, select("#", ...) do 133 | res[ select(i, ...) ] = true 134 | end 135 | return res 136 | end 137 | 138 | local space_chars = create_set(" ", "\t", "\r", "\n") 139 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 140 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 141 | local literals = create_set("true", "false", "null") 142 | 143 | local literal_map = { 144 | [ "true" ] = true, 145 | [ "false" ] = false, 146 | [ "null" ] = nil, 147 | } 148 | 149 | 150 | local function next_char(str, idx, set, negate) 151 | for i = idx, #str do 152 | if set[str:sub(i, i)] ~= negate then 153 | return i 154 | end 155 | end 156 | return #str + 1 157 | end 158 | 159 | 160 | local function decode_error(str, idx, msg) 161 | local line_count = 1 162 | local col_count = 1 163 | for i = 1, idx - 1 do 164 | col_count = col_count + 1 165 | if str:sub(i, i) == "\n" then 166 | line_count = line_count + 1 167 | col_count = 1 168 | end 169 | end 170 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 171 | end 172 | 173 | 174 | local function codepoint_to_utf8(n) 175 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 176 | local f = math.floor 177 | if n <= 0x7f then 178 | return string.char(n) 179 | elseif n <= 0x7ff then 180 | return string.char(f(n / 64) + 192, n % 64 + 128) 181 | elseif n <= 0xffff then 182 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 183 | elseif n <= 0x10ffff then 184 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 185 | f(n % 4096 / 64) + 128, n % 64 + 128) 186 | end 187 | error( string.format("invalid unicode codepoint '%x'", n) ) 188 | end 189 | 190 | 191 | local function parse_unicode_escape(s) 192 | local n1 = tonumber( s:sub(3, 6), 16 ) 193 | local n2 = tonumber( s:sub(9, 12), 16 ) 194 | -- Surrogate pair? 195 | if n2 then 196 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 197 | else 198 | return codepoint_to_utf8(n1) 199 | end 200 | end 201 | 202 | 203 | local function parse_string(str, i) 204 | local has_unicode_escape = false 205 | local has_surrogate_escape = false 206 | local has_escape = false 207 | local last 208 | for j = i + 1, #str do 209 | local x = str:byte(j) 210 | 211 | if x < 32 then 212 | decode_error(str, j, "control character in string") 213 | end 214 | 215 | if last == 92 then -- "\\" (escape char) 216 | if x == 117 then -- "u" (unicode escape sequence) 217 | local hex = str:sub(j + 1, j + 5) 218 | if not hex:find("%x%x%x%x") then 219 | decode_error(str, j, "invalid unicode escape in string") 220 | end 221 | if hex:find("^[dD][89aAbB]") then 222 | has_surrogate_escape = true 223 | else 224 | has_unicode_escape = true 225 | end 226 | else 227 | local c = string.char(x) 228 | if not escape_chars[c] then 229 | decode_error(str, j, "invalid escape char '" .. c .. "' in string") 230 | end 231 | has_escape = true 232 | end 233 | last = nil 234 | 235 | elseif x == 34 then -- '"' (end of string) 236 | local s = str:sub(i + 1, j - 1) 237 | if has_surrogate_escape then 238 | s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) 239 | end 240 | if has_unicode_escape then 241 | s = s:gsub("\\u....", parse_unicode_escape) 242 | end 243 | if has_escape then 244 | s = s:gsub("\\.", escape_char_map_inv) 245 | end 246 | return s, j + 1 247 | 248 | else 249 | last = x 250 | end 251 | end 252 | decode_error(str, i, "expected closing quote for string") 253 | end 254 | 255 | 256 | local function parse_number(str, i) 257 | local x = next_char(str, i, delim_chars) 258 | local s = str:sub(i, x - 1) 259 | local n = tonumber(s) 260 | if not n then 261 | decode_error(str, i, "invalid number '" .. s .. "'") 262 | end 263 | return n, x 264 | end 265 | 266 | 267 | local function parse_literal(str, i) 268 | local x = next_char(str, i, delim_chars) 269 | local word = str:sub(i, x - 1) 270 | if not literals[word] then 271 | decode_error(str, i, "invalid literal '" .. word .. "'") 272 | end 273 | return literal_map[word], x 274 | end 275 | 276 | 277 | local function parse_array(str, i) 278 | local res = {} 279 | local n = 1 280 | i = i + 1 281 | while 1 do 282 | local x 283 | i = next_char(str, i, space_chars, true) 284 | -- Empty / end of array? 285 | if str:sub(i, i) == "]" then 286 | i = i + 1 287 | break 288 | end 289 | -- Read token 290 | x, i = parse(str, i) 291 | res[n] = x 292 | n = n + 1 293 | -- Next token 294 | i = next_char(str, i, space_chars, true) 295 | local chr = str:sub(i, i) 296 | i = i + 1 297 | if chr == "]" then break end 298 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 299 | end 300 | return res, i 301 | end 302 | 303 | 304 | local function parse_object(str, i) 305 | local res = {} 306 | i = i + 1 307 | while 1 do 308 | local key, val 309 | i = next_char(str, i, space_chars, true) 310 | -- Empty / end of object? 311 | if str:sub(i, i) == "}" then 312 | i = i + 1 313 | break 314 | end 315 | -- Read key 316 | if str:sub(i, i) ~= '"' then 317 | decode_error(str, i, "expected string for key") 318 | end 319 | key, i = parse(str, i) 320 | -- Read ':' delimiter 321 | i = next_char(str, i, space_chars, true) 322 | if str:sub(i, i) ~= ":" then 323 | decode_error(str, i, "expected ':' after key") 324 | end 325 | i = next_char(str, i + 1, space_chars, true) 326 | -- Read value 327 | val, i = parse(str, i) 328 | -- Set 329 | res[key] = val 330 | -- Next token 331 | i = next_char(str, i, space_chars, true) 332 | local chr = str:sub(i, i) 333 | i = i + 1 334 | if chr == "}" then break end 335 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 336 | end 337 | return res, i 338 | end 339 | 340 | 341 | local char_func_map = { 342 | [ '"' ] = parse_string, 343 | [ "0" ] = parse_number, 344 | [ "1" ] = parse_number, 345 | [ "2" ] = parse_number, 346 | [ "3" ] = parse_number, 347 | [ "4" ] = parse_number, 348 | [ "5" ] = parse_number, 349 | [ "6" ] = parse_number, 350 | [ "7" ] = parse_number, 351 | [ "8" ] = parse_number, 352 | [ "9" ] = parse_number, 353 | [ "-" ] = parse_number, 354 | [ "t" ] = parse_literal, 355 | [ "f" ] = parse_literal, 356 | [ "n" ] = parse_literal, 357 | [ "[" ] = parse_array, 358 | [ "{" ] = parse_object, 359 | } 360 | 361 | 362 | parse = function(str, idx) 363 | local chr = str:sub(idx, idx) 364 | local f = char_func_map[chr] 365 | if f then 366 | return f(str, idx) 367 | end 368 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 369 | end 370 | 371 | 372 | function json.decode(str) 373 | if type(str) ~= "string" then 374 | error("expected argument of type string, got " .. type(str)) 375 | end 376 | return ( parse(str, next_char(str, 1, space_chars, true)) ) 377 | end 378 | 379 | 380 | return json -------------------------------------------------------------------------------- /googleanalytics/internal/queue.lua: -------------------------------------------------------------------------------- 1 | --- Queue of Google Analytics hit tracking 2 | -- The queue provides functionality to add data to the queue and to send the 3 | -- data to the Google Analytics servers. 4 | -- The queue is persisted to disk. It is loaded from disk when this module 5 | -- is initialised and written periodically. 6 | -- The queue will hold a configurable amount of hit data. If the limit is 7 | -- reached the oldest data will be thrown away. 8 | 9 | local file = require "googleanalytics.internal.file" 10 | local json_encode = require "googleanalytics.internal.json_encode" 11 | local user_agent = require "googleanalytics.internal.user_agent" 12 | 13 | local M = { 14 | last_dispatch_time = nil, 15 | last_save_time = nil, 16 | minimum_save_period = tonumber(sys.get_config("googleanalytics.queue_save_period", 5 * 60)), 17 | } 18 | 19 | 20 | -- payload rules, https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide 21 | local MAX_HITS_PER_PAYLOAD = 20 -- A maximum of 20 hits can be specified per request. 22 | local MAX_HIT_SIZE = 8 * 1024 -- No single hit payload can be greater than 8K bytes. 23 | local MAX_TOTAL_PAYLOAD_SIZE = 16 * 1024 -- The total size of all hit payloads cannot be greater than 16K bytes. 24 | 25 | local QUEUE_FILENAME = "__ga_queue" 26 | 27 | local q = {} 28 | 29 | local queue_data = file.load(QUEUE_FILENAME) 30 | if queue_data then 31 | local decoded_queue, err = pcall(json.decode, json) 32 | if not err then 33 | q = decoded_queue 34 | end 35 | end 36 | 37 | 38 | local function sort() 39 | table.sort(q, function(a, b) 40 | return a.time < b.time 41 | end) 42 | end 43 | 44 | 45 | function M.log(...) 46 | -- no-op 47 | end 48 | 49 | if sys.get_config("googleanalytics.verbose") == "1" then 50 | M.log = function(s, ...) 51 | if select("#", ...) > 0 then 52 | print(s:format(...)) 53 | else 54 | print(s) 55 | end 56 | end 57 | end 58 | 59 | --- Add tracking parameters to queue 60 | -- @param params 61 | function M.add(params) 62 | assert(params, "You must provide params") 63 | if #params <= MAX_HIT_SIZE then 64 | table.insert(q, { time = socket.gettime(), params = params }) 65 | end 66 | 67 | if #q > 0 and (not M.last_save_time or (socket.gettime() >= (M.last_save_time + M.minimum_save_period))) then 68 | local ok, err = pcall(function() 69 | assert(file.save(QUEUE_FILENAME, json_encode.encode(q))) 70 | end) 71 | if not ok then 72 | M.log("ERR: Something went wrong while saving Google Analytics data", err) 73 | M.dispatch() 74 | q = {} 75 | return 76 | end 77 | M.last_save_time = socket.gettime() 78 | end 79 | end 80 | 81 | --- Dispatch all stored hits to Google Analytics 82 | function M.dispatch() 83 | M.last_dispatch_time = socket.gettime() 84 | if #q == 0 then 85 | M.log("Nothing to send") 86 | return 87 | end 88 | 89 | local hits_to_retry = {} 90 | while #q > 0 do 91 | 92 | local hits_in_flight = {} 93 | local payload = {} 94 | local payload_size = 0 95 | for _=1,math.min(MAX_HITS_PER_PAYLOAD, #q) do 96 | local hit = q[1] 97 | local queue_time = math.floor((socket.gettime() - hit.time) * 1000) 98 | local qt = "&qt=" .. tostring(queue_time) 99 | local hit_size = #hit.params + #qt 100 | if payload_size + hit_size <= MAX_TOTAL_PAYLOAD_SIZE then 101 | local data = table.remove(q, 1) 102 | table.insert(hits_in_flight, data) 103 | payload[#payload + 1] = data.params .. qt 104 | payload_size = payload_size + hit_size 105 | end 106 | end 107 | 108 | if #payload > 0 then 109 | local post_data = table.concat(payload, "\n") 110 | local headers = { ["User-Agent"] = user_agent.get() } 111 | M.log("Sending %d hit(s) to Google Analytics", #hits_in_flight) 112 | http.request("https://www.google-analytics.com/batch", "POST", function(self, id, response) 113 | if response.status < 200 or response.status >= 300 then 114 | M.log("ERR: Problem when sending hits to Google Analytics. Code: ", response.status) 115 | for i=1,#hits_in_flight do 116 | hits_to_retry[#hits_to_retry + 1] = hits_in_flight[i] 117 | end 118 | end 119 | end, headers, post_data) 120 | end 121 | end 122 | 123 | -- re-add any hits that we failed to send due to http errors 124 | if #hits_to_retry > 0 then 125 | for i=1,#hits_to_retry do 126 | q[#q + 1] = hits_to_retry[i] 127 | end 128 | sort() 129 | end 130 | end 131 | 132 | 133 | 134 | return M -------------------------------------------------------------------------------- /googleanalytics/internal/user_agent.lua: -------------------------------------------------------------------------------- 1 | --- Utility module to create a reasonably well formatted user agent string based 2 | -- on the platform dmengine is running on. 3 | 4 | local M = {} 5 | 6 | --- Get a user agent string 7 | -- @return User agent string 8 | function M.get() 9 | local sys_info = sys.get_sys_info() 10 | local device_model = sys_info.device_model 11 | local manufacturer = sys_info.manufacturer 12 | local system_name = sys_info.system_name 13 | local system_version = sys_info.system_version 14 | local system_version_underscore = system_version:gsub("%.", "_") 15 | local api_version = sys_info.api_version 16 | local device_language = sys_info.device_language 17 | 18 | local engine_info = sys.get_engine_info() 19 | local engine_version = engine_info.version 20 | 21 | local user_agent 22 | if sys_info.system_name == "Android" then 23 | user_agent = ("Mozilla/5.0 (Linux; Android %s; %s %s) AppleWebKit/537.36 (KHTML, like Gecko) Defold/%s"):format(system_version, manufacturer, device_model, engine_version) 24 | elseif sys_info.system_name == "iPhone OS" then 25 | if sys_info.device_model:match("iPad.*") == sys_info.device_model then 26 | user_agent = ("Mozilla/5.0 (iPad; CPU OS %s like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Defold/%s"):format(system_version_underscore, engine_version) 27 | elseif sys_info.device_model:match("iPod.*") == sys_info.device_model then 28 | user_agent = ("Mozilla/5.0 (iPod; CPU OS %s like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Defold/%s"):format(system_version_underscore, engine_version) 29 | else 30 | user_agent = ("Mozilla/5.0 (iPhone; CPU iPhone OS %s like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Defold/%s"):format(system_version_underscore, engine_version) 31 | end 32 | elseif sys_info.system_name == "Darwin" then 33 | user_agent = ("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/602.3.12 (KHTML, like Gecko) Defold/%s"):format(system_version, engine_version) 34 | elseif sys_info.system_name == "Windows" then 35 | user_agent = ("Mozilla/5.0 (MSIE 10.0; Windows NT %s; Trident/5.0) Defold/%s"):format(system_version, engine_version) 36 | elseif sys_info.system_name == "Linux" then 37 | user_agent = ("Mozilla/5.0 (X11; Linux x86_64; Debian) AppleWebKit/537.36 (KHTML, like Gecko) Defold/%s"):format(engine_version) 38 | elseif sys_info.system_name == "HTML5" then 39 | -- modern browsers do not allow that the user agent is set manually 40 | user_agent = nil 41 | else 42 | user_agent = ("Mozilla/5.0 (%s; %s; %s %s) Defold/%s"):format(system_name, system_version, device_model, manufacturer, engine_version) 43 | end 44 | return user_agent 45 | end 46 | 47 | 48 | return M -------------------------------------------------------------------------------- /googleanalytics/internal/uuid.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------------- 2 | -- Copyright 2012 Rackspace (original), 2013 Thijs Schreijer (modifications) 3 | -- 4 | -- Licensed under the Apache License, Version 2.0 (the "License"); 5 | -- you may not use this file except in compliance with the License. 6 | -- You may obtain a copy of the License at 7 | -- 8 | -- http://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS-IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | -- 16 | -- see http://www.ietf.org/rfc/rfc4122.txt 17 | -- 18 | -- Note that this is not a true version 4 (random) UUID. Since `os.time()` precision is only 1 second, it would be hard 19 | -- to guarantee spacial uniqueness when two hosts generate a uuid after being seeded during the same second. This 20 | -- is solved by using the node field from a version 1 UUID. It represents the mac address. 21 | -- 22 | -- 28-apr-2013 modified by Thijs Schreijer from the original [Rackspace code](https://github.com/kans/zirgo/blob/807250b1af6725bad4776c931c89a784c1e34db2/util/uuid.lua) as a generic Lua module. 23 | -- Regarding the above mention on `os.time()`; the modifications use the `socket.gettime()` function from LuaSocket 24 | -- if available and hence reduce that problem (provided LuaSocket has been loaded before uuid). 25 | -- 26 | -- **6-nov-2015 Please take note of this issue**; [https://github.com/Mashape/kong/issues/478](https://github.com/Mashape/kong/issues/478) 27 | -- It demonstrates the problem of using time as a random seed. Specifically when used from multiple processes. 28 | -- So make sure to seed only once, application wide. And to not have multiple processes do that 29 | -- simultaneously (like nginx does for example). 30 | 31 | local M = {} 32 | 33 | local bitsize = 32 -- bitsize assumed for Lua VM. See randomseed function below. 34 | local lua_version = tonumber(_VERSION:match("%d%.*%d*")) -- grab Lua version used 35 | 36 | local MATRIX_AND = {{0,0},{0,1} } 37 | local MATRIX_OR = {{0,1},{1,1}} 38 | local HEXES = '0123456789abcdef' 39 | 40 | local math_floor = math.floor 41 | local math_random = math.random 42 | local math_abs = math.abs 43 | local string_sub = string.sub 44 | local to_number = tonumber 45 | local assert = assert 46 | local type = type 47 | 48 | -- performs the bitwise operation specified by truth matrix on two numbers. 49 | local function BITWISE(x, y, matrix) 50 | local z = 0 51 | local pow = 1 52 | while x > 0 or y > 0 do 53 | z = z + (matrix[x%2+1][y%2+1] * pow) 54 | pow = pow * 2 55 | x = math_floor(x/2) 56 | y = math_floor(y/2) 57 | end 58 | return z 59 | end 60 | 61 | local function INT2HEX(x) 62 | local s,base = '',16 63 | local d 64 | while x > 0 do 65 | d = x % base + 1 66 | x = math_floor(x/base) 67 | s = string_sub(HEXES, d, d)..s 68 | end 69 | while #s < 2 do s = "0" .. s end 70 | return s 71 | end 72 | 73 | ---------------------------------------------------------------------------- 74 | -- Creates a new uuid. Either provide a unique hex string, or make sure the 75 | -- random seed is properly set. The module table itself is a shortcut to this 76 | -- function, so `my_uuid = uuid.new()` equals `my_uuid = uuid()`. 77 | -- 78 | -- For proper use there are 3 options; 79 | -- 80 | -- 1. first require `luasocket`, then call `uuid.seed()`, and request a uuid using no 81 | -- parameter, eg. `my_uuid = uuid()` 82 | -- 2. use `uuid` without `luasocket`, set a random seed using `uuid.randomseed(some_good_seed)`, 83 | -- and request a uuid using no parameter, eg. `my_uuid = uuid()` 84 | -- 3. use `uuid` without `luasocket`, and request a uuid using an unique hex string, 85 | -- eg. `my_uuid = uuid(my_networkcard_macaddress)` 86 | -- 87 | -- @return a properly formatted uuid string 88 | -- @param hwaddr (optional) string containing a unique hex value (e.g.: `00:0c:29:69:41:c6`), to be used to compensate for the lesser `math_random()` function. Use a mac address for solid results. If omitted, a fully randomized uuid will be generated, but then you must ensure that the random seed is set properly! 89 | -- @usage 90 | -- local uuid = require("uuid") 91 | -- print("here's a new uuid: ",uuid()) 92 | function M.new(hwaddr) 93 | -- bytes are treated as 8bit unsigned bytes. 94 | local bytes = { 95 | math_random(0, 255), 96 | math_random(0, 255), 97 | math_random(0, 255), 98 | math_random(0, 255), 99 | math_random(0, 255), 100 | math_random(0, 255), 101 | math_random(0, 255), 102 | math_random(0, 255), 103 | math_random(0, 255), 104 | math_random(0, 255), 105 | math_random(0, 255), 106 | math_random(0, 255), 107 | math_random(0, 255), 108 | math_random(0, 255), 109 | math_random(0, 255), 110 | math_random(0, 255) 111 | } 112 | 113 | if hwaddr then 114 | assert(type(hwaddr)=="string", "Expected hex string, got "..type(hwaddr)) 115 | -- Cleanup provided string, assume mac address, so start from back and cleanup until we've got 12 characters 116 | local i,str = #hwaddr, hwaddr 117 | hwaddr = "" 118 | while i>0 and #hwaddr<12 do 119 | local c = str:sub(i,i):lower() 120 | if HEXES:find(c, 1, true) then 121 | -- valid HEX character, so append it 122 | hwaddr = c..hwaddr 123 | end 124 | i = i - 1 125 | end 126 | assert(#hwaddr == 12, "Provided string did not contain at least 12 hex characters, retrieved '"..hwaddr.."' from '"..str.."'") 127 | 128 | -- no split() in lua. :( 129 | bytes[11] = to_number(hwaddr:sub(1, 2), 16) 130 | bytes[12] = to_number(hwaddr:sub(3, 4), 16) 131 | bytes[13] = to_number(hwaddr:sub(5, 6), 16) 132 | bytes[14] = to_number(hwaddr:sub(7, 8), 16) 133 | bytes[15] = to_number(hwaddr:sub(9, 10), 16) 134 | bytes[16] = to_number(hwaddr:sub(11, 12), 16) 135 | end 136 | 137 | -- set the version 138 | bytes[7] = BITWISE(bytes[7], 0x0f, MATRIX_AND) 139 | bytes[7] = BITWISE(bytes[7], 0x40, MATRIX_OR) 140 | -- set the variant 141 | bytes[9] = BITWISE(bytes[7], 0x3f, MATRIX_AND) 142 | bytes[9] = BITWISE(bytes[7], 0x80, MATRIX_OR) 143 | return INT2HEX(bytes[1])..INT2HEX(bytes[2])..INT2HEX(bytes[3])..INT2HEX(bytes[4]).."-".. 144 | INT2HEX(bytes[5])..INT2HEX(bytes[6]).."-".. 145 | INT2HEX(bytes[7])..INT2HEX(bytes[8]).."-".. 146 | INT2HEX(bytes[9])..INT2HEX(bytes[10]).."-".. 147 | INT2HEX(bytes[11])..INT2HEX(bytes[12])..INT2HEX(bytes[13])..INT2HEX(bytes[14])..INT2HEX(bytes[15])..INT2HEX(bytes[16]) 148 | end 149 | 150 | ---------------------------------------------------------------------------- 151 | -- Improved randomseed function. 152 | -- Lua 5.1 and 5.2 both truncate the seed given if it exceeds the integer 153 | -- range. If this happens, the seed will be 0 or 1 and all randomness will 154 | -- be gone (each application run will generate the same sequence of random 155 | -- numbers in that case). This improved version drops the most significant 156 | -- bits in those cases to get the seed within the proper range again. 157 | -- @param seed the random seed to set (integer from 0 - 2^32, negative values will be made positive) 158 | -- @return the (potentially modified) seed used 159 | -- @usage 160 | -- local socket = require("socket") -- gettime() has higher precision than os.time() 161 | -- local uuid = require("uuid") 162 | -- -- see also example at uuid.seed() 163 | -- uuid.randomseed(socket.gettime()*10000) 164 | -- print("here's a new uuid: ",uuid()) 165 | function M.randomseed(seed) 166 | seed = math_floor(math_abs(seed)) 167 | if seed >= (2^bitsize) then 168 | -- integer overflow, so reduce to prevent a bad seed 169 | seed = seed - math_floor(seed / 2^bitsize) * (2^bitsize) 170 | end 171 | if lua_version < 5.2 then 172 | -- 5.1 uses (incorrect) signed int 173 | math.randomseed(seed - 2^(bitsize-1)) 174 | else 175 | -- 5.2 uses (correct) unsigned int 176 | math.randomseed(seed) 177 | end 178 | return seed 179 | end 180 | 181 | ---------------------------------------------------------------------------- 182 | -- Seeds the random generator. 183 | -- It does so in 2 possible ways; 184 | -- 185 | -- 1. use `os.time()`: this only offers resolution to one second (used when 186 | -- LuaSocket hasn't been loaded yet 187 | -- 2. use luasocket `gettime()` function, but it only does so when LuaSocket 188 | -- has been required already. 189 | -- @usage 190 | -- local socket = require("socket") -- gettime() has higher precision than os.time() 191 | -- -- LuaSocket loaded, so below line does the same as the example from randomseed() 192 | -- uuid.seed() 193 | -- print("here's a new uuid: ",uuid()) 194 | function M.seed() 195 | if package.loaded["socket"] and package.loaded["socket"].gettime then 196 | return M.randomseed(package.loaded["socket"].gettime()*10000) 197 | else 198 | return M.randomseed(os.time()) 199 | end 200 | end 201 | 202 | return setmetatable( M, { __call = function(self, hwaddr) return self.new(hwaddr) end} ) 203 | -------------------------------------------------------------------------------- /googleanalytics/tracker.lua: -------------------------------------------------------------------------------- 1 | local uuid_generator = require "googleanalytics.internal.uuid" 2 | local queue = require "googleanalytics.internal.queue" 3 | local file = require "googleanalytics.internal.file" 4 | 5 | local M = {} 6 | 7 | local UUID_FILENAME = "__ga_uuid" 8 | 9 | local function url_encode(str) 10 | if (str) then 11 | str = string.gsub (str, "\n", "\r\n") 12 | str = string.gsub (str, "([^%w %-%_%.%~])", function (c) return string.format ("%%%02X", string.byte(c)) end) 13 | str = string.gsub (str, " ", "+") 14 | end 15 | return str 16 | end 17 | 18 | local function url_decode(str) 19 | str = string.gsub (str, "+", " ") 20 | str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end) 21 | str = string.gsub (str, "\r\n", "\n") 22 | return str 23 | end 24 | 25 | 26 | local function get_uuid() 27 | local uuid, err = file.load(UUID_FILENAME) 28 | if not uuid then 29 | uuid_generator.seed() 30 | uuid = uuid_generator() 31 | file.save(UUID_FILENAME, uuid) 32 | end 33 | return uuid 34 | end 35 | 36 | local function get_application_name() 37 | return sys.get_config("project.title"):gsub(" ", "_") 38 | end 39 | 40 | local function get_application_id() 41 | local APPLICATION_IDS = { 42 | ["Android"] = sys.get_config("android.package"), 43 | ["iPhone OS"] = sys.get_config("ios.bundle_identifier"), 44 | ["Darwin"] = sys.get_config("osx.bundle_identifier"), 45 | } 46 | local system_name = sys.get_sys_info().system_name 47 | return APPLICATION_IDS[system_name] or (get_application_name() .. system_name) 48 | end 49 | 50 | 51 | --- Create a tracker instance 52 | -- @param tracking_id Tracking id from the Google Analytics admin dashboard 53 | -- @return Tracker instance 54 | function M.create(tracking_id) 55 | local tracker = { 56 | -- https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters 57 | base_params = "v=1&ds=app" 58 | .. "&cid=" .. get_uuid() 59 | .. "&tid=" .. tracking_id 60 | .. "&vp=" .. sys.get_config("display.width") .. "x" .. sys.get_config("display.height") 61 | .. "&ul=" .. sys.get_sys_info().device_language 62 | .. "&an=" .. url_encode(get_application_name()) 63 | .. "&aid=" .. url_encode(get_application_id()) 64 | .. "&av=" .. (sys.get_config("project.version") or "1.0"), 65 | } 66 | 67 | local event_params = tracker.base_params .. "&t=event" 68 | local timing_params = tracker.base_params .. "&t=timing" 69 | local screenview_params = tracker.base_params .. "&t=screenview" 70 | local exception_params = tracker.base_params .. "&t=exception" 71 | 72 | --- Enable or disable crash reporting 73 | -- If enabled the tracker will automatically get Lua soft crashes using 74 | -- the sys.set_error_handler() function and track them as non-fatal crashes. 75 | -- It will also, when called, load any previous hard crash using 76 | -- crash.load_previous() and track that as a fatal crash. 77 | -- @param enabled Set to true to enable automatic crash reporting 78 | -- @param on_soft_crash Optional callback to invoke when a soft crash is detected 79 | -- @param on_hard_crash Optional callback to invoke when a hard crash is detected 80 | function tracker.enable_crash_reporting(enabled, on_soft_crash, on_hard_crash) 81 | if enabled then 82 | sys.set_error_handler(function(source, message, traceback) 83 | tracker.exception(message, false) 84 | if on_soft_crash then 85 | on_soft_crash(source, message, traceback) 86 | end 87 | end) 88 | local handle = crash.load_previous() 89 | if handle then 90 | tracker.exception(crash.get_extra_data(handle), true) 91 | if on_hard_crash then 92 | on_hard_crash(handle) 93 | end 94 | crash.release(handle) 95 | end 96 | else 97 | sys.set_error_handler(function() end) 98 | end 99 | end 100 | 101 | --- Raw tracking 102 | -- @params params A valid Google Analytics parameter string 103 | function tracker.raw(params) 104 | assert(params and type(params) == "string", "You must provide some params (of type string)") 105 | queue.add(params) 106 | end 107 | 108 | --- Exception tracking 109 | -- @param description Specifies the description of an exception. Optional. 110 | -- @param is_fatal Specifies whether the exception was fatal. Optional. 111 | function tracker.exception(description, is_fatal) 112 | assert(not description or type(description) == "string", "Description must be nil or of type string") 113 | assert(is_fatal == nil or type(is_fatal) == "boolean", "Is_fatal must be nil or of type boolean") 114 | queue.add(exception_params .. (is_fatal ~= nil and ("&exf=" .. (is_fatal and "1" or "0")) or "") .. (description and ("&exd=" .. url_encode(description)) or "")) 115 | end 116 | 117 | --- Event tracking 118 | -- @param category Specifies the event category. Must not be empty. 119 | -- @param action Specifies the event action. Must not be empty. 120 | -- @param label Specifies the event label. Optional. 121 | -- @param value Specifies the event value. Optional. Non-negative. 122 | function tracker.event(category, action, label, value) 123 | assert(category and type(category) == "string" and #category > 0, "You must provide a category (of type string, must not be empty)") 124 | assert(action and type(action) == "string" and #action > 0, "You must provide an action (of type string, must not be empty)") 125 | assert(not label or type(label) == "string", "Label must be nil or of type string") 126 | assert(not value or (type(value) == "number" and value >= 0), "Value must be nil or a positive number") 127 | queue.add(event_params .. "&ec=" .. url_encode(category) .. "&ea=" .. url_encode(action) .. (label and ("&el=" .. url_encode(label)) or "") .. (value and ("&ev=" .. tostring(value)) or "")) 128 | end 129 | 130 | --- Screenview 131 | -- @param screen_name Specified the screen name of a screenview hit 132 | function tracker.screenview(screen_name) 133 | assert(screen_name and type(screen_name) == "string", "You must specify a screen name (of type string)") 134 | queue.add(screenview_params .. "&cd=" .. url_encode(screen_name)) 135 | end 136 | 137 | --- User timing 138 | -- @param category Specifies the user timing category 139 | -- @param variable Specifies the user timing variable 140 | -- @param time Specifies the user timing value (milliseconds) 141 | -- @param label Specifies the user timing label. Optional. 142 | function tracker.timing(category, variable, time, label) 143 | assert(category and type(category) == "string", "You must provide a category (of type string)") 144 | assert(variable and type(variable) == "string", "You must provide a variable (of type string)") 145 | assert(time and type(time) == "number" and time >= 0, "You must provide a time (as a positive number)") 146 | assert(not label or type(label) == "string", "Label must be nil or a string") 147 | queue.add(timing_params .. "&utc=" .. url_encode(category) .. "&utv=" .. url_encode(variable) .. "&utt=" .. tostring(time) .. (label and ("&utl=" .. url_encode(label)) or "")) 148 | end 149 | 150 | return tracker 151 | end 152 | 153 | 154 | return M 155 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "touch" 4 | } 5 | text_trigger { 6 | input: TEXT 7 | action: "text" 8 | } 9 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/britzl/defold-googleanalytics/4da1097d58620ef22be7f62c9168d2a11c2c520e/logo.png -------------------------------------------------------------------------------- /tests/test_file.lua: -------------------------------------------------------------------------------- 1 | local mock_fs = require "deftest.mock.fs" 2 | local file = require "googleanalytics.internal.file" 3 | 4 | return function() 5 | describe("file", function() 6 | local file 7 | 8 | before(function() 9 | mock_fs.mock() 10 | file = require "googleanalytics.internal.file" 11 | end) 12 | 13 | after(function() 14 | mock_fs.unmock() 15 | package.loaded["googleanalytics.internal.file"] = nil 16 | end) 17 | 18 | it("should be able to provide a full path to a save file", function() 19 | assert(file.get_save_file_name("foobar")) 20 | end) 21 | 22 | it("should be able write to a file", function() 23 | local name1 = "foo" 24 | local name2 = "bar" 25 | file.save(name1, "some data") 26 | file.save(name2, "some other data") 27 | 28 | assert(mock_fs.has_file(file.get_save_file_name(name1))) 29 | assert(mock_fs.has_file(file.get_save_file_name(name2))) 30 | assert(mock_fs.get_file(file.get_save_file_name(name1)) == "some data") 31 | assert(mock_fs.get_file(file.get_save_file_name(name2)) == "some other data") 32 | end) 33 | 34 | it("should write to a temporary file and then move it if successful", function() 35 | file.save("foobar", "some data") 36 | 37 | assert(os.rename.calls == 1) 38 | assert(os.remove.calls == 1) 39 | end) 40 | 41 | it("should not write a partial file to disk", function() 42 | file.save("foobar", "some data") 43 | mock_fs.fail_writes(true) 44 | assert_error(function() file.save("foobar", "some other data") end) 45 | 46 | assert(mock_fs.get_file(file.get_save_file_name("foobar")) == "some data") 47 | end) 48 | 49 | it("should be able to load an existing file", function() 50 | local name1 = "foo" 51 | local name2 = "bar" 52 | file.save(name1, "some data") 53 | file.save(name2, "some other data") 54 | 55 | assert(file.load(name1) == "some data") 56 | assert(file.load(name2) == "some other data") 57 | end) 58 | 59 | it("should not crash when loading a file that does not exist", function() 60 | local data, err = file.load("foobar") 61 | assert(not data and err) 62 | end) 63 | end) 64 | end -------------------------------------------------------------------------------- /tests/test_ga.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local mock_time = require "deftest.mock.time" 4 | local file = require "googleanalytics.internal.file" 5 | 6 | return function() 7 | local ga 8 | local queue 9 | local tracker 10 | 11 | describe("ga", function() 12 | local mocked_sys_config_values = {} 13 | local function mock_sys_config_value(key, value) 14 | mocked_sys_config_values[key] = value 15 | end 16 | 17 | before(function() 18 | mock.mock(sys) 19 | mock_fs.mock() 20 | mock_time.mock() 21 | 22 | ga = require "googleanalytics.ga" 23 | tracker = require "googleanalytics.tracker" 24 | queue = require "googleanalytics.internal.queue" 25 | mock.mock(queue) 26 | mock.mock(tracker) 27 | --queue.dispatch.replace(function() end) 28 | sys.get_config.replace(function(key, default) 29 | local value = mocked_sys_config_values[key] 30 | if value and type(value) == "string" then 31 | return value 32 | elseif value ~= nil and type(value) ~= "string" then 33 | return default 34 | else 35 | return sys.get_config.original(key, default) 36 | end 37 | end) 38 | end) 39 | 40 | after(function() 41 | mock.unmock(sys) 42 | mock.unmock(queue) 43 | mock.unmock(tracker) 44 | mock_fs.unmock() 45 | mock_time.unmock() 46 | 47 | package.loaded["googleanalytics.internal.queue"] = nil 48 | package.loaded["googleanalytics.ga"] = nil 49 | package.loaded["googleanalytics.tracker"] = nil 50 | end) 51 | 52 | it("has a default tracker instance", function() 53 | local t = ga.get_default_tracker() 54 | assert(t) 55 | assert(ga.get_default_tracker() == t) 56 | end) 57 | 58 | it("should read tracking id from game.project", function() 59 | mock_sys_config_value("googleanalytics.tracking_id", "UA-123456-7") 60 | 61 | local t = ga.get_default_tracker() 62 | assert(t) 63 | assert(tracker.create.params[1] == "UA-123456-7") 64 | end) 65 | 66 | it("should read dispatch period from game.project", function() 67 | mock_sys_config_value("googleanalytics.dispatch_period", "12345") 68 | 69 | package.loaded["googleanalytics.ga"] = nil 70 | ga = require "googleanalytics.ga" 71 | assert(ga.dispatch_period == 12345) 72 | end) 73 | 74 | it("should use a default dispatch period if none is provided game.project", function() 75 | mock_sys_config_value("googleanalytics.dispatch_period", false) 76 | 77 | package.loaded["googleanalytics.ga"] = nil 78 | ga = require "googleanalytics.ga" 79 | assert(ga.dispatch_period == 30 * 60) 80 | end) 81 | 82 | it("should let the queue dispatch hits at regular intervals", function() 83 | mock_time.set(0) 84 | ga.update() 85 | assert(queue.dispatch.calls == 1) 86 | ga.update() 87 | assert(queue.dispatch.calls == 1) 88 | 89 | mock_time.elapse(ga.dispatch_period) 90 | 91 | ga.update() 92 | assert(queue.dispatch.calls == 2) 93 | end) 94 | 95 | it("should not automatically dispatch hits if set to manually dispatch them", function() 96 | ga.dispatch_period = 0 97 | ga.update() 98 | assert(queue.dispatch.calls == 0) 99 | end) 100 | end) 101 | end -------------------------------------------------------------------------------- /tests/test_queue.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local mock_time = require "deftest.mock.time" 4 | local file = require "googleanalytics.internal.file" 5 | 6 | return function() 7 | local queue 8 | local http_history 9 | local http_status = 200 10 | 11 | describe("queue", function() 12 | before(function() 13 | http_history = {} 14 | http_status = 200 15 | mock.mock(http) 16 | mock_fs.mock() 17 | mock_time.mock() 18 | http.request.replace(function(url, method, callback, headers, post_data, options) 19 | table.insert(http_history, { url = url, method = method, callback = callback, headers = headers, post_data = post_data, options = options }) 20 | if callback then 21 | callback({}, "id", { status = http_status, response = "", headers = {} }) 22 | end 23 | end) 24 | queue = require "googleanalytics.internal.queue" 25 | end) 26 | 27 | after(function() 28 | mock.unmock(http) 29 | mock_fs.unmock() 30 | mock_time.unmock() 31 | package.loaded["googleanalytics.internal.queue"] = nil 32 | end) 33 | 34 | it("should be able to add params", function() 35 | queue.add("foo=bar") 36 | end) 37 | 38 | it("should be saving added params to disk", function() 39 | mock_time.set(100) 40 | local filename = file.get_save_file_name("__ga_queue") 41 | queue.add("foo=bar") 42 | assert(mock_fs.has_file(filename)) 43 | assert(queue.last_save_time == 100) 44 | 45 | -- nothing will happen the next time we add something 46 | queue.add("foo=car") 47 | assert(queue.last_save_time == 100) 48 | mock_time.elapse(queue.minimum_save_period) 49 | 50 | -- and nothing will happen even if we elapsed the time 51 | assert(queue.last_save_time == 100) 52 | 53 | -- we need to add something more before the queue is written to disk 54 | queue.add("foo=dar") 55 | assert(queue.last_save_time == 100 + queue.minimum_save_period) 56 | end) 57 | 58 | it("should be sending added params to Google Analytics", function() 59 | queue.add("a=b") 60 | queue.add("c=d") 61 | queue.add("e=f") 62 | assert(#http_history == 0) 63 | queue.dispatch() 64 | assert(#http_history == 1) 65 | assert(http_history[1].post_data == "a=b&qt=0\nc=d&qt=0\ne=f&qt=0") 66 | end) 67 | 68 | it("should add queue time before sending to Google Analytics", function() 69 | mock_time.set(100) 70 | queue.add("a=b") 71 | mock_time.set(110) 72 | queue.add("c=d") 73 | mock_time.set(120) 74 | queue.add("e=f") 75 | 76 | mock_time.elapse(300) 77 | queue.dispatch() 78 | assert(#http_history == 1) 79 | assert(http_history[1].post_data == "a=b&qt=320000\nc=d&qt=310000\ne=f&qt=300000") 80 | end) 81 | 82 | it("should not accept very large param strings", function() 83 | local filename = file.get_save_file_name("__ga_queue") 84 | 85 | -- this first very large hit should be completely ignored 86 | local large = ("x"):rep(1 + 8 * 1024) 87 | queue.add(large) 88 | assert(not mock_fs.has_file(filename)) 89 | assert(not queue.last_save_time) 90 | 91 | -- a small hit should be writte to disk 92 | queue.add("a=b") 93 | assert(mock_fs.has_file(filename)) 94 | assert(queue.last_save_time) 95 | 96 | -- the small hit should be sent to Google 97 | queue.dispatch() 98 | assert(#http_history == 1) 99 | assert(http_history[1].post_data == "a=b&qt=0") 100 | end) 101 | 102 | it("should limit the size of the total http payload", function() 103 | local large = "a=" .. ("x"):rep(8000) 104 | queue.add(large) -- first batch 105 | queue.add(large) -- first batch 106 | queue.add(large) -- second batch 107 | 108 | queue.dispatch() 109 | 110 | assert(#http_history == 2) 111 | assert(http_history[1].post_data == large .. "&qt=0\n" .. large .. "&qt=0") 112 | assert(http_history[2].post_data == large .. "&qt=0") 113 | end) 114 | 115 | it("should send 20 params per http request", function() 116 | local post_data1 = {} 117 | for i=1,20 do 118 | queue.add("a=b") 119 | table.insert(post_data1, "a=b&qt=0") 120 | end 121 | local post_data2 = {} 122 | for i=1,10 do 123 | queue.add("a=b") 124 | table.insert(post_data2, "a=b&qt=0") 125 | end 126 | 127 | queue.dispatch() 128 | assert(#http_history == 2) 129 | assert(http_history[1].post_data == table.concat(post_data1, "\n")) 130 | assert(http_history[2].post_data == table.concat(post_data2, "\n")) 131 | end) 132 | 133 | it("should not throw away data when http request fails", function() 134 | queue.add("a=b") 135 | queue.add("c=d") 136 | queue.add("e=f") 137 | 138 | http_status = 500 139 | queue.dispatch() 140 | assert(#http_history == 1) 141 | assert(http_history[1].post_data == "a=b&qt=0\nc=d&qt=0\ne=f&qt=0") 142 | 143 | mock_time.elapse(300) 144 | queue.add("g=h") 145 | http_status = 200 146 | queue.dispatch() 147 | assert(#http_history == 2) 148 | assert(http_history[2].post_data == "a=b&qt=300000\nc=d&qt=300000\ne=f&qt=300000\ng=h&qt=0") 149 | end) 150 | end) 151 | end -------------------------------------------------------------------------------- /tests/test_tracker.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local mock_fs = require "deftest.mock.fs" 3 | local file = require "googleanalytics.internal.file" 4 | 5 | 6 | return function() 7 | local tracker 8 | local queue 9 | local queue_params 10 | 11 | local function split_params(hit) 12 | local params = {} 13 | for param in hit:gmatch("[^&]+") do 14 | local k,v = param:match("(.*)=(.*)") 15 | params[k] = v 16 | end 17 | return params 18 | end 19 | 20 | describe("tracker", function() 21 | before(function() 22 | mock_fs.mock() 23 | mock.mock(sys) 24 | tracker = require "googleanalytics.tracker" 25 | queue = require "googleanalytics.internal.queue" 26 | 27 | queue_params = {} 28 | mock.mock(queue) 29 | queue.add.replace(function(params) 30 | table.insert(queue_params, params) 31 | end) 32 | end) 33 | 34 | after(function() 35 | mock_fs.unmock() 36 | mock.unmock(queue) 37 | mock.unmock(sys) 38 | package.loaded["googleanalytics.tracker"] = nil 39 | package.loaded["googleanalytics.internal.queue"] = nil 40 | end) 41 | 42 | it("should be able to create new instances", function() 43 | assert_error(function() tracker.create() end) 44 | 45 | local instance1 = tracker.create("UA-87977671-1") 46 | local instance2 = tracker.create("UA-87977671-1") 47 | assert(instance1 ~= instance2) 48 | end) 49 | 50 | it("should require a tracking id when created", function() 51 | assert_error(function() tracker.create() end) 52 | end) 53 | 54 | it("should persist an uuid", function() 55 | local t = tracker.create("UA-87977671-1") 56 | local filename = file.get_save_file_name("__ga_uuid") 57 | assert(mock_fs.has_file(filename)) 58 | local uuid = mock_fs.get_file(filename) 59 | 60 | local t = tracker.create("UA-87977671-1") 61 | assert(mock_fs.get_file(filename) == uuid) 62 | end) 63 | 64 | it("should have some base parameters", function() 65 | local t = tracker.create("UA-87977671-1") 66 | local params = split_params(t.base_params) 67 | local sys_info = sys.get_sys_info() 68 | assert(params.v == "1") 69 | assert(params.ds == "app") 70 | assert(params.vp == sys.get_config("display.width") .. "x" .. sys.get_config("display.height")) 71 | assert(params.ul == sys.get_sys_info().device_language) 72 | assert(params.tid == "UA-87977671-1") 73 | assert(params.av == sys.get_config("project.version")) 74 | assert(params.aid) 75 | assert(params.an) 76 | assert(params.cid:match("%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x") == params.cid) 77 | end) 78 | 79 | it("should be able to add raw hits", function() 80 | local t = tracker.create("UA-87977671-1") 81 | assert_error(function() t.raw() end) 82 | 83 | t.raw("a=b&c=d") 84 | assert(#queue_params == 1) 85 | assert(queue_params[1] == "a=b&c=d") 86 | end) 87 | 88 | it("should be able to add events", function() 89 | local t = tracker.create("UA-87977671-1") 90 | assert_error(function() t.event(nil, "action1") end) 91 | assert_error(function() t.event("category1") end) 92 | assert_error(function() t.event("", "action1") end) 93 | assert_error(function() t.event("category1", "") end) 94 | assert_error(function() t.event("category1", "action1", nil, -1) end) 95 | 96 | t.event("category1", "action1") 97 | t.event("category2", "action2", "label2") 98 | t.event("category3", "action3", "label3", 150) 99 | assert(#queue_params == 3) 100 | assert(queue_params[1] == t.base_params .. "&t=event&ec=category1&ea=action1") 101 | assert(queue_params[2] == t.base_params .. "&t=event&ec=category2&ea=action2&el=label2") 102 | assert(queue_params[3] == t.base_params .. "&t=event&ec=category3&ea=action3&el=label3&ev=150") 103 | end) 104 | 105 | it("should be able to add exceptions", function() 106 | local t = tracker.create("UA-87977671-1") 107 | t.exception() 108 | t.exception(nil, true) 109 | t.exception("description1") 110 | t.exception("description2", false) 111 | t.exception("description3", true) 112 | assert(#queue_params == 5) 113 | assert(queue_params[1] == t.base_params .. "&t=exception") 114 | assert(queue_params[2] == t.base_params .. "&t=exception&exf=1") 115 | assert(queue_params[3] == t.base_params .. "&t=exception&exd=description1") 116 | assert(queue_params[4] == t.base_params .. "&t=exception&exf=0&exd=description2") 117 | assert(queue_params[5] == t.base_params .. "&t=exception&exf=1&exd=description3") 118 | end) 119 | 120 | it("should be able to add screen views", function() 121 | local t = tracker.create("UA-87977671-1") 122 | assert_error(function() t.screenview() end) 123 | 124 | t.screenview("screen1") 125 | t.screenview("screen2") 126 | assert(#queue_params == 2) 127 | assert(queue_params[1] == t.base_params .. "&t=screenview&cd=screen1") 128 | assert(queue_params[2] == t.base_params .. "&t=screenview&cd=screen2") 129 | end) 130 | 131 | it("should be able to add timings", function() 132 | local t = tracker.create("UA-87977671-1") 133 | assert_error(function() t.timing(nil, "variable1", 10) end) 134 | assert_error(function() t.timing("category1", nil, 10) end) 135 | assert_error(function() t.timing("category1", "variable1") end) 136 | assert_error(function() t.timing("category1", "variable1", -1) end) 137 | 138 | t.timing("category1", "variable1", 10) 139 | t.timing("category2", "variable2", 20, "label2") 140 | assert(#queue_params == 2) 141 | assert(queue_params[1] == t.base_params .. "&t=timing&utc=category1&utv=variable1&utt=10") 142 | assert(queue_params[2] == t.base_params .. "&t=timing&utc=category2&utv=variable2&utt=20&utl=label2") 143 | end) 144 | 145 | it("should be able to enable automatic hard crash reporting", function() 146 | if not crash then 147 | return 148 | end 149 | local t = tracker.create("UA-87977671-1") 150 | crash.write_dump() 151 | t.enable_crash_reporting(true) 152 | 153 | assert(#queue_params == 1) 154 | local params = split_params(queue_params[1]) 155 | assert(params.exf == "1") -- fatal 156 | assert(params.exd) 157 | end) 158 | 159 | it("should be able to enable automatic soft crash reporting", function() 160 | if not crash then 161 | return 162 | end 163 | local soft_crash 164 | sys.set_error_handler.replace(function(handler) 165 | soft_crash = handler 166 | end) 167 | 168 | local t = tracker.create("UA-87977671-1") 169 | t.enable_crash_reporting(true) 170 | soft_crash("lua", "message", "traceback") 171 | 172 | assert(#queue_params == 1) 173 | local params = split_params(queue_params[1]) 174 | assert(params.exf == "0") -- non-fatal 175 | assert(params.exd == "message") 176 | assert(params.exd) 177 | end) 178 | 179 | it("should be able to forward hard crashes when automatic crash reporting is enabled", function() 180 | if not crash then 181 | return 182 | end 183 | 184 | local on_hard_crash_invoked = false 185 | local function on_hard_crash() on_hard_crash_invoked = true end 186 | 187 | local t = tracker.create("UA-87977671-1") 188 | crash.write_dump() 189 | t.enable_crash_reporting(true, nil, on_hard_crash) 190 | 191 | assert(#queue_params == 1) 192 | local params = split_params(queue_params[1]) 193 | assert(params.exf == "1") 194 | assert(params.exd) 195 | assert(on_hard_crash_invoked) 196 | end) 197 | 198 | it("should be able to forward soft crashes when automatic crash reporting is enabled", function() 199 | if not crash then 200 | return 201 | end 202 | 203 | local on_soft_crash_invoked = false 204 | local function on_soft_crash() on_soft_crash_invoked = true end 205 | 206 | local soft_crash 207 | sys.set_error_handler.replace(function(handler) 208 | soft_crash = handler 209 | end) 210 | 211 | local t = tracker.create("UA-87977671-1") 212 | t.enable_crash_reporting(true, on_soft_crash, nil) 213 | soft_crash("lua", "message", "traceback") 214 | 215 | assert(#queue_params == 1) 216 | local params = split_params(queue_params[1]) 217 | assert(params.exf == "0") -- non-fatal 218 | assert(params.exd == "message") 219 | assert(params.exd) 220 | assert(on_soft_crash_invoked) 221 | end) 222 | end) 223 | end -------------------------------------------------------------------------------- /tests/test_user_agent.lua: -------------------------------------------------------------------------------- 1 | local mock = require "deftest.mock.mock" 2 | local user_agent = require "googleanalytics.internal.user_agent" 3 | 4 | return function() 5 | describe("user_agent", function() 6 | 7 | local mocked_values 8 | 9 | before(function() 10 | mock.mock(sys) 11 | mocked_values = {} 12 | sys.get_sys_info.replace(function() 13 | local sys_info = sys.get_sys_info.original() 14 | for k,v in pairs(mocked_values) do 15 | sys_info[k] = v 16 | end 17 | return sys_info 18 | end) 19 | end) 20 | 21 | after(function() 22 | mock.unmock(sys) 23 | end) 24 | 25 | it("should be able to provide user agent strings for all platforms", function() 26 | mocked_values["system_name"] = "iPhone OS" 27 | mocked_values["device_model"] = "iPhone5,0" 28 | local uas = user_agent.get() 29 | assert(uas and uas:match("Mozilla%/5%.0 %(iPhone;.*") == uas) 30 | 31 | mocked_values["system_name"] = "iPhone OS" 32 | mocked_values["device_model"] = "iPad5,0" 33 | local uas = user_agent.get() 34 | assert(uas and uas:match("Mozilla%/5%.0 %(iPad;.*") == uas) 35 | 36 | mocked_values["system_name"] = "iPhone OS" 37 | mocked_values["device_model"] = "iPod5,0" 38 | local uas = user_agent.get() 39 | assert(uas and uas:match("Mozilla%/5%.0 %(iPod;.*") == uas) 40 | 41 | mocked_values["system_name"] = "Android" 42 | mocked_values["device_model"] = nil 43 | local uas = user_agent.get() 44 | assert(uas and uas:match("Mozilla%/5%.0 %(Linux;.*") == uas) 45 | 46 | mocked_values["system_name"] = "Darwin" 47 | mocked_values["device_model"] = nil 48 | local uas = user_agent.get() 49 | assert(uas and uas:match("Mozilla%/5%.0 %(Macintosh;.*") == uas) 50 | 51 | mocked_values["system_name"] = "Darwin" 52 | mocked_values["device_model"] = nil 53 | local uas = user_agent.get() 54 | assert(uas and uas:match("Mozilla%/5%.0 %(Macintosh;.*") == uas) 55 | end) 56 | 57 | it("should provide no user agent string for HTML5", function() 58 | mocked_values["system_name"] = "HTML5" 59 | mocked_values["device_model"] = nil 60 | local uas = user_agent.get() 61 | assert(not uas) 62 | end) 63 | 64 | it("should be able to handle unknown system names", function() 65 | mocked_values["system_name"] = "Foobar" 66 | mocked_values["device_model"] = nil 67 | local uas = user_agent.get() 68 | assert(uas and uas:match("Mozilla%/5%.0 %(Foobar;.*") == uas) 69 | end) 70 | end) 71 | end -------------------------------------------------------------------------------- /tests/test_uuid.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local uuid_generator 3 | 4 | describe("uuid", function() 5 | before(function() 6 | uuid_generator = require "googleanalytics.internal.uuid" 7 | end) 8 | 9 | after(function() 10 | package.loaded["googleanalytics.internal.uuid"] = nil 11 | end) 12 | 13 | it("should generate uuids", function() 14 | uuid_generator.seed() 15 | local uuid = uuid_generator() 16 | assert(uuid) 17 | -- d902fd39-bbd3-41ad-c1e9--0ee49473f3e0 18 | assert(uuid:match("%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x") == uuid) 19 | end) 20 | end) 21 | end -------------------------------------------------------------------------------- /tests/tests.collection: -------------------------------------------------------------------------------- 1 | name: "tests" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "go" 5 | data: "components {\n" 6 | " id: \"script\"\n" 7 | " component: \"/tests/tests.script\"\n" 8 | " position {\n" 9 | " x: 0.0\n" 10 | " y: 0.0\n" 11 | " z: 0.0\n" 12 | " }\n" 13 | " rotation {\n" 14 | " x: 0.0\n" 15 | " y: 0.0\n" 16 | " z: 0.0\n" 17 | " w: 1.0\n" 18 | " }\n" 19 | "}\n" 20 | "" 21 | position { 22 | x: 0.0 23 | y: 0.0 24 | z: 0.0 25 | } 26 | rotation { 27 | x: 0.0 28 | y: 0.0 29 | z: 0.0 30 | w: 1.0 31 | } 32 | scale3 { 33 | x: 1.0 34 | y: 1.0 35 | z: 1.0 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/tests.script: -------------------------------------------------------------------------------- 1 | local test_tracker = require "tests.test_tracker" 2 | local test_queue = require "tests.test_queue" 3 | local test_file = require "tests.test_file" 4 | local test_uuid = require "tests.test_uuid" 5 | local test_ga = require "tests.test_ga" 6 | local test_user_agent = require "tests.test_user_agent" 7 | 8 | local deftest = require "deftest.deftest" 9 | 10 | function init(self) 11 | deftest.add(test_tracker) 12 | deftest.add(test_queue) 13 | deftest.add(test_file) 14 | deftest.add(test_uuid) 15 | deftest.add(test_ga) 16 | deftest.add(test_user_agent) 17 | deftest.run() 18 | end 19 | --------------------------------------------------------------------------------