text:
#!/usr/bin/env tarantool
local test = require("sqltester")
test:plan(509)

--!./tcltestrunner.lua
-- 2010 July 16
--
-- The author disclaims copyright to this source code.  In place of
-- a legal notice, here is a blessing:
--
--    May you do good and not evil.
--    May you find forgiveness for yourself and forgive others.
--    May you share freely, never taking more than you give.
--
-------------------------------------------------------------------------
--
-- This file implements tests to verify that the "testable statements" in
-- the lang_select.html document are correct.
--
-- ["set","testdir",[["file","dirname",["argv0"]]]]
-- ["source",[["testdir"],"\/tester.tcl"]]

test:do_execsql_test(
    "e_select-1.0",
    [[
        CREATE TABLE t1(a TEXT PRIMARY KEY, b TEXT);
        INSERT INTO t1 VALUES('a', 'one');
        INSERT INTO t1 VALUES('b', 'two');
        INSERT INTO t1 VALUES('c', 'three');

        CREATE TABLE t2(a TEXT PRIMARY KEY, b TEXT);
        INSERT INTO t2 VALUES('a', 'I');
        INSERT INTO t2 VALUES('b', 'II');
        INSERT INTO t2 VALUES('c', 'III');

        CREATE TABLE t3(a TEXT PRIMARY KEY, c INT);
        INSERT INTO t3 VALUES('a', 1);
        INSERT INTO t3 VALUES('b', 2);

        CREATE TABLE t4(a TEXT PRIMARY KEY, c INT);
        INSERT INTO t4 VALUES('a', NULL);
        INSERT INTO t4 VALUES('b', 2);
    ]], {
        -- <e_select-1.0>

        -- </e_select-1.0>
    })

local t1_cross_t2 = { "a", "one", "a", "I", "a", "one", "b", "II", "a", "one", "c", "III", "b", "two", "a", "I", "b", "two", "b", "II", "b", "two", "c", "III", "c", "three", "a", "I", "c", "three", "b", "II", "c", "three", "c", "III" }
local t1_cross_t1 = { "a", "one", "a", "one", "a", "one", "b", "two", "a", "one", "c", "three", "b", "two", "a", "one", "b", "two", "b", "two", "b", "two", "c", "three", "c", "three", "a", "one", "c", "three", "b", "two", "c", "three", "c", "three" }
-- This proc is a specialized version of [do_execsql_test].
-- The second argument to this proc must be a SELECT statement that
-- features a cross join of some time. Instead of the usual ",",
-- "CROSS JOIN" or "INNER JOIN" join-op, the string JOIN_PATTERN must be
-- substituted.
--
-- This test runs the SELECT three times - once with:
--
--   * s/JOIN_PATTERN/,/
--   * s/JOIN_PATTERN/JOIN/
--   * s/JOIN_PATTERN/INNER JOIN/
--   * s/JOIN_PATTERN/CROSS JOIN/
--
-- and checks that each time the results of the SELECT are $res.
--
local function do_join_test(tn, select, res)
    for tn2, joinop in ipairs({",", "CROSS JOIN", "INNER JOIN"}) do
        local S = string.gsub(select, "JOIN_PATTERN", joinop)
        test:do_execsql_test(
            tn.."."..tn2,
            S,
            res)
    end
end

---------------------------------------------------------------------------
-- The following tests check that all paths on the syntax diagrams on
-- the lang_select.html page may be taken.
--
-- -- syntax diagram join-constraint
--
do_join_test("e_select-0.1.1", [[
  SELECT count(*) FROM t1 JOIN_PATTERN t2 ON (t1.a=t2.a)
]], {3})
do_join_test("e_select-0.1.2", [[
  SELECT count(*) FROM t1 JOIN_PATTERN t2 USING (a)
]], {3})
do_join_test("e_select-0.1.3", [[
  SELECT count(*) FROM t1 JOIN_PATTERN t2
]], {9})
test:do_catchsql_test(
    "e_select-0.1.4",
    [[
        SELECT count(*) FROM t1, t2 ON (t1.a=t2.a) USING (a)
    ]], {
        -- <e_select-0.1.4>
        1, "cannot have both ON and USING clauses in the same join"
        -- </e_select-0.1.4>
    })

test:do_catchsql_test(
    "e_select-0.1.5",
    [[
        SELECT count(*) FROM t1, t2 USING (a) ON (t1.a=t2.a)
    ]], {
        -- <e_select-0.1.5>
        1, [[At line 1 at or near position 47: keyword 'ON' is reserved. Please use double quotes if 'ON' is an identifier.]]
        -- </e_select-0.1.5>
    })

-- -- syntax diagram select-core
--
--   0: SELECT ...
--   1: SELECT DISTINCT ...
--   2: SELECT ALL ...
--
--   0: No FROM clause
--   1: Has FROM clause
--
--   0: No WHERE clause
--   1: Has WHERE clause
--
--   0: No GROUP BY clause
--   1: Has GROUP BY clause
--   2: Has GROUP BY and HAVING clauses
--
test:do_select_tests(
    "e_select-0.2",
    {
        {"0000.1", "SELECT 1, 2, 3 ", {1, 2, 3}},
        {"1000.1", "SELECT DISTINCT 1, 2, 3 ", {1, 2, 3}},
        {"2000.1", "SELECT ALL 1, 2, 3 ", {1, 2, 3}},

        {"0100.1", "SELECT a, b, a||b FROM t1 ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
        {"1100.1", "SELECT DISTINCT a, b, a||b FROM t1 ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
        {"1200.1", "SELECT ALL a, b, a||b FROM t1 ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},

        {"0010.1", "SELECT 1, 2, 3 WHERE true ", {1, 2, 3}},
        {"0010.2", "SELECT 1, 2, 3 WHERE false ", {}},
        {"0010.3", "SELECT 1, 2, 3 WHERE NULL ", {}},

        {"1010.1", "SELECT DISTINCT 1, 2, 3 WHERE true ", {1, 2, 3}},

        {"2010.1", "SELECT ALL 1, 2, 3 WHERE true ", {1, 2, 3}},

        {"0110.1", "SELECT a, b, a||b FROM t1 WHERE a!='x' ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},
        {"0110.2", "SELECT a, b, a||b FROM t1 WHERE a=='x'", {}},

        {"1110.1", "SELECT DISTINCT a, b, a||b FROM t1 WHERE a!='x' ", {"a", "one", "aone", "b", "two", "btwo", "c", "three", "cthree"}},

        {"2110.0", "SELECT ALL a, b, a||b FROM t1 WHERE a=='x'", {}},

        {"0001.1", "SELECT 1, 2, 3 GROUP BY 2", {1, 2, 3}},
        {"0002.1", "SELECT 1, 2, 3 GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
        {"0002.2", "SELECT 1, 2, 3 GROUP BY 2 HAVING count(*)>1", {}},

        {"1001.1", "SELECT DISTINCT 1, 2, 3 GROUP BY 2", {1, 2, 3}},
        {"1002.1", "SELECT DISTINCT 1, 2, 3 GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
        {"1002.2", "SELECT DISTINCT 1, 2, 3 GROUP BY 2 HAVING count(*)>1", {}},

        {"2001.1", "SELECT ALL 1, 2, 3 GROUP BY 2", {1, 2, 3}},
        {"2002.1", "SELECT ALL 1, 2, 3 GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
        {"2002.2", "SELECT ALL 1, 2, 3 GROUP BY 2 HAVING count(*)>1", {}},

        {"0101.1", "SELECT count(*), max(a) FROM t1 GROUP BY b", {1, "a", 1, "c", 1, "b"}},
        {"0102.1", "SELECT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=1", {1, "a", 1, "c", 1, "b"}},
        {"0102.2", "SELECT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=2", { }},

        {"1101.1", "SELECT DISTINCT count(*), max(a) FROM t1 GROUP BY b", {1, "a", 1, "c", 1, "b"}},
        {"1102.1", "SELECT DISTINCT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=1", {1, "a", 1, "c", 1, "b"}},
        {"1102.2", "SELECT DISTINCT count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=2", { }},

        {"2101.1", "SELECT ALL count(*), max(a) FROM t1 GROUP BY b", {1, "a", 1, "c", 1, "b"}},
        {"2102.1", "SELECT ALL count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=1", {1, "a", 1, "c", 1, "b"}},
        {"2102.2", "SELECT ALL count(*), max(a) FROM t1 GROUP BY b HAVING count(*)=2", {}},

        {"0011.1", "SELECT 1, 2, 3 WHERE true GROUP BY 2", {1, 2, 3}},
        {"0012.1", "SELECT 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)=1", {}},
        {"0012.2", "SELECT 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)>1", {}},

        {"1011.1", "SELECT DISTINCT 1, 2, 3 WHERE false GROUP BY 2", {}},
        {"1012.1", "SELECT DISTINCT 1, 2, 3 WHERE true GROUP BY 2 HAVING count(*)=1", {1, 2, 3}},
        {"1012.2", "SELECT DISTINCT 1, 2, 3 WHERE NULL GROUP BY 2 HAVING count(*)>1", {}},

        {"2011.1", "SELECT ALL 1, 2, 3 WHERE true GROUP BY 2", {1, 2, 3}},
        {"2012.1", "SELECT ALL 1, 2, 3 WHERE false GROUP BY 2 HAVING count(*)=1", {}},
        {"2012.2", "SELECT ALL 1, 2, 3 WHERE true GROUP BY 2 HAVING count(*)>1", {}},

        {"0111.1", "SELECT count(*), max(a) FROM t1 WHERE a='a' GROUP BY b", {1, "a"}},
        {"0112.1", "SELECT count(*), max(a) FROM t1 WHERE a='c' GROUP BY b HAVING count(*)=1", {1, "c"}},
        {"0112.2", "SELECT count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},
        {"1111.1", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a<'c' GROUP BY b", {1, "a", 1, "b"}},
        {"1112.1", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE a>'a' GROUP BY b HAVING count(*)=1", {1, "c", 1, "b"}},
        {"1112.2", "SELECT DISTINCT count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},

        {"2111.1", "SELECT ALL count(*), max(a) FROM t1 WHERE b>'one' GROUP BY b", {1, "c", 1, "b"}},
        {"2112.1", "SELECT ALL count(*), max(a) FROM t1 WHERE a!='b' GROUP BY b HAVING count(*)=1", {1, "a", 1, "c"}},
        {"2112.2", "SELECT ALL count(*), max(a) FROM t1 WHERE false GROUP BY b HAVING count(*)=2", { }},
    })

-- -- syntax diagram result-column
--
test:do_select_tests(
    "e_select-0.3",
    {
        {"1", "SELECT * FROM t1", {"a", "one", "b", "two", "c", "three"}},
        {"2", "SELECT t1.* FROM t1", {"a", "one", "b", "two", "c", "three"}},
        {"3", "SELECT 'x'||a||'x' FROM t1", {"xax", "xbx", "xcx"}},
        {"4", "SELECT 'x'||a||'x' alias FROM t1", {"xax", "xbx", "xcx"}},
        {"5", "SELECT 'x'||a||'x' AS alias FROM t1", {"xax", "xbx", "xcx"}},
    })

-- # -- syntax diagram join-source
-- #
-- # -- syntax diagram join-op
-- #
-- do_select_tests e_select-0.4 {
--   1  "SELECT t1.rowid FROM t1" {1, 2, 3}
--   2  "SELECT t1.rowid FROM t1,t2" {1, 1, 1, 2, 2, 2, 3, 3, 3}
--   3  "SELECT t1.rowid FROM t1,t2,t3" {1 1 1 1 1 1 2 2 2 2, 2, 2, 3, 3, 3, 3, 3, 3}
--   4  "SELECT t1.rowid FROM t1" {1, 2, 3}
--   5  "SELECT t1.rowid FROM t1 JOIN t2" {1, 1, 1, 2, 2, 2, 3, 3, 3}
--   6  "SELECT t1.rowid FROM t1 JOIN t2 JOIN t3"--      {1 1 1 1 1 1 2 2 2 2, 2, 2, 3, 3, 3, 3, 3, 3}
--   7  "SELECT t1.rowid FROM t1 NATURAL JOIN t3" {1, 2}
--   8  "SELECT t1.rowid FROM t1 NATURAL LEFT OUTER JOIN t3" {1, 2, 3}
--   9  "SELECT t1.rowid FROM t1 NATURAL LEFT JOIN t3" {1, 2, 3}
--   10 "SELECT t1.rowid FROM t1 NATURAL INNER JOIN t3" {1, 2}
--   11 "SELECT t1.rowid FROM t1 NATURAL CROSS JOIN t3" {1, 2}
--   12 "SELECT t1.rowid FROM t1 JOIN t3" {1, 1, 2, 2, 3, 3}
--   13 "SELECT t1.rowid FROM t1 LEFT OUTER JOIN t3" {1, 1, 2, 2, 3, 3}
--   14 "SELECT t1.rowid FROM t1 LEFT JOIN t3" {1, 1, 2, 2, 3, 3}
--   15 "SELECT t1.rowid FROM t1 INNER JOIN t3" {1, 1, 2, 2, 3, 3}
--   16 "SELECT t1.rowid FROM t1 CROSS JOIN t3" {1, 1, 2, 2, 3, 3}
-- }
-- # -- syntax diagram compound-operator
-- #
-- do_select_tests e_select-0.5 {
--   1  "SELECT rowid FROM t1 UNION ALL SELECT rowid+2 FROM t4" {1, 2, 3, 3, 4}
--   2  "SELECT rowid FROM t1 UNION     SELECT rowid+2 FROM t4" {1, 2, 3, 4}
--   3  "SELECT rowid FROM t1 INTERSECT SELECT rowid+2 FROM t4" {3}
--   4  "SELECT rowid FROM t1 EXCEPT    SELECT rowid+2 FROM t4" {1, 2}
-- }
-- -- syntax diagram ordering-term
--
test:do_select_tests(
    "e_select-0.6",
    {
        {"1", "SELECT b||a FROM t1 ORDER BY b||a", {"onea", "threec", "twob"}},
        {"2", "SELECT b||a FROM t1 ORDER BY (b||a) COLLATE \"unicode_ci\"", {"onea", "threec", "twob"}},
        {"3", "SELECT b||a FROM t1 ORDER BY (b||a) ASC", {"onea", "threec", "twob"}},
        {"4", "SELECT b||a FROM t1 ORDER BY (b||a) DESC", {"twob", "threec", "onea"}},
    })

-- -- syntax diagram select-stmt
--
test:do_select_tests(
    "e_select-0.7",
    {
        {"1", "SELECT * FROM t1", {"a", "one", "b", "two", "c", "three"}},
        {"2", "SELECT * FROM t1 ORDER BY b", {"a", "one", "c", "three", "b", "two"}},
        {"3", "SELECT * FROM t1 ORDER BY b, a", {"a", "one", "c", "three", "b", "two"}},

        {"4", "SELECT * FROM t1 LIMIT 10", {"a", "one", "b", "two", "c", "three"}},
        {"5", "SELECT * FROM t1 LIMIT 10 OFFSET 5", {}},
        {"6", "SELECT * FROM t1 LIMIT 10, 5", {}},

        {"7", "SELECT * FROM t1 ORDER BY a LIMIT 10", {"a", "one", "b", "two", "c", "three"}},
        {"8", "SELECT * FROM t1 ORDER BY b LIMIT 10 OFFSET 5", {}},
        {"9", "SELECT * FROM t1 ORDER BY a,b LIMIT 10, 5", {}},

        {"10", "SELECT * FROM t1 UNION SELECT b, a FROM t1", {"a", "one", "b", "two", "c", "three", "one", "a", "three", "c", "two", "b"}},
        {"11", "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b", {"one", "a", "two", "b", "three", "c", "a", "one", "c", "three", "b", "two"}},
        {"12", "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b, a", {"one", "a", "two", "b", "three", "c", "a", "one", "c", "three", "b", "two"}},
        {"13", "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10", {"a", "one", "b", "two", "c", "three", "one", "a", "three", "c", "two", "b"}},
        {"14", "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10 OFFSET 5", {"two", "b"}},
        {"15", "SELECT * FROM t1 UNION SELECT b, a FROM t1 LIMIT 10, 5", {}},
        {"16", "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY a LIMIT 10", {"a", "one", "b", "two", "c", "three", "one", "a", "three", "c", "two", "b"}},
        {"17", "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY b LIMIT 10 OFFSET 5", {"b", "two"}},
        {"18", "SELECT * FROM t1 UNION SELECT b, a FROM t1 ORDER BY a,b LIMIT 10, 5", {}},
    })

---------------------------------------------------------------------------
-- The following tests focus on FROM clause (join) processing.
--
-- EVIDENCE-OF: R-16074-54196 If the FROM clause is omitted from a simple
-- SELECT statement, then the input data is implicitly a single row zero
-- columns wide
--
test:do_select_tests(
    "e_select-1.1",
    {
        {"1", "SELECT 'abc'", {"abc"}},
        {"2", "SELECT 'abc' WHERE NULL", {}},
        {"3", "SELECT NULL", {""}},
        {"4", "SELECT count(*)", {1}},
        {"5", "SELECT count(*) WHERE false", {0}},
        {"6", "SELECT count(*) WHERE true", {1}},
    })

--
-- The following block of tests - e_select-1.4.* - test that the description
-- of cartesian joins in the SELECT documentation is consistent with sql.
-- In doing so, we test the following three requirements as a side-effect:
--
-- EVIDENCE-OF: R-49872-03192 If the join-operator is "CROSS JOIN",
-- "INNER JOIN", "JOIN" or a comma (",") and there is no ON or USING
-- clause, then the result of the join is simply the cartesian product of
-- the left and right-hand datasets.
--
--    The tests are built on this assertion. NUMBERly, they test that the output
--    of a CROSS JOIN, JOIN, INNER JOIN or "," join matches the expected result
--    of calculating the cartesian product of the left and right-hand datasets.
--
-- EVIDENCE-OF: R-46256-57243 There is no difference between the "INNER
-- JOIN", "JOIN" and "," join operators.
--
-- EVIDENCE-OF: R-25071-21202 The "CROSS JOIN" join operator produces the
-- same result as the "INNER JOIN", "JOIN" and "," operators
--
--    All tests are run 4 times, with the only difference in each run being
--    which of the 4 equivalent cartesian product join operators are used.
--    Since the output data is the same in all cases, we consider that this
--    qualifies as testing the two statements above.
--
test:do_execsql_test(
    "e_select-1.4.0",
    [[
        CREATE TABLE x1(id  INT primary key, a TEXT , b TEXT );
        CREATE TABLE x2(id  INT primary key, c NUMBER , d NUMBER , e NUMBER );
        CREATE TABLE x3(id  INT primary key, f TEXT , g TEXT , h TEXT , i TEXT );

        -- x1: 3 rows, 2 columns
        INSERT INTO x1 VALUES(1,'24', 'converging');
        INSERT INTO x1 VALUES(2, NULL, CAST(X'CB71' as TEXT));
        INSERT INTO x1 VALUES(3,'blonds', 'proprietary');

        -- x2: 2 rows, 3 columns
        INSERT INTO x2 VALUES(1,-60.06, NULL, NULL);
        INSERT INTO x2 VALUES(2,-58, NULL, 1.21);

        -- x3: 5 rows, 4 columns
        INSERT INTO x3 VALUES(1,'-39.24', NULL, 'encompass', '-1');
        INSERT INTO x3 VALUES(2,'presenting', '51', 'reformation', 'dignified');
        INSERT INTO x3 VALUES(3,'conducting', '-87.24', '37.56', NULL);
        INSERT INTO x3 VALUES(4,'coldest', '-96', 'dramatists', '82.3');
        INSERT INTO x3 VALUES(5,'alerting', NULL, '-93.79', NULL);
    ]], {
        -- <e_select-1.4.0>

        -- </e_select-1.4.0>
    })

-- EVIDENCE-OF: R-59089-25828 The columns of the cartesian product
-- dataset are, in order, all the columns of the left-hand dataset
-- followed by all the columns of the right-hand dataset.
--
do_join_test("e_select-1.4.1.1", [[
  SELECT a,b,c,d,e FROM x1 JOIN_PATTERN x2 LIMIT 1
]], {"24", "converging", -60.06, "", ""})
do_join_test("e_select-1.4.1.2", [[
  SELECT c,d,e,a,b FROM x2 JOIN_PATTERN x1 LIMIT 1
]], {-60.06, "", "", "24", "converging"})
do_join_test("e_select-1.4.1.3", [[
  SELECT f,g,h,i,c,d,e FROM x3 JOIN_PATTERN x2 LIMIT 1
]], {'-39.24', "", "encompass", '-1', -60.06, "", ""})
do_join_test("e_select-1.4.1.4", [[
  SELECT c,d,e,f,g,h,i FROM x2 JOIN_PATTERN x3 LIMIT 1
]], {-60.06, "", "", '-39.24', "", "encompass", '-1'})
-- EVIDENCE-OF: R-44414-54710 There is a row in the cartesian product
-- dataset formed by combining each unique combination of a row from the
-- left-hand and right-hand datasets.
--
do_join_test("e_select-1.4.2.1", [[
  SELECT c,d,e,f,g,h,i FROM x2 JOIN_PATTERN x3 ORDER BY +c, +f
]], { -60.06,"","","-39.24","","encompass","-1",-60.06,"","","alerting","","-93.79","",-60.06,"","","coldest","-96","dramatists","82.3",-60.06,"","","conducting","-87.24","37.56","",-60.06,"","","presenting","51","reformation","dignified",-58,"",1.21,"-39.24","","encompass","-1",-58,"",1.21,"alerting","","-93.79","",-58,"",1.21,"coldest","-96","dramatists","82.3",-58,"",1.21,"conducting","-87.24","37.56","",-58,"",1.21,"presenting","51","reformation","dignified" })
-- TODO: Come back and add a few more like the above.
-- EVIDENCE-OF: R-18439-38548 In other words, if the left-hand dataset
-- consists of Nleft rows of Mleft columns, and the right-hand dataset of
-- Nright rows of Mright columns, then the cartesian product is a dataset
-- of Nleft×Nright rows, each containing Mleft+Mright columns.
--
-- x1, x2    (Nlhs=3, Nrhs=2)   (Mlhs=2, Mrhs=3)
do_join_test("e_select-1.4.3.1", [[
  SELECT count(*) FROM x1 JOIN_PATTERN x2
]], {3 * 2})
test:do_test(
    "e_select-1.4.3.2",
    function()
        return #test:execsql("SELECT a,b,c,d,e FROM x1, x2") / 6
    end, 2 + 3)

-- x2, x3    (Nlhs=2, Nrhs=5)   (Mlhs=3, Mrhs=4)
do_join_test("e_select-1.4.3.3", [[
  SELECT count(*) FROM x2 JOIN_PATTERN x3
]], {2 * 5})
test:do_test(
    "e_select-1.4.3.4",
    function()
        return #test:execsql("SELECT c,d,e,f,g,h,i FROM x2 JOIN x3") / 10
    end,(3 + 4))

-- x3, x1    (Nlhs=5, Nrhs=3)   (Mlhs=4, Mrhs=2)
do_join_test("e_select-1.4.3.5", [[
  SELECT count(*) FROM x3 JOIN_PATTERN x1
]], {5 * 3})
test:do_test(
    "e_select-1.4.3.6",
    function()
        return #test:execsql("SELECT f,g,h,i,a,b FROM x3 CROSS JOIN x1") / 15
    end, (4 + 2))

-- x3, x3    (Nlhs=5, Nrhs=5)   (Mlhs=4, Mrhs=4)
do_join_test("e_select-1.4.3.7", [[
  SELECT count(*) FROM x3 JOIN_PATTERN x3
]], {5 * 5})
test:do_test(
    "e_select-1.4.3.8",
    function()
        return #test:execsql("SELECT x3.f,x3.g,x3.h,x3.i,x4.f,x4.g,x4.h,x4.i FROM x3 INNER JOIN x3 AS x4") / 25
    end, (4 + 4))

-- Some extra cartesian product tests using tables t1 and t2.
--
test:do_execsql_test(
    "e_select-1.4.4.1",
    [[
        SELECT * FROM t1, t2
    ]], t1_cross_t2)

test:do_execsql_test(
    "e_select-1.4.4.2",
    [[
        SELECT * FROM t1 AS x, t1 AS y
    ]], t1_cross_t1)

test:do_select_tests(
    "e_select-1.4.5",{
    {1, " SELECT * FROM t1 CROSS JOIN t2 ", t1_cross_t2},
    {2, " SELECT * FROM t1 AS y CROSS JOIN t1 AS x ", t1_cross_t1},
    {3, " SELECT * FROM t1 INNER JOIN t2 ", t1_cross_t2},
    {4, " SELECT * FROM t1 AS y INNER JOIN t1 AS x ", t1_cross_t1 }})

-- EVIDENCE-OF: R-38465-03616 If there is an ON clause then the ON
-- expression is evaluated for each row of the cartesian product as a
-- boolean expression. Only rows for which the expression evaluates to
-- true are included from the dataset.
--
local data ={
    {"1"," SELECT * FROM t1 JOIN_PATTERN t2 ON (true) ",t1_cross_t2},
    {"2"," SELECT * FROM t1 JOIN_PATTERN t2 ON (false) ",{}},


text:
-- These default paths are relative to the premake5.lua file (or whatever you named it) of your project
-- They can be absolute paths too if you so desire
-- Default path for the workspace files
DEFAULT_WORKSPACE_DIRECTORY = os.target() .. "/" .. _ACTION

-- Default path for the source files (beware this will be used by all projects)
DEFAULT_SOURCE_DIRECTORY = "../source"

-- Default path for the SourceSDK files (root of the source code)
DEFAULT_SOURCESDK_DIRECTORY = nil

-- Default path for the GarrysMod/garrysmod/lua/bin directory (where the compiled files will be copied to)
DEFAULT_GARRYSMOD_LUA_BIN_DIRECTORY = nil

domain: lua


text:
/*
*   @package        : rcore
*   @module         : base
*	@extends		: ulx, xlib
*   @author         : Richard [http://steamcommunity.com/profiles/76561198135875727]
*   @copyright      : (c) 2018 - 2020
*   @since          : 1.0.0
*   @website        : https://rlib.io
*   @docs           : https://docs.rlib.io
*
*   MIT License
*
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
*   LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
*   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
*   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/*
*   standard tables and localization
*/

local base                  = rlib

/*
*   module calls
*/

local mod, pf       	    = base.modules:req( 'base' )
local cfg               	= base.modules:cfg( mod )

/*
*   check :: ulx override enabled
*/

if not ulx or not cfg or not cfg.ulx_override then return end

--Commands module (formerly players module) v2 for ULX GUI -- by Stickly Man!
--Handles all user-executable commands, such as kick, slay, ban, etc.

local cmds					= xlib.makepanel { parent = xgui.null }
cmds.selcmd 				= nil
cmds.mask 					= xlib.makepanel { x = 160, y = 30, w = 425, h = 330, parent = cmds }
cmds.argslist 				= xlib.makelistlayout { w = 165, h = 330, parent = cmds.mask }
cmds.argslist.secondaryPos 	= nil

cmds.argslist.scroll:SetVisible( false )

function cmds.argslist:Open( cmd, secondary )
	if secondary then
		if cmds.plist.open then
			cmds.plist:Close()
		elseif self.open then
			self:Close()
		end
	end
	self:openAnim( cmd, secondary )
	self.open = true
end
function cmds.argslist:Close()
	self:closeAnim( self.secondaryPos )
	self.open = false
end
cmds.plist = xlib.makelistview{ w=250, h=330, multiselect=true, parent=cmds.mask }
function cmds.plist:Open( arg )
	if cmds.argslist.secondaryPos == true then cmds.argslist:Close()
	elseif self.open then self:Close() end
	self:openAnim( arg )
	--Determine if the arguments should be visible after changing (If a valid player will be selected)
	local targets = cmds.calculateValidPlayers( arg )
	local playerWillBeSelected = false
	for _, line in ipairs( cmds.plist:GetSelected() ) do
		if table.HasValue( targets, line.ply ) then
			playerWillBeSelected = true
			break
		end
	end
	if playerWillBeSelected then
		cmds.argslist:Open( ULib.cmds.translatedCmds[arg.cmd], false )
	end
	self.open = true
end
function cmds.plist:Close()
	if cmds.argslist.open then cmds.argslist:Close() end
	self:closeAnim()
	self.open = false
end
cmds.plist.DoDoubleClick = function()
	cmds.runCmd( cmds.selcmd )
end
cmds.plist:SetVisible( false )
cmds.plist:AddColumn( "Name" )
cmds.plist:AddColumn( "Group" )

cmds.cmds = xlib.makelistlayout{ x=5, y=30, w=150, h=330, parent=cmds, padding=1, spacing=1 }
cmds.setselected = function( selcat, LineID )
	if selcat.Lines[LineID]:GetColumnText(2) == cmds.selcmd then
		selcat:ClearSelection()
		if cmds.plist.open then cmds.plist:Close() else cmds.argslist:Close() end
		xlib.animQueue_start()
		cmds.selcmd = nil
		return
	end

	for _, cat in pairs( cmds.cmd_contents ) do
		if cat ~= selcat then
			cat:ClearSelection()
		end
	end
	cmds.selcmd = selcat.Lines[LineID]:GetColumnText(2)

	if cmds.permissionChanged then cmds.refreshPlist() return end

	if xlib.animRunning then xlib.animQueue_forceStop() end

	local cmd = ULib.cmds.translatedCmds[cmds.selcmd]
	if cmd.args[2] and ( cmd.args[2].type == ULib.cmds.PlayersArg or cmd.args[2].type == ULib.cmds.PlayerArg ) then
		cmds.plist:Open( cmd.args[2] )
	else
		cmds.argslist:Open( cmd, true )
	end
	xlib.animQueue_start()
end

function cmds.refreshPlist( arg )
	if not arg then arg = ULib.cmds.translatedCmds[cmds.selcmd].args[2] end
	if not arg or ( arg.type ~= ULib.cmds.PlayersArg and arg.type ~= ULib.cmds.PlayerArg ) then return end

	local lastplys = {}
	for k, Line in pairs( cmds.plist.Lines ) do
		if ( Line:IsLineSelected() ) then table.insert( lastplys, Line:GetColumnText(1) ) end
	end

	local targets = cmds.calculateValidPlayers( arg )

	cmds.plist:Clear()
	cmds.plist:SetMultiSelect( arg.type == ULib.cmds.PlayersArg )
	for _, ply in ipairs( targets ) do
		local line = cmds.plist:AddLine( ply:Nick(), ply:GetUserGroup() )
		line.ply = ply
		line.OnSelect = function()
			if cmds.permissionChanged then return end

			if not xlib.animRunning and not cmds.argslist.open then
				cmds.argslist:Open( ULib.cmds.translatedCmds[cmds.selcmd], false )
				xlib.animQueue_start( )
			else
				if not cmds.clickedFlag then --Prevent this from happening multiple times.
					cmds.clickedFlag = true
					xlib.addToAnimQueue( function() if not cmds.argslist.open then
						cmds.argslist:Open( ULib.cmds.translatedCmds[cmds.selcmd], false ) end
						cmds.clickedFlag = nil end )
				end
			end
		end

		--Select previously selected Lines
		if table.HasValue( lastplys, ply:Nick() ) then
			cmds.plist:SelectItem( line )
		end
	end
	--Select only the first item if multiselect is disabled.
	if not cmds.plist:GetMultiSelect() then
		local firstSelected = cmds.plist:GetSelected()[1]
		cmds.plist:ClearSelection()
		cmds.plist:SelectItem( firstSelected )
	end

	if not cmds.plist:GetSelectedLine() then
		if not xlib.animRunning then
			if cmds.argslist.open then
				cmds.argslist:Close()
				xlib.animQueue_start()
			end
		else
			if cmds.permissionChanged then
				xlib.addToAnimQueue( function() if cmds.argslist.open and cmds.plist.open then cmds.argslist:Close() end end )
			end
		end
	end
end

function cmds.calculateValidPlayers( arg )
	if not arg then arg = ULib.cmds.translatedCmds[cmds.selcmd].args[2] end

	local access, tag = LocalPlayer():query( arg.cmd )
	local restrictions = {}
	ULib.cmds.PlayerArg.processRestrictions( restrictions, LocalPlayer(), arg, ulx.getTagArgNum( tag, 1 ) )

	local targets = restrictions.restrictedTargets
	if targets == false then -- No one allowed
		targets = {}
	elseif targets == nil then -- Everyone allowed
		targets = player.GetAll()
	end
	return targets
end

function cmds.buildArgsList( cmd )
	cmds.argslist:Clear()
	cmds.curargs = {}
	local argnum = 0
	local zpos = 0
	local expectingplayers = cmd.args[2] and ( ( cmd.args[2].type == ULib.cmds.PlayersArg ) or ( cmd.args[2].type == ULib.cmds.PlayerArg ) ) or false
	for _, arg in ipairs( cmd.args ) do
		if not arg.type.invisible then
			argnum = argnum + 1
			if not ( argnum == 1 and expectingplayers ) then
				if arg.invisible ~= true then
					local curitem = arg
					if curitem.repeat_min then --This command repeats!
						local panel = xlib.makepanel{ h=20, parent=cmds.argslist }
						local choices = {}
						panel.argnum = argnum
						panel.xguiIgnore = true
						panel.arg = curitem
						panel.addbutton = xlib.makebutton{ label="Add", w=83, parent=panel }
						panel.addbutton.DoClick = function( self )
							local parent = self:GetParent()
							local ctrl = parent.arg.type.x_getcontrol( parent.arg, parent.argnum, cmds.argslist )
							cmds.argslist:Add( ctrl )
							ctrl:MoveToAfter( choices[#choices] )
							table.insert( choices, ctrl )
							table.insert( cmds.curargs, ctrl )
							panel.removebutton:SetDisabled( false )
							if parent.arg.repeat_max and #choices >= parent.arg.repeat_max then self:SetDisabled( true ) end
						end
						panel.removebutton = xlib.makebutton{ label="Remove", x=83, w=82, disabled=true, parent=panel }
						panel.removebutton.DoClick = function( self )
							local parent = self:GetParent()
							local ctrl = choices[#choices]
							ctrl:Remove()
							table.remove( choices )
							table.remove( cmds.curargs )
							panel.addbutton:SetDisabled( false )
							if #choices <= parent.arg.repeat_min then self:SetDisabled( true ) end
						end
						cmds.argslist:Add( panel )
						panel:SetZPos( zpos )
						zpos = zpos + 1
						for i=1,curitem.repeat_min do
							local ctrl = arg.type.x_getcontrol( arg, argnum, cmds.argslist )
							cmds.argslist:Add( ctrl )
							ctrl:SetZPos( zpos )
							zpos = zpos + 1
							table.insert( choices, ctrl )
							table.insert( cmds.curargs, ctrl )
						end
					else
						local panel = arg.type.x_getcontrol( arg, argnum, cmds.argslist )
						table.insert( cmds.curargs, panel )
						if curitem.type == ULib.cmds.NumArg then
							panel.TextArea.OnEnter = function( self )
								cmds.runCmd( cmd.cmd )
							end
						elseif curitem.type == ULib.cmds.StringArg then
							panel.OnEnter = function( self )
								cmds.runCmd( cmd.cmd )
							end
						end
						cmds.argslist:Add( panel )
						panel:SetZPos( zpos )
						zpos = zpos + 1
					end
				end
			end
		end
	end
	if LocalPlayer():query( cmd.cmd ) then
		local str_button = cmd.cmd
		if cmd.setButton and isstring( cmd.setButton ) then
			str_button = cmd.setButton
		end
		local panel = xlib.makebutton{ label=str_button, parent=cmds.argslist }
		panel.xguiIgnore = true
		panel.DoClick = function()
			cmds.runCmd( cmd.cmd )
		end
		cmds.argslist:Add( panel )
		panel:SetZPos( zpos )
		zpos = zpos + 1
	end
	if cmd.opposite and LocalPlayer():query( cmd.opposite ) then
		local panel = xlib.makebutton{ label=cmd.opposite, parent=cmds.argslist }
		panel.DoClick = function()
			cmds.runCmd( cmd.opposite )
		end
		panel.xguiIgnore = true
		cmds.argslist:Add( panel )
		panel:SetZPos( zpos )
		zpos = zpos + 1
	end
	if cmd.helpStr then --If the command has a string for help
		local panel = xlib.makelabel{ w=160, label=cmd.helpStr, wordwrap=true, parent=cmds.argslist }
		panel.xguiIgnore = true
		cmds.argslist:Add( panel )
		panel:SetZPos( zpos )
		zpos = zpos + 1
	end
end

function cmds.runCmd( cmd )
	local cmd = string.Explode( " ", cmd )
	if cmds.plist:IsVisible() then
		local plys = {}
		for _, line in ipairs( cmds.plist:GetSelected() ) do
			table.insert( plys, "$" .. ULib.getUniqueIDForPlayer( line.ply ) )
			table.insert( plys, "," )
		end
		table.remove( plys ) --Removes the final comma
		table.insert( cmd, table.concat( plys ) )
	end

	for _, arg in ipairs( cmds.curargs ) do
		if not arg.xguiIgnore then
			table.insert( cmd, arg:GetValue() )
		end
	end
	RunConsoleCommand( unpack( cmd ) )
end

function cmds.playerNameChanged( ply, old, new )
	for i, line in ipairs( cmds.plist.Lines ) do
		if line:GetColumnText(1) == old then
			line:SetColumnText( 1, new )
		end
	end
end

cmds.refresh = function( permissionChanged )
	local lastcmd = cmds.selcmd
	cmds.cmds:Clear()
	cmds.cmd_contents = {}
	cmds.expandedcat = nil
	cmds.selcmd = nil
	cmds.permissionChanged = true

	local newcategories = {}
	local sortcategories = {}
	local matchedCmdFound = false
	for cmd, data in pairs( ULib.cmds.translatedCmds ) do
		local opposite = data.opposite
		if opposite ~= cmd and ( LocalPlayer():query( data.cmd ) or (opposite and LocalPlayer():query( opposite ) )) then
			local catname = data.category
			if catname == nil or catname == "" then catname = "_Uncategorized" end
			if not cmds.cmd_contents[catname] then
				--Make a new category
				cmds.cmd_contents[catname] = xlib.makelistview{ headerheight=0, multiselect=false, h=136 }
				cmds.cmd_contents[catname].OnRowSelected = function( self, LineID ) cmds.setselected( self, LineID ) end
				cmds.cmd_contents[catname]:AddColumn( "" )
				local cat = xlib.makecat{ label=catname, contents=cmds.cmd_contents[catname], expanded=false, parent=xgui.null }
				function cat.Header:OnMousePressed( mcode )
					if ( mcode == MOUSE_LEFT ) then
						self:GetParent():Toggle()
						if cmds.expandedcat then
							if cmds.expandedcat ~= self:GetParent() then
								cmds.expandedcat:Toggle()
							else
								cmds.expandedcat = nil
								return
							end
						end
						cmds.expandedcat = self:GetParent()
						return
					end
					return self:GetParent():OnMousePressed( mcode )
				end
				newcategories[catname] = cat
				table.insert( sortcategories, catname )
			end
			local line = cmds.cmd_contents[catname]:AddLine( string.gsub( data.cmd, "ulx ", "" ), data.cmd )
			if data.cmd == lastcmd then
				cmds.cmd_contents[catname]:SelectItem( line )
				cmds.expandedcat = cmds.cmd_contents[catname]:GetParent()
				cmds.expandedcat:SetExpanded( true )
				matchedCmdFound = true
			end
		end
	end
	if not matchedCmdFound then
		if cmds.plist.open then
			cmds.plist:Close()
			xlib.animQueue_start()
		elseif cmds.argslist.open then
			cmds.argslist:Close()
			xlib.animQueue_start()
		end
	end

	table.sort( sortcategories )
	for _, catname in ipairs( sortcategories ) do
		local cat = newcategories[catname]
		cmds.cmds:Add( cat )
		cat.Contents:SortByColumn( 1 )
		cat.Contents:SetHeight( 17*#cat.Contents:GetLines() )
	end
	cmds.permissionChanged = nil
end

--------------
--ANIMATIONS--
--------------
function cmds.plist:openAnim( arg )
	xlib.addToAnimQueue( cmds.refreshPlist, arg )
	xlib.addToAnimQueue( "pnlSlide", { panel=self, startx=-250, starty=0, endx=0, endy=0, setvisible=true } )
end

function cmds.plist:closeAnim()
	xlib.addToAnimQueue( "pnlSlide", { panel=self, startx=0, starty=0, endx=-250, endy=0, setvisible=false } )
end

function cmds.argslist:openAnim( cmd, secondary )
	xlib.addToAnimQueue( function() cmds.argslist.secondaryPos = secondary end )
	xlib.addToAnimQueue( cmds.buildArgsList, cmd )
	if secondary then
		xlib.addToAnimQueue( "pnlSlide", { panel=self.scroll, startx=-170, starty=0, endx=0, endy=0, setvisible=true } )
	else
		xlib.addToAnimQueue( "pnlSlide", { panel=self.scroll, startx=80, starty=0, endx=255, endy=0, setvisible=true } )
	end
end

function cmds.argslist:closeAnim( secondary )
	if secondary then
		xlib.addToAnimQueue( "pnlSlide", { panel=self.scroll, startx=0, starty=0, endx=-170, endy=0, setvisible=false } )
	else
		xlib.addToAnimQueue( "pnlSlide", { panel=self.scroll, startx=255, starty=0, endx=80, endy=0, setvisible=false } )
	end
	xlib.addToAnimQueue( function() cmds.argslist.secondaryPos = nil end )
end
--------------

cmds.refresh()
hook.Add( "UCLChanged", "xgui_RefreshPlayerCmds", cmds.refresh )
hook.Add( "ULibPlayerNameChanged", "xgui_plyUpdateCmds", cmds.playerNameChanged )
xgui.addModule( "Cmds", cmds, "icon16/user_gray.png" )


text:
local crypto_secretbox_keygen_sig = [[
  void %s(unsigned char *k)
]]

local crypto_secretbox_sig = [[
  int %s(unsigned char *c,
         const unsigned char *m,
         unsigned long long mlen,
         const unsigned char *n,
         const unsigned char *k)
]]

local crypto_secretbox_open_sig = [[
  int %s(unsigned char *m,
         const unsigned char *c,
         unsigned long long clen,
         const unsigned char *n,
         const unsigned char *k)
]]

local crypto_secretbox_easy_sig = [[
  int %s(unsigned char *c,
         const unsigned char *m,
         unsigned long long mlen,
         const unsigned char *n,
         const unsigned char *k)
]]

local crypto_secretbox_open_easy_sig = [[
  int %s(unsigned char *m,
         const unsigned char *c,
         unsigned long long clen,
         const unsigned char *n,
         const unsigned char *k)
]]

local crypto_secretbox_detached_sig = [[
  int %s(unsigned char *c,
         unsigned char *mac,
         const unsigned char *m,
         unsigned long long mlen,
         const unsigned char *n,
         const unsigned char *k)
]]

local crypto_secretbox_open_detached_sig = [[
  int %s(unsigned char *m,
         const unsigned char *c,
         const unsigned char *mac,
         unsigned long long clen,
         const unsigned char *n,
         const unsigned char *k)
]]

local signatures = {
  ['crypto_secretbox_keygen'] = crypto_secretbox_keygen_sig,
  ['crypto_secretbox'] = crypto_secretbox_sig,
  ['crypto_secretbox_open'] = crypto_secretbox_open_sig,
  ['crypto_secretbox_easy'] = crypto_secretbox_easy_sig,
  ['crypto_secretbox_open_easy'] = crypto_secretbox_open_easy_sig,
  ['crypto_secretbox_detached'] = crypto_secretbox_detached_sig,
  ['crypto_secretbox_open_detached'] = crypto_secretbox_open_detached_sig,

  ['crypto_secretbox_xsalsa20poly1305_keygen'] = crypto_secretbox_keygen_sig,
  ['crypto_secretbox_xsalsa20poly1305'] = crypto_secretbox_sig,
  ['crypto_secretbox_xsalsa20poly1305_open'] = crypto_secretbox_open_sig,
}

return signatures


text:
util.AddNetworkString('FAdmin_kick_start')
util.AddNetworkString('FAdmin_kick_cancel')
util.AddNetworkString('FAdmin_kick_update')
util.AddNetworkString('FAdmin_ban_start')
util.AddNetworkString('FAdmin_ban_cancel')
util.AddNetworkString('FAdmin_ban_update')

-- Kicking
local function Kick(ply, cmd, args)
    local targets = FAdmin.FindPlayer(args[1])
    if not targets or #targets == 1 and not IsValid(targets[1]) then
        FAdmin.Messages.SendMessage(ply, 1, "Player not found")
        return false
    end

    local CanKick = hook.Call("FAdmin_CanKick", nil, ply, targets)

    if CanKick == false then return false end

    local stage = args[2] or ""
    stage = string.lower(stage)
    local stages = {"start", "cancel", "update", "execute"}
    local Reason = (not table.HasValue(stages, stage) and table.concat(args, ' ', 2)) or table.concat(args, ' ', 3) or ply.FAdminKickReason

    for _, target in pairs(targets) do
        if not FAdmin.Access.PlayerHasPrivilege(ply, "Kick", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
        if IsValid(target) then
            if stage == "start" then
                net.Start('FAdmin_kick_start')
                net.Send(target) -- Tell him he's getting kicked
                target:Lock() -- Make sure he can't remove the hook clientside and keep minging.
                target:KillSilent()
            elseif stage == "cancel" then
                net.Start('FAdmin_kick_cancel')
                net.Send(target) -- No I changed my mind, you can stay
                target:UnLock()
                target:Spawn()
                ply.FAdminKickReason = nil
            elseif stage == "update" then -- Update reason text
                if not args[3] then return false end
                ply.FAdminKickReason = args[3]
                net.Start('FAdmin_kick_update')
                    net.WriteString(args[3])
                net.Send(target)
            else
                local name = IsValid(ply) and ply:IsPlayer() and ply:Nick() or "Console"

                Reason = Reason and string.gsub(Reason, ";", " ") or "No reason provided"

                game.ConsoleCommand(string.format("kickid %s %s\n", target:UserID(), "Kicked by " .. name ..
                    " (" .. Reason .. ")"))
                ply.FAdminKickReason = nil
            end
        end
    end

    if stage ~= "start" and stage ~= "cancel" and stage ~= "update" then
        FAdmin.Messages.FireNotification("kick", ply, nil, {FAdmin.TargetsToString(targets), Reason})
    end

    return true, targets, stage, Reason
end

local StartBannedUsers = {} -- Prevent rejoining before actual ban occurs
hook.Add("PlayerAuthed", "FAdmin_LeavingBeforeBan", function(ply, SteamID, ...)
    if not StartBannedUsers[SteamID] then return end
    if not IsValid(StartBannedUsers[SteamID].author) then
        StartBannedUsers[SteamID] = nil
        return
    end

    game.ConsoleCommand(string.format("kickid %s %s\n", ply:UserID(), "Getting banned"))
end)

-- Banning
FAdmin.BANS = FAdmin.BANS or {}

local function RequestBans(ply, cmd, args)
    if not FAdmin.Access.PlayerHasPrivilege(ply, "UnBan") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
    net.Start("FAdmin_retrievebans")
        net.WriteUInt(table.Count(FAdmin.BANS), 32)

        for k, v in pairs(FAdmin.BANS) do
            net.WriteString(k or "") -- SteamID
            net.WriteUInt(v.time or 0, 32)
            net.WriteString(v.name or "")
            net.WriteString(v.reason or "")
            net.WriteString(v.adminname or "")
            net.WriteString(v.adminsteam or "")
        end
    net.Send(ply)

    return true, FAdmin.BANS
end

timer.Create("FAdminCheckBans", 60, 0, function()
    for k, v in pairs(FAdmin.BANS) do
        if v.time and not isstring(v.time) and tonumber(v.time) < os.time() and v.time ~= 0 then
            FAdmin.BANS[k] = nil
        end
    end
end)

local function SaveBan(SteamID, Nick, Duration, Reason, AdminName, Admin_steam)
    local StoreBans = hook.Call("FAdmin_StoreBan", nil, SteamID, Nick, Duration, Reason, AdminName, Admin_steam)

    if tonumber(Duration) == 0 then
        FAdmin.BANS[SteamID] = {}
        FAdmin.BANS[SteamID].time = 0
        FAdmin.BANS[SteamID].name = Nick
        FAdmin.BANS[SteamID].reason = Reason
        FAdmin.BANS[SteamID].adminname = AdminName
        FAdmin.BANS[SteamID].adminsteam = Admin_steam
    else
        FAdmin.BANS[SteamID] = {}
        FAdmin.BANS[SteamID].time = os.time() + Duration * 60 --in minutes, so *60
        FAdmin.BANS[SteamID].name = Nick
        FAdmin.BANS[SteamID].reason = Reason
        FAdmin.BANS[SteamID].adminname = AdminName
        FAdmin.BANS[SteamID].adminsteam = Admin_steam
    end

    if StoreBans == true then return end
end

local function Ban(ply, cmd, args)
    if not args[2] then return false end
    --start cancel update execute

    local targets = FAdmin.FindPlayer(args[1])

    if not targets and string.find(args[1], "STEAM_") ~= 1 and string.find(args[2], "STEAM_") ~= 1 then
        FAdmin.Messages.SendMessage(ply, 1, "Player not found")
        return false
    elseif not targets and (string.find(args[1], "STEAM_") == 1 or string.find(args[2], "STEAM_") == 1) then
        targets = {(args[1] ~= "execute" and args[1]) or args[2]}
        if args[1] == "STEAM_0" then
            targets[1] = table.concat(args, "", 1, 5)
            args[1] = targets[1]
            args[2] = args[6]
            args[3] = args[7]
            for i = 2, #args do
                if i >= 4 then args[i] = nil end
            end
        end
    end

    local CanBan = hook.Call("FAdmin_CanBan", nil, ply, targets)

    if CanBan == false then return false end

    local stage = string.lower(args[2])
    local stages = {"start", "cancel", "update", "execute"}
    local Reason = (not table.HasValue(stages, stage) and table.concat(args, ' ', 3)) or table.concat(args, ' ', 4) or ply.FAdminKickReason
    local time

    for _, target in pairs(targets) do
        if (isstring(target) and not FAdmin.Access.PlayerHasPrivilege(ply, "Ban")) or
        not FAdmin.Access.PlayerHasPrivilege(ply, "Ban", target) then
            FAdmin.Messages.SendMessage(ply, 5, "No access!")
            return false
        end
        if stage == "start" and not isstring(target) and IsValid(target) then
            net.Start('FAdmin_ban_start')
            net.Send(target) -- Tell him he's getting banned
            target:Lock() -- Make sure he can't remove the hook clientside and keep minging.
            target:KillSilent()
            StartBannedUsers[target:SteamID()] = { author = ply }

        elseif stage == "cancel" then
            if not isstring(target) and IsValid(target) then
                net.Start('FAdmin_ban_cancel')
                net.Send(target) -- No I changed my mind, you can stay
                target:UnLock()
                target:Spawn()
                StartBannedUsers[target:SteamID()] = nil
            else -- If he left and you want to cancel
                StartBannedUsers[args[1]] = nil
            end
        elseif stage == "update" then -- Update reason text
            if not args[4] or isstring(target) or not IsValid(target) then return false end
            ply.FAdminKickReason = args[4]
            net.Start('FAdmin_ban_update')
                net.WriteInt(tonumber(args[3]), 32)
                net.WriteString(tostring(args[4]))
            net.Send(target)
        else
            time = tonumber(args[2]) or 0
            Reason = (Reason ~= "" and Reason) or args[3] or ""

            if stage == "execute" then
                time = tonumber(args[3]) or 60 --Default to one hour, not permanent.
                Reason = args[4] or ""
            end

            local TimeText = FAdmin.PlayerActions.ConvertBanTime(time)

            if not isstring(target) and IsValid(target) then
                StartBannedUsers[target:SteamID()] = nil
                local nick = ply.Nick and ply:Nick() or "console"
                SaveBan(target:SteamID(), target:Nick(), time, Reason, nick, ply.SteamID and ply:SteamID() or "Console")

                Reason = string.gsub(Reason, ";", " ")
                target:Kick("banned by " .. nick .. " for " .. TimeText .. " (" .. Reason .. ")")
            else
                StartBannedUsers[args[1]] = nil

                SaveBan(target, nil, time, Reason ~= "" and Reason, ply.Nick and ply:Nick() or "console", ply.SteamID and ply:SteamID() or "Console") -- Again default to one hour
            end
            ply.FAdminKickReason = nil
        end
    end

    if stage ~= "start" and stage ~= "cancel" and stage ~= "update" then
        FAdmin.Messages.FireNotification("ban", ply, nil, {FAdmin.TargetsToString(targets), time, Reason})
    end

    return true, targets, stage, Reason
end

-- Unbanning
local function UnBan(ply, cmd, args)
    if not FAdmin.Access.PlayerHasPrivilege(ply, "UnBan") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
    if not args[1] then return false end
    local SteamID = string.upper(args[1])
    local nick = "Unknown"

    hook.Call("FAdmin_UnBan", nil, ply, SteamID)

    for k, v in pairs(FAdmin.BANS) do
        if string.upper(k) == SteamID then
            nick = FAdmin.BANS[k].name or nick
            FAdmin.BANS[k] = nil
            break
        end
    end

    StartBannedUsers[SteamID] = nil

    game.ConsoleCommand("removeid " .. SteamID .. "\n")
    FAdmin.Messages.FireNotification("unban", ply, nil, {nick, SteamID})

    return true, SteamID
end

-- Commands and privileges
FAdmin.StartHooks["KickBan"] = function()
    FAdmin.Commands.AddCommand("kick", Kick)
    FAdmin.Commands.AddCommand("ban", Ban)
    FAdmin.Commands.AddCommand("unban", UnBan)
    FAdmin.Commands.AddCommand("RequestBans", RequestBans)

    FAdmin.Access.AddPrivilege("Kick", 2)
    FAdmin.Access.AddPrivilege("Ban", 2)
    FAdmin.Access.AddPrivilege("UnBan", 2)
end

hook.Add("DarkRPDBInitialized", "FAdmin_Retrievebans", function()
    timer.Simple(2, function()
        local RetrieveBans = hook.Call("FAdmin_RetrieveBans", nil)

        if RetrieveBans then
            for k, v in pairs(RetrieveBans) do
                FAdmin.BANS[string.upper(k)] = v
            end
            return
        end
    end)

    if file.Exists("FAdmin/Bans.txt", "DATA") then
        local bans = util.KeyValuesToTable(file.Read("FAdmin/bans.txt", "DATA") or {})
        for k, v in pairs(bans) do
            FAdmin.BANS[string.upper(k)] = v
        end

        for k, v in pairs(FAdmin.BANS) do
            v.time = tonumber(v.time)
            if v.time and v.time < os.time() then
                FAdmin.BANS[string.upper(k)] = nil
                continue
            elseif not v.time then
                continue
            end

            hook.Call("FAdmin_StoreBan", nil, string.upper(k), v.name, (v.time - os.time()) / 60, v.reason, v.adminname, v.adminsteam)
        end
        file.Delete("FAdmin/Bans.txt", "DATA")
    end
end)

local function canJoin(steamID)
    local ban = FAdmin.BANS[string.upper(steamID)]

    if not ban then return end

    if ban.time ~= 0 and ban.time < os.time() then
        FAdmin.BANS[string.upper(steamID)] = nil
        return
    end

    local timeText = FAdmin.PlayerActions.ConvertBanTime((ban.time - os.time()) / 60)

    return false, "banned by " .. (ban.adminname or "<Unknown>") .. " for " .. timeText .. " (" .. (ban.reason or "no reason") .. ")"
end
hook.Add("CheckPassword", "FAdmin_Bans", fc{canJoin, util.SteamIDFrom64})

-- When the bans haven't loaded yet, or when the player passes
-- CheckPassword when banned, they'll be caught on InitialSpawn
hook.Add("PlayerInitialSpawn", "FAdmin_Bans", function(ply)
    local allowedIn, reason = canJoin(ply:SteamID())
    if allowedIn == false then
        ply:Kick(reason)
    end
end)

hook.Add("PlayerDisconnected", "FAdmin bans", function(ply)
    for k,v in pairs(StartBannedUsers) do
        if v.author ~= ply then continue end
        StartBannedUsers[k] = nil
        local target = player.GetBySteamID(k)
        if not IsValid(target) then continue end
        SendUserMessage("FAdmin_ban_cancel", target)
        target:UnLock()
        target:Spawn()
    end
end)


text:
-- Copyright 2015 J.C. Moyer
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

local function escapequotes(s)
  return s:gsub('"', '\\"')
end

local html = {}
local element = {}

-- contains metadata for tags
-- right now the only metadata stored is whether or not an element is a void
-- element
-- TODO: instantiable elementspecs?
local elementspec = { specs = {} }

function elementspec.define(name, void)
  local instance = {
    name = name,
    void = void
  }
  instance.__index = instance
  elementspec.specs[name] = setmetatable(instance, element)
  return instance
end

function elementspec.get(name)
  return elementspec.specs[name]
end

element.__index = element

function element.new(name)
  local instance = {
    children = {},
    attrs = {}
  }
  return setmetatable(instance, elementspec.get(name))
end

function element.fromstring(s, name)
  local e = element.new(name)
  e:appendchild(s)
  return e
end

function element.fromtable(t, name)
  local e = element.new(name)
  for k,v in pairs(t) do
    if type(k) == 'number' then
      e:appendchild(v)
    else
      e:addattr(k, v)
    end
  end
  return e
end

function element.fromvalue(x, name)
  if type(x) == 'table' then
    return element.fromtable(x, name)
  elseif type(x) == 'nil' then
    return element.fromtable({}, name)
  else
    return element.fromstring(tostring(x), name)
  end
end

function element:opentag()
  local t = {}
  for k,v in pairs(self.attrs) do
    if type(v) == 'string' then
      v = escapequotes(v)
    end
    table.insert(t, k .. '=' .. '"' .. v .. '"')
  end
  if #t > 0 then
    return '<' .. self.name .. ' ' .. table.concat(t, ' ') .. '>'
  else
    return '<' .. self.name .. '>'
  end
end

function element:closetag()
  return '</' .. self.name .. '>'
end

function element:appendchild(data)
  table.insert(self.children, data)
end

function element:addattr(attr, value)
  self.attrs[attr] = value
end

function element:render()
  local t = {}
  table.insert(t, self:opentag())
  if not self.void then
    for i = 1, #self.children do
      local child = self.children[i]
      if element.type(child) then
        table.insert(t, child:render())
      else
        table.insert(t, tostring(child))
      end
    end
    table.insert(t, self:closetag())
  end
  return table.concat(t)
end

function element.type(t)
  -- we have to climb the __index chain because it's likely that we were given
  -- a table whose mt is an elementspec
  local mt = getmetatable(t)
  while mt do
    if mt == element then
      return 'element'
    end
    mt = getmetatable(mt)
  end
end

-- defines an element by adding an elementspec for the given name and a
-- corresponding factory function in the html module
-- TODO: should elementspec mt declaration be moved here?
local function defel(name, void)
  elementspec.define(name, void)
  html[name] = function(x)
    return element.fromvalue(x, name)
  end
end

local function defels(t)
  for _,v in ipairs(t) do
    if type(v) == 'table' then
      defel(v[1] or v.name, v[2] or v.void)
    else
      defel(v, false)
    end
  end
end

function html:__call(t)
  return self.html(t)
end

function html.rendermixed(t)
  local u = {}
  for _,v in ipairs(t) do
    if element.type(v) then
      table.insert(u, v:render())
    else
      table.insert(u, tostring(v))
    end
  end
  return table.concat(u)
end

-- Elements: http://www.w3.org/TR/html5/
-- Void elements: http://www.w3.org/TR/html5/syntax.html#elements-0
defels {
  -- 4.1 The root element
  'html',

  -- 4.2 Document metadata
  'head', 'title', {'base', true}, {'link', true}, {'meta', true}, 'style',

  -- 4.3 Sections
  'body', 'article', 'section', 'nav', 'aside', 'h1', 'h2', 'h3', 'h4', 'h5',
  'h6', 'header', 'footer', 'address',

  -- 4.4 Grouping content
  'p', {'hr', true}, 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd',
  'figure', 'figcaption', 'div', 'main',

  -- 4.5 Text-level semantics
  'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'data',
  'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b', 'u', 'mark',
  'ruby', 'rt', 'rtc', 'rp', 'bdi', 'bdo', 'span', {'br', true}, {'wbr', true},

  -- 4.6 Edits
  'ins', 'del',

  -- 4.7 Embedded content
  {'img', true}, 'iframe', {'embed', true}, 'object', {'param', true}, 'video',
  'audio', {'source', true}, {'track', true}, 'map', {'area', true},

  -- 4.9 Tabular data
  'table', 'caption', 'colgroup', {'col', true}, 'tbody', 'thead', 'tfoot',
  'tr', 'td', 'th',

  -- 4.10 Forms
  'form', 'label', {'input', true}, 'button', 'select', 'datalist', 'optgroup',
  'option', 'textarea', {'keygen', true}, 'output', 'progress', 'meter',
  'fieldset', 'legend',

  -- 4.11 Scripting
  'script', 'noscript', 'template', 'canvas'
}

html.element = element
return setmetatable(html, html)


text:
-- Creator:
--	EarthSalamander, May 12th, 2019
--	Leshrac Diabolic Edict luafied

LinkLuaModifier("modifier_imba_leshrac_diabolic_edict", "components/abilities/heroes/hero_leshrac", LUA_MODIFIER_MOTION_NONE)

imba_leshrac_diabolic_edict = class({})

function imba_leshrac_diabolic_edict:GetAbilityTextureName()
	if not IsClient() then return end
	if not self:GetCaster().arcana_style then return "leshrac_diabolic_edict" end
	return "leshrac_diabolic_edict_ti9"
end

function imba_leshrac_diabolic_edict:OnSpellStart()
	if IsClient() then return end
	local num_explosions = self:GetSpecialValueFor("num_explosions") + self:GetCaster():FindTalentValue("special_bonus_unique_leshrac_1")
	local delay = self:GetDuration() / num_explosions

	self:GetCaster():EmitSound("Hero_Leshrac.Diabolic_Edict_lp")
	self:GetCaster():AddNewModifier(self:GetCaster(), self, "modifier_imba_leshrac_diabolic_edict", {duration = self:GetDuration(), delay = delay})
end

-----------------------------
-- DIABOLIC EDICT MODIFIER --
-----------------------------

modifier_imba_leshrac_diabolic_edict = class({})

function modifier_imba_leshrac_diabolic_edict:IsPurgable() return false end
function modifier_imba_leshrac_diabolic_edict:RemoveOnDeath() return false end
function modifier_imba_leshrac_diabolic_edict:GetAttributes() return MODIFIER_ATTRIBUTE_MULTIPLE end

function modifier_imba_leshrac_diabolic_edict:OnCreated(keys)
	if IsClient() then return end

	self:OnIntervalThink()
	self:StartIntervalThink(keys.delay)
end

function modifier_imba_leshrac_diabolic_edict:OnIntervalThink()
	if IsClient() then return end

	local enemies = FindUnitsInRadius(self:GetParent():GetTeamNumber(),
		self:GetParent():GetAbsOrigin(),
		nil,
		self:GetAbility():GetSpecialValueFor("radius"),
		self:GetAbility():GetAbilityTargetTeam(),
		self:GetAbility():GetAbilityTargetType(),
		self:GetAbility():GetAbilityTargetFlags(),
		FIND_ANY_ORDER,
		false
	)

	-- Create Particle
	local pfx = ParticleManager:CreateParticle(CustomNetTables:GetTableValue("battlepass", "leshrac").diabolic_edict, PATTACH_ABSORIGIN_FOLLOW, self:GetParent())
	local position_cast
	local enemy = enemies[RandomInt(1, #enemies)]

	if enemy then
		position_cast = enemy:GetAbsOrigin()
		ParticleManager:SetParticleControlEnt(pfx, 1, enemy, PATTACH_ABSORIGIN_FOLLOW, "attach_hitloc", Vector(0,0,0), true)

		ApplyDamage({
			attacker = self:GetParent(),
			victim = enemy,
			damage = self:GetAbility():GetAbilityDamage(),
			damage_type = self:GetAbility():GetAbilityDamageType(),
			damage_flags = self:GetAbility():GetAbilityTargetFlags()
		})
	else
		position_cast = self:GetParent():GetAbsOrigin() + RandomVector(RandomInt(0, self:GetAbility():GetSpecialValueFor("radius")))
		ParticleManager:SetParticleControl(pfx, 1, position_cast)
	end

	EmitSoundOnLocationWithCaster(position_cast, "Hero_Leshrac.Diabolic_Edict", self:GetParent())
	ParticleManager:ReleaseParticleIndex(pfx)
end

function modifier_imba_leshrac_diabolic_edict:OnDestroy(params)
	if IsClient() then return end

	self:GetParent():StopSound("Hero_Leshrac.Diabolic_Edict_lp")
end


text:
--- A really basic argument parser
-- The Urn implementation was much nicer

local function errorf(msg, ...)
  error(msg:format(...), 0)
end

local function setter(arg, result, value)
  result[arg.name] = value or true
end

local parser = { __name = "ArgParser" }
parser.__index = parser

function parser:add(names, arg)
  if type(names) == "string" then names = { names } end

  arg.names = names
  for i = 1, #names do
    local name = names[i]
    if name:sub(1, 2) == "--" then self.options[name:sub(3)] = arg
    elseif name:sub(1, 1) == "-" then self.flags[name:sub(2)] = arg
    else self.arguments[#self.arguments + 1] = arg; arg.argument = true end
  end

  table.insert(self.list, #self.list, arg)

  -- Default to the setter action
  if arg.action == nil then arg.action = setter end
  -- Require if we're an argument, otherwise continue as normal
  if arg.required == nil then arg.required = names[1]:sub(1, 1) ~= "-" end
  if arg.name == nil then arg.name = names[1]:gsub("^-+", "") end
  if arg.mvar == nil then arg.mvar = arg.name:upper() end
end

function parser:parse(...)
  local args = table.pack(...)
  local i, n = 1, #args
  local arg_idx = 1

  local result = {}
  while i <= n do
    local arg = args[i]
    i = i + 1

    if arg:find("^%-%-([^=]+)=(.+)$") then
      local name, value = arg:match("^%-%-([^=]+)=(.+)$")
      local arg = self.options[name]

      -- Some sanity checking for arguments
      if not arg then errorf("Unknown argument %q", name) end
      if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end
      if not arg.value then errorf("%s does not accept a value", name) end

      -- Run the setter
      arg:action(result, value)
    elseif arg:find("^%-%-(.*)$") then
      local name = arg:match("^%-%-(.*)$")
      local arg = self.options[name]

      -- Some sanity checking for arguments
      if not arg then errorf("Unknown argument %q", name) end
      if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end

      -- Consume the value and run the setter
      if arg.value then
        local value = args[i]
        i = i + 1
        if not value then errorf("%s needs a value", name) end
        arg:action(result, value)
      else
        arg:action(result)
      end
    elseif arg:find("^%-(.+)$") then
      local flags = arg:match("^%-(.+)$")
      for j = 1, #flags do
        local name = flags:sub(j, j)
        local arg = self.flags[name]

        -- Some sanity checking
        if not arg then errorf("Unknown argument %q", name) end
        if not arg.many and result[arg.name] ~= nil then errorf("%s has already been set", name) end

        -- Consume the value and run the setter
        if arg.value then
          local value
          if j == #flags then
            value = args[i]
            i = i + 1
          else
            value = flags:sub(j + 1)
          end

          if not value then errorf("%s expects a value", name) end
          arg:action(result, value)
          break
        else
          arg:action(result)
        end
      end
    else
      local argument = self.arguments[arg_idx]
      if argument then
        argument:action(result, arg)
        arg_idx = arg_idx + 1
      else
        errorf("Unexpected argument %q", arg)
      end
    end
  end

  for i = 1, #self.list do
    local arg = self.list[i]
    if arg and arg.required and result[arg.name] == nil then
      errorf("%s is required (use -h to see usage)", arg.name)
    end
  end

  return result
end

local function get_usage(arg)
  local name
  if arg.argument then name = arg.mvar
  elseif arg.value then name = arg.names[1] .. "=" .. arg.mvar
  else name = arg.names[1]
  end

  if #arg.names > 1 then name = name .. "," .. table.concat(arg.names, ",", 2) end
  return name
end

local function create(prefix)
  local parser = setmetatable({
    options = {},
    flags = {},
    arguments = {},

    list = {},
  }, parser)

  parser:add({ "-h", "--help", "-?" }, {
    value = false, required = false,
    doc = "Show this help message",
    action = function()
      if prefix then print(prefix) print() end

      print("USAGE")
      local max = 0
      for i = 1, #parser.list do max = math.max(max, #get_usage(parser.list[i])) end
      local format = " %-" .. max .. "s %s"

      for i = 1, #parser.list do
        local arg = parser.list[i]
        print(format:format(get_usage(arg), arg.doc or ""))
      end

      error("", 0)
    end,
  })

  return parser
end

local function is_help(cmd)
  return cmd == "help" or cmd == "--help" or cmd == "-h" or cmd == "-?"
end

return { create = create, is_help = is_help }


text:
return {
	map_id = 10005,
	id = 4004,
	stages = {
		{
			stageIndex = 1,
			failCondition = 1,
			timeCount = 300,
			passCondition = 1,
			backGroundStageID = 1,
			totalArea = {
				-70,
				20,
				90,
				70
			},
			playerArea = {
				-70,
				20,
				37,
				68
			},
			enemyArea = {},
			mainUnitPosition = {
				{
					Vector3(-105, 0, 58),
					Vector3(-105, 0, 78),
					Vector3(-105, 0, 38)
				},
				[-1] = {
					Vector3(15, 0, 58),
					Vector3(15, 0, 78),
					Vector3(15, 0, 38)
				}
			},
			fleetCorrdinate = {
				-80,
				0,
				75
			},
			waves = {
				{
					triggerType = 1,
					waveIndex = 100,
					preWaves = {},
					triggerParams = {
						timeout = 0.5
					}
				},
				{
					triggerType = 0,
					key = true,
					waveIndex = 101,
					conditionType = 1,
					preWaves = {
						100
					},
					triggerParam = {},
					spawn = {
						{
							monsterTemplateID = 651,
							reinforceDelay = 5,
							delay = 0,
							moveCast = true,
							corrdinate = {
								0,
								0,
								65
							},
							bossData = {
								hpBarNum = 25,
								icon = "miaogao"
							},
							buffList = {
								8027,
								8028
							}
						}
					}
				},
				{
					triggerType = 0,
					key = true,
					waveIndex = 102,
					conditionType = 1,
					preWaves = {
						101
					},
					triggerParam = {},
					spawn = {
						{
							monsterTemplateID = 652,
							moveCast = true,
							delay = 0,
							corrdinate = {
								8,
								0,
								55
							},
							bossData = {
								hpBarNum = 25,
								icon = "ligen"
							},
							buffList = {
								8027,
								8028
							}
						}
					}
				},
				{
					triggerType = 0,
					key = true,
					waveIndex = 103,
					conditionType = 1,
					preWaves = {
						102
					},
					triggerParam = {},
					spawn = {
						{
							monsterTemplateID = 653,
							moveCast = true,
							delay = 0,
							corrdinate = {
								0,
								0,
								80
							},
							bossData = {
								hpBarNum = 20,
								icon = "xianghe"
							}
						},
						{
							monsterTemplateID = 654,
							moveCast = true,
							delay = 0,
							corrdinate = {
								0,
								0,
								55
							},
							bossData = {
								hpBarNum = 20,
								icon = "ruihe"
							}
						}
					}
				}
			}
		}
	},
	fleet_prefab = {}
}


text:
require "resty.hoedown.buffer"
local ffi      = require "ffi"
local ffi_cdef = ffi.cdef

ffi_cdef[[
typedef enum hoedown_autolink_flags {
    HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0)
} hoedown_autolink_flags;
int hoedown_autolink_is_safe(const uint8_t *data, size_t size);
size_t hoedown_autolink__www(size_t *rewind_p, hoedown_buffer *link, uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
size_t hoedown_autolink__email(size_t *rewind_p, hoedown_buffer *link, uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
size_t hoedown_autolink__url(size_t *rewind_p, hoedown_buffer *link, uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
]]

return {}

-- TODO: NYI.


text:
local publicClass={};

local cp1251 =
    {
        [128]='\208\130',[129]='\208\131',[130]='\226\128\154',[131]='\209\147',[132]='\226\128\158',[133]='\226\128\166',
        [134]='\226\128\160',[135]='\226\128\161',[136]='\226\130\172',[137]='\226\128\176',[138]='\208\137',[139]='\226\128\185',
        [140]='\208\138',[141]='\208\140',[142]='\208\139',[143]='\208\143',[144]='\209\146',[145]='\226\128\152',
        [146]='\226\128\153',[147]='\226\128\156',[148]='\226\128\157',[149]='\226\128\162',[150]='\226\128\147',[151]='\226\128\148',
        [152]='\194\152',[153]='\226\132\162',[154]='\209\153',[155]='\226\128\186',[156]='\209\154',[157]='\209\156',
        [158]='\209\155',[159]='\209\159',[160]='\194\160',[161]='\209\142',[162]='\209\158',[163]='\208\136',
        [164]='\194\164',[165]='\210\144',[166]='\194\166',[167]='\194\167',[168]='\208\129',[169]='\194\169',
        [170]='\208\132',[171]='\194\171',[172]='\194\172',[173]='\194\173',[174]='\194\174',[175]='\208\135',
        [176]='\194\176',[177]='\194\177',[178]='\208\134',[179]='\209\150',[180]='\210\145',[181]='\194\181',
        [182]='\194\182',[183]='\194\183',[184]='\209\145',[185]='\226\132\150',[186]='\209\148',[187]='\194\187',
        [188]='\209\152',[189]='\208\133',[190]='\209\149',[191]='\209\151'
    }

local nmdc =
    {
        [36] = '$',
        [124] = '|'
    }

function publicClass.cp1251_utf8(s)
    local r, b = ''
    for i = 1, s and s:len() or 0 do
        b = s:byte(i)
        if b < 128 then
            r = r..string.char(b)
        else
            if b > 239 then
                r = r..'\209'..string.char(b-112)
            elseif b > 191 then
                r = r..'\208'..string.char(b-48)
            elseif cp1251[b] then
                r = r..cp1251[b]
            else
                r = r..'_'
            end
        end
    end
    return r
end

return publicClass;


text:
-- stdlib expected prog table that holds the Lua application
prog =
{
	name = "base stdlib Example",
	banner = " 0.1 (05/27/2008) by RJP Computing <rjpcomputing@gmail.com>)",
	purpose = "Shows some of the 'base' functions that stdlib includes",
}

nestedTable =
{
	hi = "there",
	"I",
	"see",
	"you",
	{
		"another",
		"table",
		what = "A nested table"
	}
}
print( "old style 'print()' " )
print( nestedTable )

require( "base" )
print( "stdlib version of 'print()'" )
print( nestedTable )

print( "prettytostring of table" )
print( prettytostring( nestedTable ) )

require( "io_ext" ) -- Needed by warn()
warn( "some warning:%i", 101 )


text:
-------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------

local generalLoaded = false
local PlayingAnim = false

-------------------------------------------------------------------------------------------------
-------------------------------------- AJOUTE de PNJ --------------------------------------------
-------------------------------------------------------------------------------------------------

local ShopClerk = {
  -- ID: id de PNJ | nom: Nom du Blip | BlipID: Icone de Blip | VoiceName: NPC Talk Quand près de lui | Ambiance: Ambiance de Boutique | Arme: Hash of Weapon | modelHash: Modèle | X: Position x | Y: Position Y | Z: Position Z | Rubrique: Où Npc regarde
	{id = 3, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "a_f_y_genhot_01", animation="random@shop_clothes@mid", x = -165.047, y = -303.228, z = 38.783, heading = 256.336}, -- Caisse Las Lagunas
	{id = 4, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "s_f_y_shop_mid", animation="random@shop_clothes@mid", x = 117.765, y = -225.823, z = 53.608, heading = 308.572}, -- Vendeuse Assistante Hawick Avenue
	{id = 5, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "a_f_y_genhot_01", animation="random@shop_clothes@mid", x = 127.192, y = -224.230, z = 53.558, heading = 73.663}, -- Vendeuse Caisse Hawick Avenue
	{id = 6, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "s_f_y_shop_mid", animation="random@shop_clothes@mid", x = -1185.296, y = -770.696, z = 17.331, heading = 82.786}, -- Vendeuse Assistante San Adreas Avenue
	{id = 7, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "a_f_y_genhot_01", animation="random@shop_clothes@mid", x = -1195.377, y = -767.955, z = 17.316, heading = 215.831}, -- Vendeuse Caisse San Adreas Avenue
	{id = 8, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "s_f_y_shop_mid", animation="random@shop_clothes@mid", x = -1447.854, y = -242.982, z = 49.821, heading = 11.678}, -- Vendeuse Assistante Rockford Drive
	{id = 9, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "a_f_y_genhot_01", animation="random@shop_clothes@mid", x = -1448.604, y = -237.870, z = 49.814, heading = 52.460}, -- Vendeuse Caisse Rockford Drive
	-- {id = 12, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "a_f_m_ktown_02", animation="random@shop_clothes@mid", x = -822.739, y = -1072.054, z = 11.328, heading = 208.076}, -- Vendeur Caisse Palomino Avenue
	{id = 13, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "s_f_y_shop_mid", animation="random@shop_clothes@mid", x = -3178.780, y = 1041.791, z = 20.863, heading = 311.489}, -- Vendeuse Assistante Barbareno Road
	{id = 13, name = "Seller", VoiceName = "GENERIC_HI", Weapon = 0xA2719263, modelHash = "s_f_y_shop_mid", animation="random@shop_clothes@mid", x = -708.856, y = -151.573, z = 37.415, heading = 117.767}, -- Vendeuse Assistante Portola Drive
}

-- Events
-- RegisterNetEvent("ped_shopclothes:createPED")
-- AddEventHandler("ped_shopclothes:createPED", function()
	-- create_ped_shopclothes()
-- end)

-- Fonction principale
-- function create_ped_shopclothes()

	-- PNJ de Spawn
	Citizen.CreateThread(function()
	  while true do
		Citizen.Wait(0)

		if (not generalLoaded) then

		  for i=1, #ShopClerk do
			RequestModel(GetHashKey(ShopClerk[i].modelHash))
			while not HasModelLoaded(GetHashKey(ShopClerk[i].modelHash)) do
			  Wait(1)
			end

			ShopClerk[i].id = CreatePed(2, ShopClerk[i].modelHash, ShopClerk[i].x, ShopClerk[i].y, ShopClerk[i].z, ShopClerk[i].heading, false, false)
			SetPedFleeAttributes(ShopClerk[i].id, 0, 0)
			SetAmbientVoiceName(ShopClerk[i].id, ShopClerk[i].Ambiance)
			SetPedDropsWeaponsWhenDead(ShopClerk[i].id, false)
			SetPedDiesWhenInjured(ShopClerk[i].id, false)
			GiveWeaponToPed(ShopClerk[i].id, ShopClerk[i].Weapon, 2800, false, true)

		  end
		  generalLoaded = true

		end

	  end
	end)

	-------------------------------------------------------------------------------------------------
	-------------------------------------------------------------------------------------------------

	-- Action lorsque joueur Près de NPC (ou non)
	Citizen.CreateThread(function()
	  while true do
		Citizen.Wait(0)
			RequestAnimDict("random@shop_clothes@mid")
			while (not HasAnimDictLoaded("random@shop_clothes@mid")) do
				Citizen.Wait(0)
			end

			for i=1, #ShopClerk do
				distance = GetDistanceBetweenCoords(ShopClerk[i].x, ShopClerk[i].y, ShopClerk[i].z, GetEntityCoords(GetPlayerPed(-1)))
				if distance < 4 and PlayingAnim ~= true then
					TaskPlayAnim(ShopClerk[i].id,"random@shop_clothes@mid","_greeting_a", 1.0, -1.0, 6000, 0, 1, true, true, true)
					PlayAmbientSpeech1(ShopClerk[i].id, ShopClerk[i].VoiceName, "SPEECH_PARAMS_FORCE", 1)
					PlayingAnim = true
					Citizen.Wait(7000)
						TaskPlayAnim(ShopClerk[i].id,"random@shop_clothes@mid","_idle_a", 1.0, -1.0, 10000, 0, 1, true, true, true)
						PlayingAnim = true
						Citizen.Wait(10000)
						TaskPlayAnim(ShopClerk[i].id,"random@shop_clothes@mid","_idle_c", 1.0, -1.0, 10000, 0, 1, true, true, true)
						PlayingAnim = true
						Citizen.Wait(10000)
				else
					--don't touch this
					--TaskPlayAnim(ShopClerk[i].id,"random@shop_gunstore","_idle", 1.0, -1.0, -1, 0, 1, true, true, true)
				end
				if distance > 6 and distance < 8 then
					PlayingAnim = false
				end

			end
	  end
	end)
-- end


text:
local drawableSprite = require("structs.drawable_sprite")
local utils = require("utils")

local wallBooster = {}

wallBooster.name = "wallBooster"
wallBooster.depth = 1999
wallBooster.canResize = {false, true}
wallBooster.placements = {
    {
        name = "booster_right",
        placementType = "rectangle",
        data = {
            height = 8,
            left = true,
            notCoreMode = false
        }
    },
    {
        name = "booster_left",
        placementType = "rectangle",
        data = {
            height = 8,
            left = false,
            notCoreMode = false
        }
    },
    {
        name = "ice_right",
        placementType = "rectangle",
        data = {
            height = 8,
            left = true,
            notCoreMode = true
        }
    },
    {
        name = "ice_left",
        placementType = "rectangle",
        data = {
            height = 8,
            left = false,
            notCoreMode = true
        }
    }
}

local fireTopTexture = "objects/wallBooster/fireTop00"
local fireMiddleTexture = "objects/wallBooster/fireMid00"
local fireBottomTexture = "objects/wallBooster/fireBottom00"

local iceTopTexture = "objects/wallBooster/iceTop00"
local iceMiddleTexture = "objects/wallBooster/iceMid00"
local iceBottomTexture = "objects/wallBooster/iceBottom00"

local function getWallTextures(entity)
    if entity.notCoreMode then
        return iceTopTexture, iceMiddleTexture, iceBottomTexture

    else
        return fireTopTexture, fireMiddleTexture, fireBottomTexture
    end
end

function wallBooster.sprite(room, entity)
    local sprites = {}

    local left = entity.left
    local height = entity.height or 8
    local tileHeight = math.floor(height / 8)
    local offsetX = left and 0 or 8
    local scaleX = left and 1 or -1

    local topTexture, middleTexture, bottomTexture = getWallTextures(entity)

    for i = 2, tileHeight - 1 do
        local middleSprite = drawableSprite.fromTexture(middleTexture, entity)

        middleSprite:addPosition(offsetX, (i - 1) * 8)
        middleSprite:setScale(scaleX, 1)
        middleSprite:setJustification(0.0, 0.0)

        table.insert(sprites, middleSprite)
    end

    local topSprite = drawableSprite.fromTexture(topTexture, entity)
    local bottomSprite = drawableSprite.fromTexture(bottomTexture, entity)

    topSprite:addPosition(offsetX, 0)
    topSprite:setScale(scaleX, 1)
    topSprite:setJustification(0.0, 0.0)

    bottomSprite:addPosition(offsetX, (tileHeight - 1) * 8)
    bottomSprite:setScale(scaleX, 1)
    bottomSprite:setJustification(0.0, 0.0)

    table.insert(sprites, topSprite)
    table.insert(sprites, bottomSprite)

    return sprites
end

function wallBooster.rectangle(room, entity)
    return utils.rectangle(entity.x, entity.y, 8, entity.height or 8)
end

return wallBooster


text:
--------------------------------------------------------------------------------
--! @file	TppPlayerPadConfig.lua
--! @brief	システム関連のユーティリティ Lua 関数群
--------------------------------------------------------------------------------

TppPlayerPadConfig = {

--[[
-- C++化
ConfigDefaultPadAssigns = function()
end,

]]

-------------------------------------------------------------------------------------------------
-- パッドコンフィグ
-------------------------------------------------------------------------------------------------
SetConfigDefaultPadAssigns = function(padType)

	Fox.Log(" > SetConfigDefaultPadAssigns padType:" .. padType )

	-- どのパッドタイプでも変化の無いもの
	TppPlayerPadConfig.baseAssigns()

	-- パッドタイプごとの設定
	if padType == 0 then
		-- アクションタイプA(デフォルト)
		TppPlayerPadConfig.defaultAssigns( false )
	elseif padType == 1 then
		-- シュータータイプA
		TppPlayerPadConfig.fpsTypeAssigns( false )
	elseif padType == 2 then
		-- アクションタイプB
		TppPlayerPadConfig.defaultAssigns( true )
	elseif padType == 3 then
		-- シュータータイプB
		TppPlayerPadConfig.fpsTypeAssigns( true )
	else
		Fox.Warning("no defined padType : " .. padType)
	end

	TppPlayerPadConfig.vehicleAssigns()

	Fox.Log(" > > > >")

end,

-- 全パターン共通
baseAssigns = function()

	-- LRトリガの検出用の閾値変更
 	-- @param level 0.5-3.0の範囲でlevelが高いほど深い入力にに敏感になります.
 	-- @note	通常1.0-1.75程度に設定します.
	-- 引数を省略する(無効な値を入力すると)とデフォルト値に戻します.わかり辛れぇ…
	HidDriver.SetPadSensibility("digitaltrigger",0.8)

--[[
   	if Pad.GetDeviceType() == "PAD_DEVICE_TYPE_X360" then
		Fox.Log("360")
	elseif Pad.GetDeviceType() == "PAD_DEVICE_TYPE_PS3" then
		Fox.Log("PS3")
	elseif Pad.GetDeviceType() == "PAD_DEVICE_TYPE_OTHERS" then
		Fox.Log("Other")
	else
		Fox.Log("UnKnown")
	end
]]
	-- 基本ボタン
	Pad.RegisterButtonAssign	( "OK",				fox.PAD_A )
	Pad.RegisterButtonAssign	( "CANCEL",			fox.PAD_B )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_PAUSE",			fox.PAD_SELECT )

	-- アクション
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ROLLING",			fox.PAD_L3 )	-- Side Roll
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_LIGHTSWITCH",		fox.PAD_R )		-- フラッシュライトのスイッチ切替

	-- スティック
	Pad.RegisterAxisAssign		( "LSTICK_X",		fox.PAD_AXIS_LX, false )
	Pad.RegisterAxisAssign		( "LSTICK_Y",		fox.PAD_AXIS_LY, false )
	Pad.RegisterAxisAssign		( "RSTICK_X",		fox.PAD_AXIS_RX, false )
	Pad.RegisterAxisAssign		( "RSTICK_Y",		fox.PAD_AXIS_RY, false )

	-- CQC動作チェック
	TppPadOperatorUtility.RegisterButtonAssign	( "CQC_TESTMODE",		fox.PAD_X )
	TppPadOperatorUtility.RegisterButtonAssign	( "CQC_TESTMODE_UP",	fox.PAD_U )
	TppPadOperatorUtility.RegisterButtonAssign	( "CQC_TESTMODE_DOWN",	fox.PAD_D )

	-- カメラ
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ZOOM_CHANGE", fox.PAD_R3, "Camera" )	-- 注目
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ZOOM_IN", fox.PAD_U, "Camera" )	-- ズームイン
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ZOOM_OUT", fox.PAD_D, "Camera" )	-- ズームアウト

	-- サーチライト
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SEARCHLIGHT",		fox.PAD_R ) -- used in turret

	-- 声かけ
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CALL",	fox.PAD_L1 )

	-- 装備
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_PAD_PRIMARY",		fox.PAD_U )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_PAD_SECONDARY",	fox.PAD_D )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_PAD_SUPPORT",		fox.PAD_L )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_PAD_ITEM",		fox.PAD_R )

	-- 犬(PadDogActions)
    Pad.RegisterButtonAssign( "DOG_PAD_ACCEL",	fox.PAD_B )
    Pad.RegisterButtonAssign( "DOG_PAD_BREAK",	fox.PAD_X )
	Pad.RegisterButtonAssign( "DOG_PAD_BARK_NORMAL",	fox.PAD_R )
	Pad.RegisterButtonAssign( "DOG_PAD_BARK_MAD",	fox.PAD_L )
	Pad.RegisterButtonAssign( "DOG_PAD_BARK_JOY",	fox.PAD_U )
	Pad.RegisterButtonAssign( "DOG_PAD_SQUAT",	fox.PAD_A )
	Pad.RegisterButtonAssign( "DOG_PAD_SCENT",	fox.PAD_Y )
	Pad.RegisterButtonAssign( "DOG_PAD_CRAWL",	fox.PAD_D )

	-- MotherBase端末
	TppPadOperatorUtility.RegisterButtonAssign	( "MB_DEVICE",	fox.PAD_START )

	-- ストライカー
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_RIDEON",		fox.PAD_Y )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_RIDEOFF",		fox.PAD_Y )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_FIRE",			fox.PAD_L1 )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_BREAK",		fox.PAD_L2 )	-- アナログ対応をしたので不要
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_ACCEL",		fox.PAD_R2 )	-- アナログ対応をしたので不要
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_CHANGESIGHT",	fox.PAD_R3 )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_SWITCH_LIGHT",	fox.PAD_R )

	--アクセル・ブレーキ(トリガーとして登録。UIのアイコン表示のためにボタンとしても登録)
	TppPadOperatorUtility.RegisterTriggerAssign( "VEHICLE_TRIGGER_BREAK",	fox.PAD_TRIGGER_LEFT )
	TppPadOperatorUtility.RegisterTriggerAssign( "VEHICLE_TRIGGER_ACCEL",	fox.PAD_TRIGGER_RIGHT )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_TRIGGER_BREAK",	fox.PAD_L2 )	-- アナログ対応をしたので不要
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_TRIGGER_ACCEL",	fox.PAD_R2 )	-- アナログ対応をしたので不要

	-- スティック
	TppPadOperatorUtility.RegisterStickAssign( "PL_STICK_L", fox.PAD_STICK_LEFT, "Player", false, false )
	TppPadOperatorUtility.RegisterStickAssign( "PL_STICK_R", fox.PAD_STICK_RIGHT, "Player", false, false )

	-- 中メタル
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_RIDEON",	fox.PAD_Y )
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_RIDEOFF",	fox.PAD_Y )
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_CARRY",	fox.PAD_A )
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_HOLD",		fox.PAD_L2 )
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_DASH",		fox.PAD_X )
	TppPadOperatorUtility.RegisterButtonAssign( "MGM_PAD_SHOT",		fox.PAD_R2 )
end,

-- 車両のためのパッドアサイン
vehicleAssigns = function()
	local localPlayerCharacter = TppPlayerUtility.GetLocalPlayerCharacter()
	if TppPlayerUtility.IsRidingVehicle( localPlayerCharacter ) then
		Fox.Log("Ride On Vehicle")
		TppPadOperatorUtility.RegisterButtonAssign( "PL_CALL", fox.PAD_X )
		TppPadOperatorUtility.RegisterButtonAssign( "PL_SUB_CAMERA", fox.PAD_R1 )
	end
	if TppPlayerUtility.IsRidingStryker( localPlayerCharacter ) then
		Fox.Log("Ride On Stryker")
		TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_TOGGLE_WEAPON", fox.PAD_R )
	end
end,

defaultAssigns = function( CqcButtonConfig )

	Fox.Log("default Pad Assignment : CqcButtonConifg is " .. tostring(CqcButtonConfig) )

	local sysL1 = fox.PAD_L1
	local sysL2 = fox.PAD_L2
	local sysR1 = fox.PAD_R1
	local sysR2 = fox.PAD_R2

	if Pad.GetDeviceType() == "PAD_DEVICE_TYPE_X360" then
		-- nothing
   	elseif Pad.GetDeviceType() == "PAD_DEVICE_TYPE_PS3" then
		sysL1 = fox.PAD_L2;
		sysL2 = fox.PAD_L1;
		sysR1 = fox.PAD_R2;
		sysR2 = fox.PAD_R1;
	else
		-- use x360, so nothing
	end

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ACTION",			fox.PAD_Y )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_RELOAD",			fox.PAD_B )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SQUAT",			fox.PAD_A )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CALL",			sysL1 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SUB_CAMERA",		sysR1 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_HOLD",			sysL2 )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SHOT",			sysR2 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_DASH", 			fox.PAD_L3 )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_STOCK",			fox.PAD_R3 ) -- L/R over the shoulder during aim mode

	if ( CqcButtonConfig ) then
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC",				fox.PAD_X )
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_EVADE_ACTION",	sysR2 )
	else
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC",				sysR2 )
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_EVADE_ACTION",	fox.PAD_X )
	end

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC_INTERROGATE",	sysL1 )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC_KNIFE_KILL",	fox.PAD_Y )

	TppPadOperatorUtility.RegisterButtonAssign	( "HORSE_PAD_ACCEL",	fox.PAD_X )
	TppPadOperatorUtility.RegisterButtonAssign	( "HORSE_PAD_CLING",	fox.PAD_A )

end,

-- FPSタイプ
fpsTypeAssigns = function( CqcButtonConfig )

	Fox.Log("fpsTypeAssigns : CqcButtonConifg is " .. tostring(CqcButtonConfig) )

	local sysL1 = fox.PAD_L1
	local sysL2 = fox.PAD_L2
	local sysR1 = fox.PAD_R1
	local sysR2 = fox.PAD_R2

	if Pad.GetDeviceType() == "PAD_DEVICE_TYPE_X360" then
		-- nothing
   	elseif Pad.GetDeviceType() == "PAD_DEVICE_TYPE_PS3" then
		sysL1 = fox.PAD_L2;
		sysL2 = fox.PAD_L1;
		sysR1 = fox.PAD_R2;
		sysR2 = fox.PAD_R1;
	else
		-- use x360, so nothing
	end

	-- プレイヤー操作
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_ACTION",			fox.PAD_A )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SQUAT",			fox.PAD_B )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_RELOAD",			fox.PAD_X )

	if ( CqcButtonConfig ) then
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC",				fox.PAD_Y )
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_EVADE_ACTION",	sysR2 )
	else
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC",				sysR2 )
		TppPadOperatorUtility.RegisterButtonAssign	( "PL_EVADE_ACTION",	fox.PAD_Y )
	end

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC_KNIFE_KILL",	fox.PAD_A )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_STOCK",			fox.PAD_R3 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_DASH", 			fox.PAD_L3 )

	-- 馬(PadTppPlayerRideHorseActions)
	TppPadOperatorUtility.RegisterButtonAssign	( "HORSE_PAD_ACCEL",	fox.PAD_A )
	TppPadOperatorUtility.RegisterButtonAssign	( "HORSE_PAD_CLING",	fox.PAD_B )

	-- ストライカー
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_RIDEON",		fox.PAD_A )
	TppPadOperatorUtility.RegisterButtonAssign( "VEHICLE_PAD_RIDEOFF",		fox.PAD_A )

	-- トリガー操作
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CALL",			sysL1 )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SUB_CAMERA",		sysR1 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_HOLD",			sysL2 )
	TppPadOperatorUtility.RegisterButtonAssign	( "PL_SHOT",			sysR2 )

	TppPadOperatorUtility.RegisterButtonAssign	( "PL_CQC_INTERROGATE",	sysL1 )

end,

-- SquadLocater以下のEnemyのenable/disableを切り替える
-- EnemyUtilityにあるべき？
SetSquadEnableState = function( locator, enableFlag )
	if ( locator:IsKindOf( "ChCharacterLocator" ) == false ) then
		Fox.Log("arg1 is not a ChCharacterLocator")
		return false
	end
	local charaObj = locator.charaObj
	if ( Entity.IsNull(charaObj) ) then
		Fox.Log("charaObj is null")
		-- Unreallize状態？
		return true
	end
	if ( charaObj:IsKindOf( "AiSquadObject" ) == false ) then
		Fox.Log("not a AiSquadObject")
		return false
	end
	charaObj:SetMembersEnable( enableFlag )
	return true
end

} -- TppPlayerPadConfig


text:
concurrent = require 'concurrent'

function pong(n)
    for i = 1, n do
        local msg  = concurrent.receive()
        print('pong received message from ping')
        concurrent.send(msg.from, { from = concurrent.self(), body = 'pong' })
        print('pong sent reply to ping')
    end
end

function ping(pid)
    while true do
        concurrent.send(pid, { from = concurrent.self(), body = 'ping' })
        print('ping sent message to pong')
        local msg = concurrent.receive(1000)
        if not msg and not concurrent.isalive(pid) then
            print('ping exiting because pong is not alive anymore')
            concurrent.exit()
        end
        print('ping received reply from pong')
    end
end

pid = concurrent.spawn(pong, 3)
concurrent.spawn(ping, pid)

concurrent.loop()


text:
local tag = "AFK"

afksys = {}
afksys.AFKTime = CreateConVar("mp_afktime", "90", { FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_NOTIFY }, "The time it takes for a player to become AFK when inactive.")

local PLAYER = FindMetaTable("Player")

function PLAYER:IsAFK()
	return self.AFK_Is
end
function PLAYER:AFKTime()
	return self.AFK_Time
end

if SERVER then
	util.AddNetworkString(tag)

	net.Receive(tag, function(_, ply)
		local is = net.ReadBool()
		ply.AFK_Is = is
		hook.Run("AFK", ply, is, ply.AFK_Time)
		ply.AFK_Time = is and CurTime() - afksys.AFKTime:GetInt() or nil
		net.Start(tag)
			net.WriteUInt(ply:EntIndex(), 8)
			net.WriteBool(is)
		net.Broadcast()
	end)

	local w = Color(194, 210, 225)
	local g = Color(127, 255, 127)
	hook.Add("AFK", "AFKNotification", function(ply, is, time)
		ply:EmitSound(not is and "replay/cameracontrolmodeentered.wav" or "replay/cameracontrolmodeexited.wav")
		if not is and time then
			ply:ChatAddText(g, "Welcome back! ", w, "You were away for ", g, string.NiceTime(math.max(0, CurTime() - time - afksys.AFKTime:GetInt())), w, ".")
		end
	end)
elseif CLIENT then
	local function CurTime()
		return _G.CurTime and _G.CurTime() or 0
	end

	afksys.Mouse = { x = 0, y = 0 }
	afksys.Focus = system.HasFocus()
	afksys.Back = CurTime()
	afksys.Gone = CurTime()
	afksys.Is = false

	local function Input()
		if not afksys.Gone then return end
		if afksys.Is then
			afksys.Back = afksys.Gone
			net.Start(tag)
				net.WriteBool(false)
			net.SendToServer()
		end
		afksys.Gone = CurTime()
		afksys.Is = false
	end
	hook.Add("StartCommand", tag, function(ply, cmd)
		if ply ~= LocalPlayer() or not afksys.Gone then return end
		local mouseMoved = system.HasFocus() and (afksys.Mouse.x ~= gui.MouseX() or afksys.Mouse.y ~= gui.MouseY()) or false
		if  mouseMoved or
			cmd:GetMouseX() ~= 0 or
			cmd:GetMouseY() ~= 0 or
			cmd:GetButtons() ~= 0 or
			(afksys.Focus == false and afksys.Focus ~= system.HasFocus())
		then
			Input()
		end
		if afksys.Gone + afksys.AFKTime:GetInt() < CurTime() and not afksys.Is then
			afksys.Is = true
			net.Start(tag)
				net.WriteBool(true)
			net.SendToServer()
		end
	end)
	hook.Add("KeyPress", tag, Input)
	hook.Add("KeyRelease", tag, Input)
	hook.Add("PlayerBindPress", tag, Input)
	local lastAFK = CurTime() - 3
	local function getAFKtime()
		local lastInput
		if afksys.Is then
		 	lastInput = afksys.Gone
		 	lastAFK = CurTime()
		else
		 	lastInput = afksys.Back
		end
		-- return time since last input since time it takes to get afksys as well as
		-- the time it was before we came back
		return math.max(lastAFK - (lastInput + afksys.AFKTime:GetInt()), 0), lastAFK
	end

	net.Receive(tag, function()
		local ply = Entity(net.ReadUInt(8))
		local is = net.ReadBool()
		ply.AFK_Is = is
		hook.Run("AFK", ply, is, ply.AFK_Time)
		ply.AFK_Time = is and CurTime() - afksys.AFKTime:GetInt() or nil
	end)

	surface.CreateFont(tag, {
		font = "Roboto Cn",
		size = 36,
		italic = true,
		weight = 800,
	})
	surface.CreateFont(tag .. "_time", {
		font = "Roboto Bk",
		size = 48,
		italic = false,
		weight = 800,
	})

	local a = 0
	local function DrawTranslucentText(txt, x, y, col)
		surface.SetTextPos(x + 2, y + 2)
		surface.SetTextColor(Color(0, 0, 0, 127))
		surface.DrawText(txt)

		surface.SetTextPos(x, y)
		if col then
			surface.SetTextColor(Color(col.r, col.g, col.b, 164))
		else
			surface.SetTextColor(Color(255, 255, 255, 164))
		end
		surface.DrawText(txt)
	end
	afksys.Draw = CreateConVar("cl_afk_hud_draw", "1", { FCVAR_ARCHIVE })
	hook.Add("HUDPaint", tag, function()
		if not afksys.Draw:GetBool() then return end
		afksys.Focus = system.HasFocus()

		local AFKTime, lastAFK = getAFKtime()

		-- wait 3 seconds before hiding
		local show = afksys.Is
		show = show or CurTime() < lastAFK + 3
		a = Lerp(FrameTime() * 3, a, show and 1 or 0)
		if a <= 0.005 then return end

		surface.SetAlphaMultiplier(a)

		surface.SetFont(tag)
		local txt = "You've been away for"
		local txtW, txtH = surface.GetTextSize(txt)

		surface.SetFont(tag .. "_time")
		local timeString = string.NiceTime(AFKTime)
		local timeW, timeH = surface.GetTextSize(timeString)

		local wH = txtH + timeH

		surface.SetDrawColor(Color(0, 0, 0, 127))
		surface.DrawRect(0, ScrH() * 0.5 * 0.5 - wH * 0.5 - txtH * 0.33, ScrW(), wH + txtH * 0.33 * 2 - 3)

		surface.SetFont(tag)
		DrawTranslucentText(txt, ScrW() / 2 - txtW / 2, ScrH() / 2 / 2 - wH / 2)

		surface.SetFont(tag .. "_time")
		local col
		if afksys.Is then
			col = Color(140, 159, 231)
		else
			col = Color(167, 255, 167)
		end
		DrawTranslucentText(timeString, ScrW() / 2 - timeW / 2, ScrH() / 2 / 2 - wH / 2 + txtH, col)

		surface.SetAlphaMultiplier(1)
	end)
end