A nightmare with I2C serial link, especially with onReceive

Sorry, I missed a crucial bit. It doesn't help you didn't use code tags to post your code.

Note that 'int' is not the correct receive type; Wire transfers go byte by byte. So use 'byte' or 'uint8_t'. If you want to send an 'int' (2 bytes), break it up into two bytes and send them separately.

Your basic approach to the problem of transferring 4 variables is indeed correct: have the master send a code to the slave specifying which variable it wants to receive in return. On the slave side, you can make for instance a 'switch-case' block that checks which code was received and hence which variable to send.

Your solution would be very similar / identical to the use of so-called 'registers' that are common in I2C devices such as sensors. I've used this approach a lot too and it works like a charm. Here's a bit of code from a project I'm working on atm:

void I2C_receive_event (int quantity) 
{
  if (Wire.available() > 0) 
  {
    i2c_register = Wire.read(); //This is the 'code' the master sends telling the slave what kind of information it wants to send
    if (quantity > 1)
       process_wire_input(i2c_register, Wire.read()); //This line calls a function that processes the incoming data from the master
  }
}

void I2C_request_event () 
{
  switch (i2c_register)  //i2c_register is defined elsewhere; it's the 'code' the master sends to the slave to ask for a particular bit of information...
  {
    case REG_STATUS: //...such as the contents of the status register...
    {
      Wire.write(reg_status_v); //...which consists of 1 byte of data...
    }
    break;
    case REG_ITIME: //..or a variable that is actually of an int type, so 2 bytes...
    {
      uint8_t *ptr = (uint8_t*)(void*)reg_itime_v; //...so we use a pointer to that variable and then use it to write 2 bytes back to the master
      for (uint8_t i = 0; i < 2; i ++)
      {
        Wire.write(*ptr);
        ptr ++;
      }
    }
  }
}

The above is just an excerpt that I edited a bit for brevity. It won't compile as it is, but it shows how you could approach this. There are several ways to skin this cat. The use of a pointer to write 2 bytes of an integer is perhaps not what everyone prefers and indeed it can be done differently as well, but this is fairly efficient also if it needs to be scaled up to e.g. 4 bytes (e.g. for a float).