Love2d Web with Solana SDK connect Phantom wallet - Part 2 END (Solana Integration)

 Love2d Web with Solana SDK connect Phantom wallet - Part 2 END



    From previous part, we created game flow UI that work well without actual connect Phantom Wallet, now we'll finish up all, i hope anyone can picture what's entirely purpose i want to show. From widely Game Engine till niche Game Framework just a bit wall to breaking out if you already read Unity2d WebGL + Solana SDK, once you finish this thread, you can do same things for all any kind of Web3/HTML5/WebGL that exported from any Engine/Framework.Bridge Love2d communicate Love.js (Love2d side)
    From previous part if you already extract "love-with-js-main.zip" you should have "love-with-js-main" folder and inside look like below image.

we'll use "js-server.lua" for Love2d "main.lua", let's implement main.lua on Visual Studio Code.
1. import js-server.lua at the top of main.lua.

local js = require("js-server") -- add this line
local gameScene = require("game")
local currentScene = "main"

2. in function update must be call js.update inside.

function love.update(dt)
    js.update() -- add this line

    if currentScene == "game" then
        gameScene.update(dt)
    end
end

3. in event mouse press, we'll send debug log from Love2d to love.js and you'll see it on browser console.

function love.mousepressed(x, y, button)
    if currentScene == "main" then
        if button == 1 and
            x >= btConnect.x and x < btConnect.y + btConnect.Image:getWidth() and
            y >= btConnect.y and y < btConnect.y + btConnect.Image:getHeight() then
            js.run("console_log", "green button pressed!") -- add this line
            currentScene = "game"
            gameScene.load()
        end
    elseif currentScene == "game" then
        local result = gameScene.mousepressed(x, y, button)
        if result == "main" then
            js.run("console_log", "red button pressed!") -- add this line
            currentScene = "main"
        end
    end
end

    Move to game.lua we need js-server.lua too.
1. import js-server.lua at the top of game.lua

local js = require("js-server") -- add this line
local game = {}

local btDisconnect = {}

2. inside update function, don't forget to save file.

function game.update(dt)
    js.update() -- add this line
end

If you try to run main.lua you'll see an error because of main.lua not found js-server.lua


    Let's copy js-server.lua from "love-with-js-main" folder and paste beside main.lua, now your main.lua, should be fine without any error.

Bridge Love2d communicate Love.js (Love.js side)
    You may need to new window from Visual Studio Code and choose File > Open Folder ..., browse to C:\[your path]\love.js-master\src\compat and open, OR stay on same window as main.ua and  browse to C:\[your path]\love.js-master\src\compat and open index.html.

1. import lua-server.js to index.html.

  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=1">
    <title>{{title}}</title>

    <!-- Load custom style sheet -->
    <link rel="stylesheet" type="text/css" href="theme/love.css">
    <script src="lua-server.js"></script> <!-- add this line -->
  </head>

2. implement function runCommand after end of window addEventlistener keydown, once we click on green or red button from Love2d on index.html runCommand function should be call and print console.log on browser console.

      window.addEventListener("keydown", function(e) {
        // space and arrow keys
        if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
          e.preventDefault();
        }
      }, false);
      // add runCommand function() { }
      function runCommand(cmd, args) {
          switch (cmd) {
              case "console_log":
                  console.log(args[0])
                  break
              case "console_warn":
                  console.warn(args[0])
                  break
              default:
                  break
          }
      };

3. copy lua-server.js from "love-with-js-main" and paste to "C:\[your path]\love.js-master\output_folder" an "output_folder" come from previous part.


Build test bridge Love2d to Love.js
     Drag all file and right-click > Compress to ... > ZIP File, then rename to main.love like previous part.


    Copy "main.love" to "C:\[your path]\love.js-master\"

    

    Open cmd "command prompt" from windows search, type "cd C:\[your path]\love.js-master\", hit Enter. Now you're in love.js-master folder, type 

node index.js main.love output_folder --title "Love2d Bridge Love.js" --compatibility 

and hit Enter

    One last thing you need to copy "love.js" from "love-with-js-main" and paste to C:\[your path]\love.js-master\output_folder, it will ask to replace love.js just replaced.
from cmd move to C:\[your path]\love.js-master\output_folder by type "cd output_folder" and hit Enter, then running localhost by type "http-server" and hit Enter. Your localhost should be run fine similar below image.

open urt http://127.0.0.1:8080 on your browser. open your developer console (i'm using Edge, click ... > More tools > Developer tools)

click green button. You'll see message on console that we send from Love2d and come in runCommand function on index.html, it's work! you can leave both http-server running and this tab too. it can be save your time to re-run them back.

**another pain point of this, any time when we compile main.love to be web game by this cmd.
node index.js main.love output_folder --title "Love2d Bridge Love.js" --compatibility 
in output_folder will be replaced with original love.js then we need to copy love.js from "love-with-js-main" every time. So just replace love.js from template before it compiled. 

copy love.js from "love-with-js-main" and paste to "C:\[your path]\love.js-master\src\compat" and replaced
Connect Wallet & Disconnect Wallet
[love2d side]
1. copy "dkjson.lua" from extracted folder "dkjson-master" and put into "love2d-hello-world".
"dkjson-master"

"love2d-hello-world"

2. go to main.lua, we need to import dkjson.lua

local js = require("js-server")
local gameScene = require("game")
local currentScene = "main"
local json = require("dkjson") -- add this line
  
3. we need to add listener in onload function both connect and disconnect event, also convert json string to json object

function love.load()
    js.set("connect_event", function(raw)
        local res = json.decode(raw)

        if res ~= nil then
            if res.success then
                js.run("console_log", "success! " .. res.data)
                currentScene = "game"
                gameScene.load()
            else
                js.run("console_warn", res.data)
            end
        end
    end)

    js.set("disconnect_event", function(raw)
        local res = json.decode(raw)

        if res ~= nil then
            if res.success then
                js.run("console_log", "disconnected!")
                currentScene = "main"
            else
                js.run("console_warn", res.data)
            end
        end
    end)

4. on mouse press function, we need to add function call connect and disconnect for each button, just comment out scene change on love2d, leave js.run connect to love.js side.

function love.mousepressed(x, y, button)
    if currentScene == "main" then
        if button == 1 and
            x >= btConnect.x and x < btConnect.y + btConnect.Image:getWidth() and
            y >= btConnect.y and y < btConnect.y + btConnect.Image:getHeight() then
            js.run("console_log", "green button pressed!")
            --currentScene = "game"
            --gameScene.load()
            js.run("connect_wallet") -- add this line
        end
    elseif currentScene == "game" then
        local result = gameScene.mousepressed(x, y, button)
        if result == "main" then
            js.run("console_log", "red button pressed!")
            --currentScene = "main"
            js.run("disconnect_wallet") -- add this line
        end
    end
end

[love.js side]
1. on index.html at function runCommand add case connect_wallet and case disconnect_wallet like below image.

     function runCommand(cmd, args) {
          switch (cmd) {
              case "console_log":
                  console.log(args[0])
                  break
              case "console_warn":
                  console.warn(args[0])
                  break

              case "connect_wallet":
                  MyGameBridge.connectWallet();
                  break      

              case "disconnect_wallet":
                  MyGameBridge.disconnectWallet();
                  break

              default:
                  break
          }
      };

2. next after end "runCommand" and before "var Module = {...}" create "window.MyGameBridge = {...}" like below, provide connect and disconnect to phantom app, and package result json to string with jStr function. return event must be match event in main.lua both connect_event and disconnect_event.

     window.MyGameBridge = {
          jStr: function(status, payload) {
              return JSON.stringify({success: status, data: payload});
          },

          connectWallet: function() {
              console.log("Connecting to Phantom...");
              try {
                  if (window.solana && window.solana.isPhantom) {
                      window.solana.connect()
                      .then(resp => {
                          lua("connect_event", window.MyGameBridge.jStr(true, resp.publicKey.toString()))
                      })
                      .catch(err => {
                          lua("connect_event", window.MyGameBridge.jStr(false, err.message))
                      });
                  } else {
                      lua("connect_event", window.MyGameBridge.jStr(false, "err wallet not found >> "))
                  }
              } catch (err) {
                  lua("connect_event", window.MyGameBridge.jStr(false, err.message))
              }
          },

          disconnectWallet: async function () {              
              if (window.solana && window.solana.isConnected) {
                  try {
                      await window.solana.disconnect();
                      lua("disconnect_event", window.MyGameBridge.jStr(true, "success disconnected."))
                  } catch (err) {
                      lua("disconnect_event", window.MyGameBridge.jStr(false, err.message))
                  }
              } else {
                  lua("disconnect_event", window.MyGameBridge.jStr(false, "unknown error."))
              }
          },
      };

after done saved index.html, let zip all file to "main.love" again copy paste to "love.js-master" and run cmd node index.js game.love output_folder --title "My Bridge Game" --compatibility again, you don't to run http-server if previous step you leave it running, just go to browser game tab previous step and click hard refresh or empty cache and hard refresh.

Test Connect and Disconnect
    Click green button when Phantom App appear, just fill password and click connect, once successful you should see your public key on console and screen change from main to game. similar below image.
 

    Once success connect, try click red button, your screen should be back to view green button on main scene, and you should see "disconnected!" on console.

Implement Buy Item (Love2d side)
1. on game.lua at top of file import dkjson, user_wallet to keep player connect wallet address and last one is receiver wallet address (your wallet address to receive SOL from player/user)

local js = require("js-server")
local json = require("dkjson") -- add this line
local game = {}

local btDisconnect = {}
local btBuyItem = {}
local total_potion = 0

local user_wallet = "" -- add this line
local receiver_wallet = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -- add wallet merchant/receiver address

2. next line after implement function store user wallet from main.lua

function game.wallet(public_address)
    user_wallet = public_address
end

3. still on game.lua under on load function implement listener function "buy_item_event", inside function handle success and err result, if success so user/player transaction is completed then you give an item to user/player. 

function game.load()
    -- add below listener
    js.set("buy_item_event", function(raw)
        local res = json.decode(raw)

        if res ~= nil then
            if res.success then
                total_potion = total_potion + 1
                js.run("console_log", "Purchased! ".. res.data .." Total Potion = " .. total_potion)
            else
                js.run("console_warn", res.data)
            end
        end
    end)

4. game.lua, on mouse press function add trigger yellow button calling buy item, it required 3 parameters ordering as user wallet, receiver wallet and item price (SOL).

function game.mousepressed(x, y, button)
    if button == 1 and
        x >= btDisconnect.x and x < btDisconnect.y + btDisconnect.Image:getWidth() and
        y >= btDisconnect.y and y < btDisconnect.y + btDisconnect.Image:getHeight() then
        return "main"    
    elseif button == 1 and
        x >= btBuyItem.x and x < btBuyItem.y + btBuyItem.Image:getWidth() and
        y >= btBuyItem.y and y < btBuyItem.y + btBuyItem.Image:getHeight() then
        js.run("console_log", "buy addr: " .. user_wallet .. " recv: " .. receiver_wallet .. " SOL: " .. 0.002)
        js.run("buy_item", user_wallet, receiver_wallet, 0.002)
    end
end

5. switch to main.lua on load function inside connect event if success you need to user wallet by add gameScene.wallet and pass parameter res.data (user/player wallet address), don't forget to save both main.lua and game.lua file.

function love.load()
    js.set("connect_event", function(raw)
        local res = json.decode(raw)

        if res ~= nil then
            if res.success then
                js.run("console_log", "success! " .. res.data)
                gameScene.wallet(res.data) -- add thisline
                currentScene = "game"
                gameScene.load()
            else
                js.run("console_warn", res.data)
            end
        end
    end)

Implement Buy Item (Love.js side)
1. open index.html (C:\[your path]\love.js-master\src\compat) import solana web3 sdk in between head tag. at line after "lua-server.js".

  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1, maximum-scale=1">
    <title>{{title}}</title>

    <!-- Load custom style sheet -->
    <link rel="stylesheet" type="text/css" href="theme/love.css">
    <script src="lua-server.js"></script>
    <script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script><!-- add this line -->
  </head>

2. scroll down to runCommand function add case buy item event. Then pass an args[0] args[1] and args[2] to solanaSignAndSend

function runCommand(cmd, args) {
          switch (cmd) {
              case "console_log":
                  console.log(args[0])
                  break
              case "console_warn":
                  console.warn(args[0])
                  break

              case "connect_wallet":
                  MyGameBridge.connectWallet();
                  break      

              case "disconnect_wallet":
                  MyGameBridge.disconnectWallet();
                  break
             
              case "buy_item":
                  // args[0] = user_wallet, args[1] = receiver_wallet, args[2] = item price
                  MyGameBridge.solanaSignAndSend(args[0], args[1], args[2]);
                  break

              default:
                  break
          }
      };

3. scroll down to window.MyGameBridge add function solanaSignAndSend after disconnectWallet. Saved index.html.

        solanaSignAndSend: async function (publicAddress, recipientAddress, solValue) {
              // when release change to "mainnet-beta", "devnet" use for testing only
              try {
                  const clusterApi = solanaWeb3.clusterApiUrl("devnet");
                  const connection = new solanaWeb3.Connection(clusterApi);
                  const fromPublicKey = new solanaWeb3.PublicKey(publicAddress);
                  const toPublicKey = new solanaWeb3.PublicKey(recipientAddress);
                  const transaction = new solanaWeb3.Transaction().add(
                      solanaWeb3.SystemProgram.transfer({
                          fromPubkey: fromPublicKey,
                          toPubkey: toPublicKey,
                          lamports: BigInt(Math.floor(solanaWeb3.LAMPORTS_PER_SOL * solValue)),
                      })
                  );

                  transaction.feePayer = fromPublicKey;
                  transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
             
                  const res = await window.solana.signAndSendTransaction(transaction);
                  if (!res || !res.signature ) {
                    lua("buy_item_event", window.MyGameBridge.jStr(false, "No signature."))
                    return;
                  }

                  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
                  if (!blockhash || !lastValidBlockHeight ) {
                    lua("buy_item_event", window.MyGameBridge.jStr(false, "Invalid blockhash."))
                    return;
                  }

                  const confirm = await connection.confirmTransaction({
                      signature: res.signature,
                      blockhash: blockhash,
                      lastValidBlockHeight: lastValidBlockHeight
                  });

                  var strJson = JSON.stringify(confirm);
                  lua("buy_item_event", window.MyGameBridge.jStr(true, strJson))

              } catch (error) {
                  console.error("err >> ", error.message);
                  lua("buy_item_event", window.MyGameBridge.jStr(false, error.message))
              }
          },

Test Solana SDK integration
Now your game should be ready sell an item. zip your love2 game to "main.love" and copy paste to folder "love.js-master", then open cmd "cd your_path/love.js-master" hit enter. Run cmd.

node index.js main.love output_folder --title "My Bridge Game" --compatibility

then refresh your current game tab, your game should be run without any error, click green button to connect, main scene should be change to game scene.



At game scene, now Potion is "0", once click yellow button for purchase potion, it's trigger Phantom popover for confirm payment (use airdrop from first Part to be enough for test), click "Confirm"


Wait a little longer when transaction completed, you should receive confirm transaction on Love2d game.lua just handle as you need. (Don't forget to check your balance in your wallet).


Now your Love2d as web game can sell any item in your game and receive purchased as SOL currency. Done, hope you enjoy making game and make money on web platform with Love2d. :)

Buy Me a Coffee.

Free Download Content Updated!!

Provide love2d connect wallet [full]
>> Download << 

Provide love.js connect wallet [full]
>> Download <<