Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Pages: [1] 2

Author Topic: TOTAL CONVERSIONS?  (Read 7141 times)

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
TOTAL CONVERSIONS?
« on: July 15, 2017, 06:37:00 AM »

The info I gathered about how to make one is confusing, often referencing a dozen core files that need replacing and many scripts/plugins that need re-writing.

I found no tutorials or actual useful information - it's all very vauge and incomplete.

Is there any PROPER information to be had? Or at the very least, a Total Conversion works on 0.81 so I can take a look at the files there?
Logged

etherealblade

  • Commander
  • ***
  • Posts: 134
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #1 on: July 15, 2017, 08:01:27 AM »

If you've read all there is on the forum about total conversions, then you know that it is the absolute hardest to do. Not only that, but players tend to want to play with factions that can integrate with other faction mods. A total conversion completely debunks that requiring you to trade off really badass mods like (to name a few but not the whole list): Diable Avionics, Blackrock Drive yards, Neutrino, Scy....Etc.
So unless you can make a TC that bests all those  :-\. You're going to have a low interest pool. Total conversions are a 100 percent labor of love so you'd be in it alone for the long haul.

You also have mods that are Total Enhancers like Nexerlin. It improves the game while allowing all factions mods to be incorporated into a better quality of life starsector. If you want to shape your starsector universe. I suggest aiming for a Total Enhancer that is compatible with other faction mods, nexerlin, and add something Nexerlin doesn't. There is lots of support and interest in that respect. The mod resources section and the Tartiflette TOYBOX has a lot of things to help you on your way to modding.

Just so you know Modding Easiest to hardest is:
1) Weapon/Ship mod
2) Custom weapon/Ship mod (this can be easy or complex depending on if you add animations, custom graphics,
    coded weapon effects,.... etc.)
3) Mission
4) Campaign Integration/Playing nice with other mods (requires basic understanding of coding so starsector knows what you want to do)
5) Total Enhancement (Nexerlin, requires code skills and Ingenuity)
5) Total Conversion  (Can do all of the above, but you have UnDebatable Purpose, Vision, and naysayers be damned.) Yes they work for any version, but you have to command a lot of understanding of how the starsector game works as a whole.

Your ambition is appreciated. To get more notice. Pump out some good looking ship sprites and then notice what happens  ;D

This got wordy for just a reply. I just wanted to know what you are getting yourself into if you continue along this path.   8)
Logged
Spoiler

[close]

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #2 on: July 15, 2017, 09:17:25 AM »

Well, s****.
It seems without decomplining and learning java, it is impossible to get it to work.

I already have ships/weapons/factions that can work as additions, BUT the style and balance really doesn't fit with regular Starsector.


B.t.w. - TC's can work with other factions, it's a just a few tweaks.
Logged

Thaago

  • Global Moderator
  • Admiral
  • *****
  • Posts: 7220
  • Harpoon Affectionado
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #3 on: July 15, 2017, 10:15:43 AM »

You don't need to decompile anything, but you do need to learn Java. What you need to do, in brief, to make a total conversion is replacing the default SectorGen located in data.scripts.world.SectorGen by using the replace tag in the mod_info.json.

Replacing that file will stop the default systems from generating - you can then add whatever content you like. I'm sure there are more things to overwrite as well, but thats the basic procedure. You will need to know enough Java coding to replace SectorGen.
Logged

etherealblade

  • Commander
  • ***
  • Posts: 134
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #4 on: July 15, 2017, 04:49:55 PM »

You could just add your factions in to vanilla starsector anyway. I mean look at approlight....we love it all the same ;).
Your faction could simply be from another dimension..I mean exingency totally licked black hole travel so hey. ;) We wont know until you give us some examples.
Logged
Spoiler

[close]

Cyan Leader

  • Admiral
  • *****
  • Posts: 718
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #5 on: July 15, 2017, 08:04:23 PM »

So unless you can make a TC that bests all those  :-\. You're going to have a low interest pool.

I strongly disagree with this statement. Uomoz Sector and Ironclads were a fry cry from the best mod factions the community has to offer but that doesn't mean they weren't interesting on their own. Total conversions have a lot of uniqueness to themselves that make them standout, like extreme balance changes or unique campaign mechanics.

There is no need to restrict modder's creativity and desires just because it doesn't follow the established paradigm.
Logged

cjuicy

  • Captain
  • ****
  • Posts: 353
  • Figuring out how the hell to wear heels (She/it)
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #6 on: July 15, 2017, 09:24:24 PM »

So unless you can make a TC that bests all those  :-\. You're going to have a low interest pool.

I strongly disagree with this statement. Uomoz Sector and Ironclads were a fry cry from the best mod factions the community has to offer but that doesn't mean they weren't interesting on their own. Total conversions have a lot of uniqueness to themselves that make them standout, like extreme balance changes or unique campaign mechanics.

There is no need to restrict modder's creativity and desires just because it doesn't follow the established paradigm.
I remember Uomoz. That was a HORROR to run on my PC at the time. Totally worth the lag, though
Logged
It's been a long time, but I still love ya!

- Pfp done by Sleepyfish!

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #7 on: July 16, 2017, 02:58:49 AM »

You don't need to decompile anything, but you do need to learn Java. What you need to do, in brief, to make a total conversion is replacing the default SectorGen located in data.scripts.world.SectorGen by using the replace tag in the mod_info.json.

Replacing that file will stop the default systems from generating - you can then add whatever content you like. I'm sure there are more things to overwrite as well, but thats the basic procedure. You will need to know enough Java coding to replace SectorGen.

I already made have SectorGen file that generates all of my starting systems and sets up factions.

Code
package data.scripts.world;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.campaign.FactionAPI;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.RepLevel;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.campaign.SectorGeneratorPlugin;
import com.fs.starfarer.api.campaign.StarSystemAPI;
import com.fs.starfarer.api.impl.campaign.CoreCampaignPluginImpl;
import com.fs.starfarer.api.impl.campaign.CoreScript;
import com.fs.starfarer.api.impl.campaign.events.CoreEventProbabilityManager;
import com.fs.starfarer.api.impl.campaign.fleets.BountyPirateFleetManager;
import com.fs.starfarer.api.impl.campaign.fleets.EconomyFleetManager;
import com.fs.starfarer.api.impl.campaign.fleets.LuddicPathFleetManager;
import com.fs.starfarer.api.impl.campaign.fleets.MercFleetManager;
import com.fs.starfarer.api.impl.campaign.fleets.PirateFleetManager;
import com.fs.starfarer.api.impl.campaign.ids.Factions;
import com.fs.starfarer.api.impl.campaign.ids.Terrain;
import com.fs.starfarer.api.util.Misc;

import data.hullmods.HeavyArmor;
import data.scripts.world.corvus.Corvus;
import data.scripts.world.EMPTY.*;
import data.scripts.world.ISA.*;
import data.scripts.world.RSF.*;
import data.scripts.world.UIN.*;
import data.scripts.world.XLE.*;
import data.scripts.world.FFS.*;
import data.scripts.world.VNS.*;

public class SectorGen implements SectorGeneratorPlugin {

public void generate(SectorAPI sector) {
//ClassLoader cl = Global.getSettings().getScriptClassLoader();

StarSystemAPI system = sector.createStarSystem("Corvus");
//system.getLocation().set(16000 - 8000, 9000 - 10000);
system.setBackgroundTextureFilename("graphics/backgrounds/background4.jpg");

//sector.setCurrentLocation(system);
sector.setRespawnLocation(system);
sector.getRespawnCoordinates().set(-2500, -3500);

initFactionRelationships(sector);

      //RSF colonies
      new Barnard().generate(sector);
      new Vega().generate(sector);
      new Lomonosov().generate(sector);           
      new Gagarin().generate(sector);
      new Shelezyaka().generate(sector);

      //ISA colonies
      new Grant().generate(sector);
      new Shington().generate(sector);
      new Darloth().generate(sector);
      new Argos().generate(sector);

      //XLE colonies
      new Nexus().generate(sector);
      new Uomoz().generate(sector);
      new Procyon().generate(sector);

      //UIN colonies
      new Centronom().generate(sector);
      new Gianopolis().generate(sector);
      new Achiles().generate(sector);     

      //FFS colonies
      new Tart().generate(sector);
      new Adra().generate(sector);
      new Kores().generate(sector);
      new Impalo().generate(sector);
 
      //VNS colonies
      new Vaynar().generate(sector);
      new GammaDraconis().generate(sector);  

      //EMPTY
      new Thule().generate(sector);
      new Zero().generate(sector);
      new Valhalla().generate(sector);


LocationAPI hyper = Global.getSector().getHyperspace();
      //SectorEntityToken zinLabel = hyper.addCustomEntity("zin_label_id", null, "warfront_label", null);
      SectorEntityToken abyssLabel = hyper.addCustomEntity("opabyss_label_id", null, "rocks_label", null);
      SectorEntityToken telmunLabel = hyper.addCustomEntity("telmun_label_id", null, "ai_label", null);
      SectorEntityToken cathedralLabel = hyper.addCustomEntity("cathedral_label_id", null, "aliens_label", null);
      //SectorEntityToken coreLabel = hyper.addCustomEntity("core_label_id", null, "nomansland_label", null);

      //zinLabel.setFixedLocation(7500, 0);
      abyssLabel.setFixedLocation(-18000, 5000);
      telmunLabel.setFixedLocation(-18000, 18000);
      cathedralLabel.setFixedLocation(18000, -18000);
      //coreLabel.setFixedLocation(-12000, -12000);


SectorEntityToken deep_hyperspace = Misc.addNebulaFromPNG("data/campaign/terrain/hyperspace_map.png",
//SectorEntityToken deep_hyperspace = Misc.addNebulaFromPNG("data/campaign/terrain/hyperspace_map_filled.png",
  0, 0, // center of nebula
  Global.getSector().getHyperspace(), // location to add to
  "terrain", "deep_hyperspace", // "nebula_blue", // texture to use, uses xxx_map for map
  4, 4, Terrain.HYPERSPACE, null); // number of cells in texture

// PirateSpawnPoint pirateSpawn = new PirateSpawnPoint(sector, sector.getHyperspace(), 1, 15, system.getHyperspaceAnchor());
// system.addSpawnPoint(pirateSpawn);
// for (int i = 0; i < 2; i++) {
// pirateSpawn.spawnFleet();
// }

// need to do this after hyperspace terrain exists
//SectorProcGen.generate();
// this is done through settings.json, "plugins"->"newGameSectorProcGen"

sector.registerPlugin(new CoreCampaignPluginImpl());
sector.addScript(new CoreScript());
sector.addScript(new CoreEventProbabilityManager());
sector.addScript(new EconomyFleetManager());
sector.addScript(new MercFleetManager());
sector.addScript(new PirateFleetManager());
sector.addScript(new BountyPirateFleetManager());

}

public static void initFactionRelationships(SectorAPI sector) {

// forget why this is necessary - workaround for some JANINO issue, I think
Class c = HeavyArmor.class;

      FactionAPI RSF = sector.getFaction("RSF");
      FactionAPI ISA = sector.getFaction("ISA");
      FactionAPI UIN = sector.getFaction("UIN");     
      FactionAPI XLE = sector.getFaction("XLE");   
      FactionAPI FFS = sector.getFaction("FFS");
      FactionAPI VNS = sector.getFaction("VNS");  

      FactionAPI pirates = sector.getFaction("pirates"); 
      FactionAPI WDW = sector.getFaction("WDW");
      FactionAPI MAR = sector.getFaction("MAR");

      FactionAPI ROCK = sector.getFaction("ROCK");
      FactionAPI AI = sector.getFaction("AI");
      FactionAPI ALIEN = sector.getFaction("ALIENS");

      FactionAPI player = sector.getFaction("player");
      FactionAPI independent = sector.getFaction("independent");

      RSF.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      RSF.setRelationship(XLE.getId(), RepLevel.COOPERATIVE);
      RSF.setRelationship(UIN.getId(), RepLevel.NEUTRAL);
      RSF.setRelationship(FFS.getId(), RepLevel.SUSPICIOUS);

      ISA.setRelationship(UIN.getId(), RepLevel.COOPERATIVE);
      ISA.setRelationship(XLE.getId(), RepLevel.NEUTRAL);
      ISA.setRelationship(FFS.getId(), RepLevel.SUSPICIOUS);
 
  VNS.setRelationship(ISA.getId(), RepLevel.NEUTRAL);
  VNS.setRelationship(RSF.getId(), RepLevel.NEUTRAL);
  VNS.setRelationship(XLE.getId(), RepLevel.HOSTILE);
  VNS.setRelationship(UIN.getId(), RepLevel.SUSPICIOUS);
  VNS.setRelationship(FFS.getId(), RepLevel.SUSPICIOUS);  

      XLE.setRelationship(UIN.getId(), RepLevel.FAVORABLE);
      XLE.setRelationship(FFS.getId(), RepLevel.VENGEFUL);
  XLE.setRelationship(VNS.getId(), RepLevel.HOSTILE);  

      independent.setRelationship(FFS.getId(), RepLevel.HOSTILE);
      UIN.setRelationship(FFS.getId(), RepLevel.VENGEFUL);

      pirates.setRelationship(player.getId(), RepLevel.HOSTILE);
      pirates.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      pirates.setRelationship(RSF.getId(), RepLevel.HOSTILE);
      pirates.setRelationship(ISA.getId(), RepLevel.HOSTILE);
      pirates.setRelationship(XLE.getId(), RepLevel.HOSTILE);
      pirates.setRelationship(UIN.getId(), RepLevel.HOSTILE);
      pirates.setRelationship(FFS.getId(), RepLevel.FAVORABLE);
      pirates.setRelationship(WDW.getId(), RepLevel.FAVORABLE);
      pirates.setRelationship(MAR.getId(), RepLevel.FAVORABLE);
      pirates.setRelationship(VNS.getId(), RepLevel.HOSTILE);  

      WDW.setRelationship(player.getId(), RepLevel.HOSTILE);
      WDW.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      WDW.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      WDW.setRelationship(UIN.getId(), RepLevel.VENGEFUL);
      WDW.setRelationship(RSF.getId(), RepLevel.HOSTILE);
      WDW.setRelationship(XLE.getId(), RepLevel.HOSTILE);
      WDW.setRelationship(MAR.getId(), RepLevel.FAVORABLE);
      WDW.setRelationship(FFS.getId(), RepLevel.FAVORABLE);
      WDW.setRelationship(VNS.getId(), RepLevel.HOSTILE);  

      MAR.setRelationship(player.getId(), RepLevel.HOSTILE);
      MAR.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      MAR.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      MAR.setRelationship(UIN.getId(), RepLevel.HOSTILE);
      MAR.setRelationship(RSF.getId(), RepLevel.VENGEFUL);
      MAR.setRelationship(XLE.getId(), RepLevel.HOSTILE);
      MAR.setRelationship(FFS.getId(), RepLevel.FAVORABLE);
      MAR.setRelationship(VNS.getId(), RepLevel.HOSTILE);  

      ROCK.setRelationship(player.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(RSF.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(XLE.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(UIN.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(FFS.getId(), RepLevel.SUSPICIOUS);
      ROCK.setRelationship(AI.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(ALIEN.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(pirates.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(WDW.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(MAR.getId(), RepLevel.VENGEFUL);
      ROCK.setRelationship(VNS.getId(), RepLevel.VENGEFUL);  

      AI.setRelationship(player.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(RSF.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(XLE.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(UIN.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(FFS.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(ALIEN.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(pirates.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(WDW.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(MAR.getId(), RepLevel.VENGEFUL);
      AI.setRelationship(VNS.getId(), RepLevel.VENGEFUL);  

      ALIEN.setRelationship(player.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(RSF.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(ISA.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(XLE.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(UIN.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(FFS.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(WDW.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(independent.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(pirates.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(MAR.getId(), RepLevel.VENGEFUL);
      ALIEN.setRelationship(VNS.getId(), RepLevel.VENGEFUL);
}
}

But starting a new game doesn't work. Game loads. Mission work. New campaign does not.
Seems derelict related
Logged

etherealblade

  • Commander
  • ***
  • Posts: 134
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #8 on: July 16, 2017, 05:42:59 AM »

My intention wasn't to restrict their creativity. I was simply pointing out there was a paradigm, what they were getting into, and that they would need diligence and determination to see it through. You are right though, I played both ironclads and Uomoz a lot. Anyway. Glad others are chiming in. I wish you were around to tell me that when I was working on my mod way back when. So this is good.

Trash-man I support you!
Logged
Spoiler

[close]

Thaago

  • Global Moderator
  • Admiral
  • *****
  • Posts: 7220
  • Harpoon Affectionado
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #9 on: July 16, 2017, 07:56:22 AM »

In what way does it not work? You say the game loads - do you mean that your fleet spawns and everything looks the same as vanilla? Is there a crash/error and if so whats the end of the log?
Logged

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #10 on: July 16, 2017, 12:55:28 PM »

I mean the game starts, I get the menu, get the ships flying in the background, missions work and you can play with ships there.

But staring a New Game leads to a crash after character creation. So when the game generates the galaxy.
Errors were related with revenants and derelicts.
I managed to fix all the revenant errors, but even cloning derelicts and making a "new" faction didn't work. For example, even though I had a warden_Defense variant defined I got the  "Ship hull variant [warden_Defense] not found!" message
Logged

Thaago

  • Global Moderator
  • Admiral
  • *****
  • Posts: 7220
  • Harpoon Affectionado
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #11 on: July 16, 2017, 01:30:56 PM »

Please post the end of the log so we can see where the error occurred. Also, have you edited that variant at all? If so, check that not only is its filename correct, but also the ID inside of it. Or the error could be someplace else entirely and the game is giving a bogus error message.

I'm guessing there are other scripts than SectorGen responsible for spawning the derelicts and the remnants - if you want to get rid of them either post a question to the misc modding thread (Alex reads it) or send him a PM politely asking which you need to replace. He's really good about helping if you ask a specific question like "what file do I need to replace to stop remnants/derelicts spawning in a total conversion".
Logged

Histidine

  • Admiral
  • *****
  • Posts: 4688
    • View Profile
    • GitHub profile
Re: TOTAL CONVERSIONS?
« Reply #12 on: July 17, 2017, 06:44:52 AM »

data/config/settings.json -> plugins
Code: json
"newGameSectorProcGen":"com.fs.starfarer.api.impl.campaign.procgen.SectorProcGen",

You'll want your own implementation of that plugin and related code that doesn't generate derelicts/remnants.
If you can't do the needed Java or get someone who can right now (the procgen code is pretty complex), specify a stub (empty) Java class to disable the procgen and come back to it later.
Logged

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #13 on: July 19, 2017, 11:50:27 AM »

You don't need to decompile anything, but you do need to learn Java.

Every single TC that works that I've seen comes with a .jar file, so obviously compiling is necessary.
Logged

TrashMan

  • Admiral
  • *****
  • Posts: 1325
    • View Profile
Re: TOTAL CONVERSIONS?
« Reply #14 on: July 19, 2017, 11:53:47 AM »

Please post the end of the log so we can see where the error occurred. Also, have you edited that variant at all? If so, check that not only is its filename correct, but also the ID inside of it. Or the error could be someplace else entirely and the game is giving a bogus error message.

That's teh first thing I checked.

Here:
Code
29935 [Thread-4] ERROR com.fs.starfarer.combat.CombatMain  - java.lang.RuntimeException: Ship hull variant [warden_Defense] not found!
java.lang.RuntimeException: Ship hull variant [warden_Defense] not found!
at com.fs.starfarer.loading.O00O.o00000(Unknown Source)
at com.fs.starfarer.loading.SpecStore.o00000(Unknown Source)
at com.fs.starfarer.loading.SpecStore.o00000(Unknown Source)
at com.fs.starfarer.loading.thissuper$Oo.<init>(Unknown Source)
at com.fs.starfarer.loading.thissuper.<init>(Unknown Source)
at com.fs.starfarer.loading.SpecStore.new.super(Unknown Source)
at com.fs.starfarer.loading.SpecStore.OO0000(Unknown Source)
at com.fs.starfarer.loading.ResourceLoaderState.init(Unknown Source)
at com.fs.state.AppDriver.begin(Unknown Source)
at com.fs.starfarer.combat.CombatMain.main(Unknown Source)
at com.fs.starfarer.StarfarerLauncher$1.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

And thw actual variant:
Code
{
    "displayName": "Defense",
    "fluxCapacitors": 0,
    "fluxVents": 10,
    "hullId": "warden",
    "hullMods": [],
    "quality": 0,
    "variantId": "warden_Defense",
    "weaponGroups": [
        {
            "autofire": false,
            "mode": "LINKED",
            "weapons": {"WS 001": "med-en-alien-beam"}
        },
        {
            "autofire": true,
            "mode": "LINKED",
            "weapons": {
                "WS 002": "sm-en-alien-laser"
            }
        }
    ]
}
 
Logged
Pages: [1] 2